Merge "Fix warnings in DesktopModeAppHelper" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 497619a..ad84900 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -21,6 +21,7 @@
     java_aconfig_libraries: [
         // !!! KEEP THIS LIST ALPHABETICAL !!!
         "aconfig_mediacodec_flags_java_lib",
+        "aconfig_settingslib_flags_java_lib",
         "aconfig_trade_in_mode_flags_java_lib",
         "android-sdk-flags-java",
         "android.adaptiveauth.flags-aconfig-java",
@@ -1757,3 +1758,19 @@
     ],
     min_sdk_version: "apex_inherit",
 }
+
+// Settings Lib
+aconfig_declarations {
+    name: "aconfig_settingslib_flags",
+    package: "com.android.settingslib.flags",
+    container: "system",
+    srcs: [
+        "packages/SettingsLib/aconfig/settingslib.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "aconfig_settingslib_flags_java_lib",
+    aconfig_declarations: "aconfig_settingslib_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 26d0d65..48f0928 100644
--- a/Android.bp
+++ b/Android.bp
@@ -220,7 +220,7 @@
         "android.hardware.contexthub-V1.0-java",
         "android.hardware.contexthub-V1.1-java",
         "android.hardware.contexthub-V1.2-java",
-        "android.hardware.contexthub-V3-java",
+        "android.hardware.contexthub-V4-java",
         "android.hardware.gnss-V1.0-java",
         "android.hardware.gnss-V2.1-java",
         "android.hardware.health-V1.0-java-constants",
@@ -399,6 +399,7 @@
         "com.android.sysprop.foldlockbehavior",
         "com.android.sysprop.view",
         "framework-internal-utils",
+        "dynamic_instrumentation_manager_aidl-java",
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
         // in favor of an API stubs dependency in java_library "framework" below.
         "mimemap",
diff --git a/api/Android.bp b/api/Android.bp
index 0ac85e2..6a01677 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -127,27 +127,54 @@
     }),
 }
 
+// Create a single file containing the latest released version of the whole
+// Android public API.
+java_genrule {
+    name: "android.api.merged.public.latest",
+    srcs: [
+        ":android.api.combined.public.latest",
+    ],
+    out: ["public-latest.txt"],
+    tools: ["metalava"],
+    cmd: metalava_cmd + " merge-signatures --format=2.0 $(in) --out $(out)",
+}
+
+// Make sure that the Android public API is compatible with the
+// previously released public API.
 java_genrule {
     name: "frameworks-base-api-current-compat",
     srcs: [
-        ":android.api.public.latest",
+        ":android.api.merged.public.latest",
         ":android-incompatibilities.api.public.latest",
         ":frameworks-base-api-current.txt",
     ],
     out: ["updated-baseline.txt"],
     tools: ["metalava"],
     cmd: metalava_cmd +
-        "--check-compatibility:api:released $(location :android.api.public.latest) " +
+        "--check-compatibility:api:released $(location :android.api.merged.public.latest) " +
         "--baseline:compatibility:released $(location :android-incompatibilities.api.public.latest) " +
         "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
         "$(location :frameworks-base-api-current.txt)",
 }
 
+// Create a single file containing the latest released version of the whole
+// Android system API.
+java_genrule {
+    name: "android.api.merged.system.latest",
+    srcs: [
+        ":android.api.combined.system.latest",
+    ],
+    out: ["system-latest.txt"],
+    tools: ["metalava"],
+    cmd: metalava_cmd + " merge-signatures --format=2.0 $(in) --out $(out)",
+}
+
+// Make sure that the Android system API is compatible with the
+// previously released system API.
 java_genrule {
     name: "frameworks-base-api-system-current-compat",
     srcs: [
-        ":android.api.public.latest",
-        ":android.api.system.latest",
+        ":android.api.merged.system.latest",
         ":android-incompatibilities.api.system.latest",
         ":frameworks-base-api-current.txt",
         ":frameworks-base-api-system-current.txt",
@@ -155,20 +182,31 @@
     out: ["updated-baseline.txt"],
     tools: ["metalava"],
     cmd: metalava_cmd +
-        "--check-compatibility:api:released $(location :android.api.public.latest) " +
-        "--check-compatibility:api:released $(location :android.api.system.latest) " +
+        "--check-compatibility:api:released $(location :android.api.merged.system.latest) " +
         "--baseline:compatibility:released $(location :android-incompatibilities.api.system.latest) " +
         "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
         "$(location :frameworks-base-api-current.txt) " +
         "$(location :frameworks-base-api-system-current.txt)",
 }
 
+// Create a single file containing the latest released version of the whole
+// Android module-lib API.
+java_genrule {
+    name: "android.api.merged.module-lib.latest",
+    srcs: [
+        ":android.api.combined.module-lib.latest",
+    ],
+    out: ["module-lib-latest.txt"],
+    tools: ["metalava"],
+    cmd: metalava_cmd + " merge-signatures --format=2.0 $(in) --out $(out)",
+}
+
+// Make sure that the Android module-lib API is compatible with the
+// previously released module-lib API.
 java_genrule {
     name: "frameworks-base-api-module-lib-current-compat",
     srcs: [
-        ":android.api.public.latest",
-        ":android.api.system.latest",
-        ":android.api.module-lib.latest",
+        ":android.api.merged.module-lib.latest",
         ":android-incompatibilities.api.module-lib.latest",
         ":frameworks-base-api-current.txt",
         ":frameworks-base-api-system-current.txt",
@@ -177,9 +215,7 @@
     out: ["updated-baseline.txt"],
     tools: ["metalava"],
     cmd: metalava_cmd +
-        "--check-compatibility:api:released $(location :android.api.public.latest) " +
-        "--check-compatibility:api:released $(location :android.api.system.latest) " +
-        "--check-compatibility:api:released $(location :android.api.module-lib.latest) " +
+        "--check-compatibility:api:released $(location :android.api.merged.module-lib.latest) " +
         "--baseline:compatibility:released $(location :android-incompatibilities.api.module-lib.latest) " +
         "--update-baseline:compatibility:released $(genDir)/updated-baseline.txt " +
         "$(location :frameworks-base-api-current.txt) " +
diff --git a/core/api/current.txt b/core/api/current.txt
index ead6554..f03ef8c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -263,6 +263,7 @@
     field public static final String READ_SMS = "android.permission.READ_SMS";
     field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
     field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
+    field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String READ_SYSTEM_PREFERENCES = "android.permission.READ_SYSTEM_PREFERENCES";
     field public static final String READ_VOICEMAIL = "com.android.voicemail.permission.READ_VOICEMAIL";
     field public static final String REBOOT = "android.permission.REBOOT";
     field public static final String RECEIVE_BOOT_COMPLETED = "android.permission.RECEIVE_BOOT_COMPLETED";
@@ -313,6 +314,7 @@
     field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
     field public static final String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
     field public static final String TURN_SCREEN_ON = "android.permission.TURN_SCREEN_ON";
+    field @FlaggedApi("android.app.enable_tv_implicit_enter_pip_restriction") public static final String TV_IMPLICIT_ENTER_PIP = "android.permission.TV_IMPLICIT_ENTER_PIP";
     field public static final String UNINSTALL_SHORTCUT = "com.android.launcher.permission.UNINSTALL_SHORTCUT";
     field public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
     field public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION = "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION";
@@ -334,6 +336,7 @@
     field public static final String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS";
     field public static final String WRITE_SETTINGS = "android.permission.WRITE_SETTINGS";
     field public static final String WRITE_SYNC_SETTINGS = "android.permission.WRITE_SYNC_SETTINGS";
+    field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String WRITE_SYSTEM_PREFERENCES = "android.permission.WRITE_SYSTEM_PREFERENCES";
     field public static final String WRITE_VOICEMAIL = "com.android.voicemail.permission.WRITE_VOICEMAIL";
   }
 
@@ -475,6 +478,8 @@
     field public static final int alpha = 16843551; // 0x101031f
     field public static final int alphabeticModifiers = 16844110; // 0x101054e
     field public static final int alphabeticShortcut = 16843235; // 0x10101e3
+    field @FlaggedApi("android.content.pm.change_launcher_badging") public static final int alternateLauncherIcons;
+    field @FlaggedApi("android.content.pm.change_launcher_badging") public static final int alternateLauncherLabels;
     field public static final int alwaysDrawnWithCache = 16842991; // 0x10100ef
     field public static final int alwaysRetainTaskState = 16843267; // 0x1010203
     field @Deprecated public static final int amPmBackgroundColor = 16843941; // 0x10104a5
@@ -8787,8 +8792,31 @@
 
 package android.app.appfunctions {
 
+  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionException extends java.lang.Exception implements android.os.Parcelable {
+    ctor public AppFunctionException(int, @Nullable String);
+    ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method public int getErrorCategory();
+    method public int getErrorCode();
+    method @Nullable public String getErrorMessage();
+    method @NonNull public android.os.Bundle getExtras();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.AppFunctionException> CREATOR;
+    field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8
+    field public static final int ERROR_CANCELLED = 2001; // 0x7d1
+    field public static final int ERROR_CATEGORY_APP = 3; // 0x3
+    field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
+    field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
+    field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
+    field public static final int ERROR_DENIED = 1000; // 0x3e8
+    field public static final int ERROR_DISABLED = 1002; // 0x3ea
+    field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
+    field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
+    field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
+  }
+
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
-    method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+    method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
     method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
@@ -8800,7 +8828,7 @@
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service {
     ctor public AppFunctionService();
     method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>);
+    method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.app.appfunctions.ExecuteAppFunctionResponse,android.app.appfunctions.AppFunctionException>);
     field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
   }
 
@@ -8822,30 +8850,14 @@
   }
 
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
+    ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument);
+    ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
     method public int describeContents();
-    method public int getErrorCategory();
-    method @Nullable public String getErrorMessage();
     method @NonNull public android.os.Bundle getExtras();
-    method public int getResultCode();
     method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
-    method public boolean isSuccess();
-    method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
-    method @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") @NonNull public static android.app.appfunctions.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
-    field public static final int ERROR_CATEGORY_APP = 3; // 0x3
-    field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
-    field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
-    field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
-    field public static final String PROPERTY_RETURN_VALUE = "returnValue";
-    field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
-    field public static final int RESULT_CANCELLED = 2001; // 0x7d1
-    field public static final int RESULT_DENIED = 1000; // 0x3e8
-    field public static final int RESULT_DISABLED = 1002; // 0x3ea
-    field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb
-    field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
-    field public static final int RESULT_OK = 0; // 0x0
-    field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0
+    field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
   }
 
 }
@@ -8868,6 +8880,7 @@
     method public void setWebUri(android.net.Uri);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.AssistContent> CREATOR;
+    field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXTRA_APP_FUNCTION_DATA = "android.app.assist.extra.APP_FUNCTION_DATA";
   }
 
   public class AssistStructure implements android.os.Parcelable {
@@ -19777,6 +19790,9 @@
     field public static final int EDGE_MODE_HIGH_QUALITY = 2; // 0x2
     field public static final int EDGE_MODE_OFF = 0; // 0x0
     field public static final int EDGE_MODE_ZERO_SHUTTER_LAG = 3; // 0x3
+    field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_OFF = 1; // 0x1
+    field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_ON = 2; // 0x2
+    field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") public static final int EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN = 0; // 0x0
     field public static final int FLASH_MODE_OFF = 0; // 0x0
     field public static final int FLASH_MODE_SINGLE = 1; // 0x1
     field public static final int FLASH_MODE_TORCH = 2; // 0x2
@@ -20076,6 +20092,7 @@
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_CURRENT_TYPE;
+    field @FlaggedApi("com.android.internal.camera.flags.night_mode_indicator") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_NIGHT_MODE_INDICATOR;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_STRENGTH;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_STATE;
@@ -23973,6 +23990,7 @@
     field public static final String KEY_MPEGH_COMPATIBLE_SETS = "mpegh-compatible-sets";
     field public static final String KEY_MPEGH_PROFILE_LEVEL_INDICATION = "mpegh-profile-level-indication";
     field public static final String KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT = "mpegh-reference-channel-layout";
+    field @FlaggedApi("android.media.codec.num_input_slots") public static final String KEY_NUM_SLOTS = "num-slots";
     field public static final String KEY_OPERATING_RATE = "operating-rate";
     field public static final String KEY_OUTPUT_REORDER_DEPTH = "output-reorder-depth";
     field public static final String KEY_PCM_ENCODING = "pcm-encoding";
@@ -40371,7 +40389,7 @@
     method @NonNull public android.security.keystore.KeyProtection.Builder setUserPresenceRequired(boolean);
   }
 
-  @FlaggedApi("android.security.keystore_grant_api") public class KeyStoreManager {
+  @FlaggedApi("android.security.keystore_grant_api") public final class KeyStoreManager {
     method @NonNull public java.util.List<java.security.cert.X509Certificate> getGrantedCertificateChainFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException;
     method @NonNull public java.security.Key getGrantedKeyFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException;
     method @NonNull public java.security.KeyPair getGrantedKeyPairFromId(long) throws android.security.keystore.KeyPermanentlyInvalidatedException, java.security.UnrecoverableKeyException;
@@ -41999,6 +42017,174 @@
 
 }
 
+package android.service.settings.preferences {
+
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class GetValueRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getPreferenceKey();
+    method @NonNull public String getScreenKey();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueRequest> CREATOR;
+  }
+
+  public static final class GetValueRequest.Builder {
+    ctor public GetValueRequest.Builder(@NonNull String, @NonNull String);
+    method @NonNull public android.service.settings.preferences.GetValueRequest build();
+  }
+
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class GetValueResult implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.service.settings.preferences.SettingsPreferenceMetadata getMetadata();
+    method public int getResultCode();
+    method @Nullable public android.service.settings.preferences.SettingsPreferenceValue getValue();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.GetValueResult> CREATOR;
+    field public static final int RESULT_DISALLOW = 4; // 0x4
+    field public static final int RESULT_INTERNAL_ERROR = 6; // 0x6
+    field public static final int RESULT_INVALID_REQUEST = 5; // 0x5
+    field public static final int RESULT_OK = 0; // 0x0
+    field public static final int RESULT_REQUIRE_APP_PERMISSION = 3; // 0x3
+    field public static final int RESULT_UNAVAILABLE = 2; // 0x2
+    field public static final int RESULT_UNSUPPORTED = 1; // 0x1
+  }
+
+  public static final class GetValueResult.Builder {
+    ctor public GetValueResult.Builder(int);
+    method @NonNull public android.service.settings.preferences.GetValueResult build();
+    method @NonNull public android.service.settings.preferences.GetValueResult.Builder setMetadata(@Nullable android.service.settings.preferences.SettingsPreferenceMetadata);
+    method @NonNull public android.service.settings.preferences.GetValueResult.Builder setValue(@Nullable android.service.settings.preferences.SettingsPreferenceValue);
+  }
+
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class MetadataRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataRequest> CREATOR;
+  }
+
+  public static final class MetadataRequest.Builder {
+    ctor public MetadataRequest.Builder();
+    method @NonNull public android.service.settings.preferences.MetadataRequest build();
+  }
+
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class MetadataResult implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata> getMetadataList();
+    method public int getResultCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.MetadataResult> CREATOR;
+    field public static final int RESULT_INTERNAL_ERROR = 2; // 0x2
+    field public static final int RESULT_OK = 0; // 0x0
+    field public static final int RESULT_UNSUPPORTED = 1; // 0x1
+  }
+
+  public static final class MetadataResult.Builder {
+    ctor public MetadataResult.Builder(int);
+    method @NonNull public android.service.settings.preferences.MetadataResult build();
+    method @NonNull public android.service.settings.preferences.MetadataResult.Builder setMetadataList(@NonNull java.util.List<android.service.settings.preferences.SettingsPreferenceMetadata>);
+  }
+
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SetValueRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getPreferenceKey();
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue getPreferenceValue();
+    method @NonNull public String getScreenKey();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueRequest> CREATOR;
+  }
+
+  public static final class SetValueRequest.Builder {
+    ctor public SetValueRequest.Builder(@NonNull String, @NonNull String, @NonNull android.service.settings.preferences.SettingsPreferenceValue);
+    method @NonNull public android.service.settings.preferences.SetValueRequest build();
+  }
+
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SetValueResult implements android.os.Parcelable {
+    method public int describeContents();
+    method public int getResultCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SetValueResult> CREATOR;
+    field public static final int RESULT_DISABLED = 2; // 0x2
+    field public static final int RESULT_DISALLOW = 7; // 0x7
+    field public static final int RESULT_INTERNAL_ERROR = 9; // 0x9
+    field public static final int RESULT_INVALID_REQUEST = 8; // 0x8
+    field public static final int RESULT_OK = 0; // 0x0
+    field public static final int RESULT_REQUIRE_APP_PERMISSION = 5; // 0x5
+    field public static final int RESULT_REQUIRE_USER_CONSENT = 6; // 0x6
+    field public static final int RESULT_RESTRICTED = 3; // 0x3
+    field public static final int RESULT_UNAVAILABLE = 4; // 0x4
+    field public static final int RESULT_UNSUPPORTED = 1; // 0x1
+  }
+
+  public static final class SetValueResult.Builder {
+    ctor public SetValueResult.Builder(int);
+    method @NonNull public android.service.settings.preferences.SetValueResult build();
+  }
+
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceMetadata implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<java.lang.String> getBreadcrumbs();
+    method @NonNull public android.os.Bundle getExtras();
+    method @NonNull public String getKey();
+    method @Nullable public android.app.PendingIntent getLaunchIntent();
+    method @NonNull public java.util.List<java.lang.String> getReadPermissions();
+    method @NonNull public String getScreenKey();
+    method @Nullable public String getSummary();
+    method @Nullable public String getTitle();
+    method @NonNull public java.util.List<java.lang.String> getWritePermissions();
+    method public int getWriteSensitivity();
+    method public boolean isAvailable();
+    method public boolean isEnabled();
+    method public boolean isRestricted();
+    method public boolean isWritable();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceMetadata> CREATOR;
+    field public static final int INTENT_ONLY = 2; // 0x2
+    field public static final int NOT_SENSITIVE = 0; // 0x0
+    field public static final int SENSITIVE = 1; // 0x1
+  }
+
+  public static final class SettingsPreferenceMetadata.Builder {
+    ctor public SettingsPreferenceMetadata.Builder(@NonNull String, @NonNull String);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata build();
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setAvailable(boolean);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setBreadcrumbs(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setEnabled(boolean);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setExtras(@NonNull android.os.Bundle);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setLaunchIntent(@Nullable android.app.PendingIntent);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setReadPermissions(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setRestricted(boolean);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setSummary(@Nullable String);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setTitle(@Nullable String);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWritable(boolean);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWritePermissions(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceMetadata.Builder setWriteSensitivity(int);
+  }
+
+  @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public final class SettingsPreferenceValue implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean getBooleanValue();
+    method public double getDoubleValue();
+    method public long getLongValue();
+    method @Nullable public String getStringValue();
+    method public int getType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.settings.preferences.SettingsPreferenceValue> CREATOR;
+    field public static final int TYPE_BOOLEAN = 0; // 0x0
+    field public static final int TYPE_DOUBLE = 2; // 0x2
+    field public static final int TYPE_LONG = 1; // 0x1
+    field public static final int TYPE_STRING = 3; // 0x3
+  }
+
+  public static final class SettingsPreferenceValue.Builder {
+    ctor public SettingsPreferenceValue.Builder(int);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue build();
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setBooleanValue(boolean);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setDoubleValue(double);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setLongValue(long);
+    method @NonNull public android.service.settings.preferences.SettingsPreferenceValue.Builder setStringValue(@Nullable String);
+  }
+
+}
+
 package android.service.textservice {
 
   public abstract class SpellCheckerService extends android.app.Service {
@@ -44104,6 +44290,8 @@
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1
     field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int CARRIER_ROAMING_NTN_CONNECT_MANUAL = 1; // 0x1
     field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_ONLY = 0; // 0x0
     field public static final int CROSS_SIM_SPN_FORMAT_CARRIER_NAME_WITH_BRANDING = 1; // 0x1
     field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
@@ -44174,11 +44362,14 @@
     field public static final String KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY = "carrier_nr_availabilities_int_array";
     field public static final String KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL = "carrier_provisions_wifi_merged_networks_bool";
     field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT = "carrier_roaming_ntn_connect_type_int";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_CARRIER_ROAMING_NTN_EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_INT = "carrier_roaming_ntn_emergency_call_to_satellite_handover_type_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY = "carrier_roaming_satellite_default_services_int_array";
     field public static final String KEY_CARRIER_SERVICE_NAME_STRING_ARRAY = "carrier_service_name_array";
     field public static final String KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY = "carrier_service_number_array";
     field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT = "carrier_supported_satellite_notification_hysteresis_sec_int";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_CARRIER_SUPPORTED_SATELLITE_SERVICES_PER_PROVIDER_BUNDLE = "carrier_supported_satellite_services_per_provider_bundle";
     field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
     field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
@@ -44229,6 +44420,7 @@
     field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
     field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
     field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL = "disable_dun_apn_while_roaming_with_preset_apn_bool";
     field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool";
     field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
     field public static final String KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL = "display_call_strength_indicator_bool";
@@ -44241,6 +44433,8 @@
     field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
     field public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool";
     field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT = "emergency_call_to_satellite_t911_handover_timeout_millis_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL = "emergency_messaging_supported_bool";
     field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int";
     field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array";
     field public static final String KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL = "enable_cross_sim_calling_on_opportunistic_data_bool";
@@ -44287,6 +44481,7 @@
     field public static final String KEY_MMS_MAX_IMAGE_HEIGHT_INT = "maxImageHeight";
     field public static final String KEY_MMS_MAX_IMAGE_WIDTH_INT = "maxImageWidth";
     field public static final String KEY_MMS_MAX_MESSAGE_SIZE_INT = "maxMessageSize";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT = "mms_max_ntn_payload_size_bytes_int";
     field public static final String KEY_MMS_MESSAGE_TEXT_MAX_SIZE_INT = "maxMessageTextSize";
     field public static final String KEY_MMS_MMS_DELIVERY_REPORT_ENABLED_BOOL = "enableMMSDeliveryReports";
     field public static final String KEY_MMS_MMS_ENABLED_BOOL = "enabledMMS";
@@ -44325,6 +44520,7 @@
     field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG = "opportunistic_network_max_backoff_time_long";
     field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL = "override_wfc_roaming_mode_while_using_ntn_bool";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int";
     field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
     field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
@@ -44343,6 +44539,7 @@
     field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
     field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array";
     field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL = "remove_satellite_plmn_in_manual_network_scan_bool";
     field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
     field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
     field public static final String KEY_RTT_AUTO_UPGRADE_BOOL = "rtt_auto_upgrade_bool";
@@ -44354,13 +44551,18 @@
     field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT = "satellite_data_support_mode_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING = "satellite_entitlement_app_name_string";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ESOS_SUPPORTED_BOOL = "satellite_esos_supported_bool";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING = "satellite_information_redirect_url_string";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING = "satellite_nidd_apn_name_string";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_ESOS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_esos_inactivity_timeout_sec_int";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_p2p_sms_inactivity_timeout_sec_int";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_P2P_SMS_SUPPORTED_BOOL = "satellite_roaming_p2p_sms_supported_bool";
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn") public static final String KEY_SATELLITE_ROAMING_SCREEN_OFF_INACTIVITY_TIMEOUT_SEC_INT = "satellite_roaming_screen_off_inactivity_timeout_sec_int";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL = "satellite_roaming_turn_off_session_for_emergency_call_bool";
     field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
     field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
     field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -44430,6 +44632,9 @@
     field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
     field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
     field public static final String REMOVE_GROUP_UUID_STRING = "00000000-0000-0000-0000-000000000000";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_DATA_SUPPORT_ALL = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED = 0; // 0x0
     field public static final int SERVICE_CLASS_NONE = 0; // 0x0
     field public static final int SERVICE_CLASS_VOICE = 1; // 0x1
     field public static final int USSD_OVER_CS_ONLY = 2; // 0x2
@@ -51799,6 +52004,7 @@
     field public static final int KEYCODE_CHANNEL_DOWN = 167; // 0xa7
     field public static final int KEYCODE_CHANNEL_UP = 166; // 0xa6
     field public static final int KEYCODE_CLEAR = 28; // 0x1c
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_CLOSE = 321; // 0x141
     field public static final int KEYCODE_COMMA = 55; // 0x37
     field public static final int KEYCODE_CONTACTS = 207; // 0xcf
     field public static final int KEYCODE_COPY = 278; // 0x116
@@ -51811,6 +52017,8 @@
     field public static final int KEYCODE_DEMO_APP_2 = 302; // 0x12e
     field public static final int KEYCODE_DEMO_APP_3 = 303; // 0x12f
     field public static final int KEYCODE_DEMO_APP_4 = 304; // 0x130
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_DICTATE = 319; // 0x13f
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_DO_NOT_DISTURB = 322; // 0x142
     field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
     field public static final int KEYCODE_DPAD_DOWN = 20; // 0x14
     field public static final int KEYCODE_DPAD_DOWN_LEFT = 269; // 0x10d
@@ -51823,7 +52031,7 @@
     field public static final int KEYCODE_DVR = 173; // 0xad
     field public static final int KEYCODE_E = 33; // 0x21
     field public static final int KEYCODE_EISU = 212; // 0xd4
-    field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_EMOJI_PICKER = 317; // 0x13d
+    field public static final int KEYCODE_EMOJI_PICKER = 317; // 0x13d
     field public static final int KEYCODE_ENDCALL = 6; // 0x6
     field public static final int KEYCODE_ENTER = 66; // 0x42
     field public static final int KEYCODE_ENVELOPE = 65; // 0x41
@@ -51835,7 +52043,19 @@
     field public static final int KEYCODE_F10 = 140; // 0x8c
     field public static final int KEYCODE_F11 = 141; // 0x8d
     field public static final int KEYCODE_F12 = 142; // 0x8e
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F13 = 326; // 0x146
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F14 = 327; // 0x147
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F15 = 328; // 0x148
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F16 = 329; // 0x149
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F17 = 330; // 0x14a
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F18 = 331; // 0x14b
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F19 = 332; // 0x14c
     field public static final int KEYCODE_F2 = 132; // 0x84
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F20 = 333; // 0x14d
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F21 = 334; // 0x14e
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F22 = 335; // 0x14f
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F23 = 336; // 0x150
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_F24 = 337; // 0x151
     field public static final int KEYCODE_F3 = 133; // 0x85
     field public static final int KEYCODE_F4 = 134; // 0x86
     field public static final int KEYCODE_F5 = 135; // 0x87
@@ -51850,6 +52070,7 @@
     field public static final int KEYCODE_FOCUS = 80; // 0x50
     field public static final int KEYCODE_FORWARD = 125; // 0x7d
     field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_FULLSCREEN = 325; // 0x145
     field public static final int KEYCODE_FUNCTION = 119; // 0x77
     field public static final int KEYCODE_G = 35; // 0x23
     field public static final int KEYCODE_GRAVE = 68; // 0x44
@@ -51873,6 +52094,7 @@
     field public static final int KEYCODE_LANGUAGE_SWITCH = 204; // 0xcc
     field public static final int KEYCODE_LAST_CHANNEL = 229; // 0xe5
     field public static final int KEYCODE_LEFT_BRACKET = 71; // 0x47
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_LOCK = 324; // 0x144
     field public static final int KEYCODE_M = 41; // 0x29
     field public static final int KEYCODE_MACRO_1 = 313; // 0x139
     field public static final int KEYCODE_MACRO_2 = 314; // 0x13a
@@ -51910,6 +52132,7 @@
     field public static final int KEYCODE_NAVIGATE_NEXT = 261; // 0x105
     field public static final int KEYCODE_NAVIGATE_OUT = 263; // 0x107
     field public static final int KEYCODE_NAVIGATE_PREVIOUS = 260; // 0x104
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_NEW = 320; // 0x140
     field public static final int KEYCODE_NOTIFICATION = 83; // 0x53
     field public static final int KEYCODE_NUM = 78; // 0x4e
     field public static final int KEYCODE_NUMPAD_0 = 144; // 0x90
@@ -51944,6 +52167,7 @@
     field public static final int KEYCODE_PLUS = 81; // 0x51
     field public static final int KEYCODE_POUND = 18; // 0x12
     field public static final int KEYCODE_POWER = 26; // 0x1a
+    field @FlaggedApi("com.android.hardware.input.enable_new_25q2_keycodes") public static final int KEYCODE_PRINT = 323; // 0x143
     field public static final int KEYCODE_PROFILE_SWITCH = 288; // 0x120
     field public static final int KEYCODE_PROG_BLUE = 186; // 0xba
     field public static final int KEYCODE_PROG_GREEN = 184; // 0xb8
@@ -51956,7 +52180,7 @@
     field public static final int KEYCODE_RIGHT_BRACKET = 72; // 0x48
     field public static final int KEYCODE_RO = 217; // 0xd9
     field public static final int KEYCODE_S = 47; // 0x2f
-    field @FlaggedApi("com.android.hardware.input.emoji_and_screenshot_keycodes_available") public static final int KEYCODE_SCREENSHOT = 318; // 0x13e
+    field public static final int KEYCODE_SCREENSHOT = 318; // 0x13e
     field public static final int KEYCODE_SCROLL_LOCK = 116; // 0x74
     field public static final int KEYCODE_SEARCH = 84; // 0x54
     field public static final int KEYCODE_SEMICOLON = 74; // 0x4a
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2a01ca0..8954f8e 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -138,6 +138,7 @@
     field public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission.DISABLE_SYSTEM_SOUND_EFFECTS";
     field public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission.DISPATCH_PROVISIONING_MESSAGE";
     field public static final String DOMAIN_VERIFICATION_AGENT = "android.permission.DOMAIN_VERIFICATION_AGENT";
+    field @FlaggedApi("com.android.art.flags.executable_method_file_offsets") public static final String DYNAMIC_INSTRUMENTATION = "android.permission.DYNAMIC_INSTRUMENTATION";
     field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE";
     field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
     field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
@@ -389,6 +390,7 @@
     field public static final String START_CROSS_PROFILE_ACTIVITIES = "android.permission.START_CROSS_PROFILE_ACTIVITIES";
     field public static final String START_REVIEW_PERMISSION_DECISIONS = "android.permission.START_REVIEW_PERMISSION_DECISIONS";
     field public static final String START_TASKS_FROM_RECENTS = "android.permission.START_TASKS_FROM_RECENTS";
+    field @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final String START_VIBRATION_SESSIONS = "android.permission.START_VIBRATION_SESSIONS";
     field public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
     field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
     field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
@@ -705,6 +707,7 @@
     field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
     field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
     field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
+    field @FlaggedApi("android.permission.flags.platform_oxygen_saturation_enabled") public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
     field @FlaggedApi("android.permission.flags.platform_skin_temperature_enabled") public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
     field public static final String OPSTR_READ_WRITE_HEALTH_DATA = "android:read_write_health_data";
     field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
@@ -11660,8 +11663,11 @@
   public abstract class Vibrator {
     method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
     method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void addVibratorStateListener(@NonNull java.util.concurrent.Executor, @NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+    method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public boolean areVendorEffectsSupported();
+    method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public boolean areVendorSessionsSupported();
     method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public boolean isVibrating();
     method @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE) public void removeVibratorStateListener(@NonNull android.os.Vibrator.OnVibratorStateChangedListener);
+    method @FlaggedApi("android.os.vibrator.vendor_vibration_effects") @RequiresPermission(allOf={android.Manifest.permission.VIBRATE, android.Manifest.permission.VIBRATE_VENDOR_EFFECTS, android.Manifest.permission.START_VIBRATION_SESSIONS}) public void startVendorSession(@NonNull android.os.VibrationAttributes, @Nullable String, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.vibrator.VendorVibrationSession.Callback);
   }
 
   public static interface Vibrator.OnVibratorStateChangedListener {
@@ -11813,6 +11819,28 @@
 
 }
 
+package android.os.vibrator {
+
+  @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public final class VendorVibrationSession implements java.lang.AutoCloseable {
+    method public void cancel();
+    method public void close();
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(@NonNull android.os.VibrationEffect, @Nullable String);
+    field public static final int STATUS_CANCELED = 4; // 0x4
+    field public static final int STATUS_IGNORED = 2; // 0x2
+    field public static final int STATUS_SUCCESS = 1; // 0x1
+    field public static final int STATUS_UNKNOWN = 0; // 0x0
+    field public static final int STATUS_UNKNOWN_ERROR = 5; // 0x5
+    field public static final int STATUS_UNSUPPORTED = 3; // 0x3
+  }
+
+  public static interface VendorVibrationSession.Callback {
+    method public void onFinished(int);
+    method public void onFinishing();
+    method public void onStarted(@NonNull android.os.vibrator.VendorVibrationSession);
+  }
+
+}
+
 package android.os.vibrator.persistence {
 
   @FlaggedApi("android.os.vibrator.vibration_xml_apis") public final class ParsedVibration {
@@ -18305,7 +18333,12 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForModemStateChanged(@NonNull android.telephony.satellite.SatelliteModemStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7; // 0x7
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5; // 0x5
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4; // 0x4
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int DATAGRAM_TYPE_SMS = 6; // 0x6
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1; // 0x1
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DATAGRAM_TYPE_UNKNOWN = 0; // 0x0
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; // 0x2
@@ -18325,6 +18358,7 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2
     field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER = 0; // 0x0
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_NONE = 6; // 0x6
@@ -18338,6 +18372,8 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_MODEM_STATE_DISABLING_SATELLITE = 9; // 0x9
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_MODEM_STATE_ENABLING_SATELLITE = 8; // 0x8
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_IDLE = 0; // 0x0
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_LISTENING = 1; // 0x1
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_NOT_CONNECTED = 6; // 0x6
@@ -18345,11 +18381,16 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNAVAILABLE = 5; // 0x5
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_MODEM_STATE_UNKNOWN = -1; // 0xffffffff
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ACCESS_BARRED = 16; // 0x10
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_DISABLE_IN_PROGRESS = 28; // 0x1c
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27; // 0x1b
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_ENABLE_IN_PROGRESS = 29; // 0x1d
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ERROR = 1; // 0x1
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_ILLEGAL_STATE = 23; // 0x17
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_ARGUMENTS = 8; // 0x8
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_MODEM_STATE = 7; // 0x7
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_INVALID_TELEPHONY_STATE = 6; // 0x6
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_LOCATION_DISABLED = 25; // 0x19
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26; // 0x1a
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_BUSY = 22; // 0x16
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_ERROR = 4; // 0x4
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24; // 0x18
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1173519..8dd12172 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3729,7 +3729,7 @@
     method public final int getDisplayId();
     method public final void setDisplayId(int);
     field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
-    field public static final int LAST_KEYCODE = 318; // 0x13e
+    field public static final int LAST_KEYCODE = 337; // 0x151
   }
 
   public final class KeyboardShortcutGroup implements android.os.Parcelable {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 71623c5..cf5ebbaa3 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -28,6 +28,7 @@
     exclude_srcs: [
         "android/os/*MessageQueue/**/*.java",
         "android/ranging/**/*.java",
+        ":dynamic_instrumentation_manager_aidl_sources",
     ],
     visibility: ["//frameworks/base"],
 }
@@ -120,6 +121,17 @@
 }
 
 filegroup {
+    name: "dynamic_instrumentation_manager_aidl_sources",
+    srcs: ["android/os/instrumentation/*.aidl"],
+}
+
+aidl_interface {
+    name: "dynamic_instrumentation_manager_aidl",
+    srcs: [":dynamic_instrumentation_manager_aidl_sources"],
+    unstable: true,
+}
+
+filegroup {
     name: "framework-internal-display-sources",
     srcs: ["com/android/internal/display/BrightnessSynchronizer.java"],
     visibility: ["//frameworks/base/services/tests/mockingservicestests"],
@@ -685,16 +697,31 @@
 // Generates com.android.internal.pm.RoSystemFeatures, optionally compiling in
 // details about fixed system features defined by build flags. When disabled,
 // the APIs are simply passthrough stubs with no meaningful side effects.
+// TODO(b/203143243): Implement the `--feature=` aggregation  directly with a native soong module.
 genrule {
     name: "systemfeatures-gen-srcs",
     cmd: "$(location systemfeatures-gen-tool) com.android.internal.pm.RoSystemFeatures " +
         // --readonly=false (default) makes the codegen an effective no-op passthrough API.
         " --readonly=" + gen_readonly_feature_apis +
-        // For now, only export "android.hardware.type.*" system features APIs.
-        // TODO(b/203143243): Use an intermediate soong var that aggregates all declared
-        // RELEASE_SYSTEM_FEATURE_* declarations into a single arg.
-        " --feature-apis=AUTOMOTIVE,WATCH,TELEVISION,EMBEDDED,PC" +
-        " > $(out)",
+        " --feature=AUTOMOTIVE:" + select(release_flag("RELEASE_SYSTEM_FEATURE_AUTOMOTIVE"), {
+            any @ value: value,
+            default: "",
+        }) + " --feature=EMBEDDED:" + select(release_flag("RELEASE_SYSTEM_FEATURE_EMBEDDED"), {
+            any @ value: value,
+            default: "",
+        }) + " --feature=LEANBACK:" + select(release_flag("RELEASE_SYSTEM_FEATURE_LEANBACK"), {
+            any @ value: value,
+            default: "",
+        }) + " --feature=PC:" + select(release_flag("RELEASE_SYSTEM_FEATURE_PC"), {
+            any @ value: value,
+            default: "",
+        }) + " --feature=TELEVISION:" + select(release_flag("RELEASE_SYSTEM_FEATURE_TELEVISION"), {
+            any @ value: value,
+            default: "",
+        }) + " --feature=WATCH:" + select(release_flag("RELEASE_SYSTEM_FEATURE_WATCH"), {
+            any @ value: value,
+            default: "",
+        }) + " > $(out)",
     out: [
         "RoSystemFeatures.java",
     ],
diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS
index 0218a78..bc8efa9 100644
--- a/core/java/android/adaptiveauth/OWNERS
+++ b/core/java/android/adaptiveauth/OWNERS
@@ -1 +1 @@
-include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
+include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
\ No newline at end of file
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 7a1c759..3fccc17 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -30,6 +30,7 @@
 
 import static java.lang.Character.MIN_VALUE;
 
+import android.Manifest;
 import android.annotation.AnimRes;
 import android.annotation.CallSuper;
 import android.annotation.CallbackExecutor;
@@ -3193,6 +3194,16 @@
         return ActivityTaskManager.getMaxNumPictureInPictureActions(this);
     }
 
+    private boolean isImplicitEnterPipProhibited() {
+        PackageManager pm = getPackageManager();
+        if (android.app.Flags.enableTvImplicitEnterPipRestriction()) {
+            return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                    && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP,
+                    getPackageName()) == PackageManager.PERMISSION_DENIED;
+        }
+        return false;
+    }
+
     /**
      * @return Whether this device supports picture-in-picture.
      */
@@ -9192,6 +9203,8 @@
         }
         dispatchActivityPreResumed();
 
+        mCanEnterPictureInPicture = true;
+
         mFragments.execPendingActions();
 
         mLastNonConfigurationInstances = null;
@@ -9243,6 +9256,11 @@
             Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performPause:"
                     + mComponent.getClassName());
         }
+
+        if (isImplicitEnterPipProhibited()) {
+            mCanEnterPictureInPicture = false;
+        }
+
         dispatchActivityPrePaused();
         mDoReportFullyDrawn = false;
         mFragments.dispatchPause();
@@ -9265,6 +9283,10 @@
 
     final void performUserLeaving() {
         onUserInteraction();
+
+        if (isImplicitEnterPipProhibited()) {
+            mCanEnterPictureInPicture = false;
+        }
         onUserLeaveHint();
     }
 
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 6879458..009cd72 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -101,7 +101,6 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
             FLAG_UNDEFINED,
-            FLAG_BASE,
             FLAG_LETTERBOX_EDU_ENABLED,
             FLAG_ELIGIBLE_FOR_LETTERBOX_EDU,
             FLAG_LETTERBOXED,
@@ -115,6 +114,10 @@
     })
     public @interface TopActivityFlag {}
 
+    /**
+     * A combination of {@link TopActivityFlag}s that have been enabled through
+     * {@link #setTopActivityFlag}.
+     */
     @TopActivityFlag
     private int mTopActivityFlags;
 
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 38c8583..fd70f4f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1621,9 +1621,12 @@
      */
     public static final int OP_RANGING = AppOpEnums.APP_OP_RANGING;
 
+    /** @hide Access to read oxygen saturation. */
+    public static final int OP_READ_OXYGEN_SATURATION = AppOpEnums.APP_OP_READ_OXYGEN_SATURATION;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 152;
+    public static final int _NUM_OP = 153;
 
     /**
      * All app ops represented as strings.
@@ -1779,6 +1782,7 @@
             OPSTR_READ_HEART_RATE,
             OPSTR_READ_SKIN_TEMPERATURE,
             OPSTR_RANGING,
+            OPSTR_READ_OXYGEN_SATURATION,
     })
     public @interface AppOpString {}
 
@@ -2521,6 +2525,11 @@
     @FlaggedApi(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
     public static final String OPSTR_READ_HEART_RATE = "android:read_heart_rate";
 
+    /** @hide Access to read oxygen saturation. */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_PLATFORM_OXYGEN_SATURATION_ENABLED)
+    public static final String OPSTR_READ_OXYGEN_SATURATION = "android:read_oxygen_saturation";
+
     /** @hide Access to read skin temperature. */
     @SystemApi
     @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED)
@@ -2608,6 +2617,7 @@
             // Health
             Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE,
             Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
+            Flags.platformOxygenSaturationEnabled() ? OP_READ_OXYGEN_SATURATION : OP_NONE,
     };
 
     /**
@@ -3129,6 +3139,11 @@
             .setPermission(Flags.rangingPermissionEnabled()?
                 Manifest.permission.RANGING : null)
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_OXYGEN_SATURATION, OPSTR_READ_OXYGEN_SATURATION,
+            "READ_OXYGEN_SATURATION").setPermission(
+                Flags.platformOxygenSaturationEnabled()
+                    ? HealthPermissions.READ_OXYGEN_SATURATION : null)
+            .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
     };
 
     // The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2cf718e..3ae60d71 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1167,6 +1167,7 @@
     @Override
     public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
         try {
+            intent.collectExtraIntentKeys();
             ActivityTaskManager.getService().startActivityAsUser(
                     mMainThread.getApplicationThread(), getOpPackageName(), getAttributionTag(),
                     intent, intent.resolveTypeIfNeeded(getContentResolver()),
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index c6c0395..0381ee0 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -155,7 +155,7 @@
             FOREGROUND_SERVICE_IMMEDIATE,
             FOREGROUND_SERVICE_DEFERRED
     })
-    public @interface ServiceNotificationPolicy {};
+    public @interface ServiceNotificationPolicy {}
 
     /**
      * If the Notification associated with starting a foreground service has been
@@ -1754,10 +1754,6 @@
     private Icon mSmallIcon;
     @UnsupportedAppUsage
     private Icon mLargeIcon;
-    private Icon mAppIcon;
-
-    /** Cache for whether the notification was posted by a headless system app. */
-    private Boolean mBelongsToHeadlessSystemApp = null;
 
     @UnsupportedAppUsage
     private String mChannelId;
@@ -3247,86 +3243,6 @@
     }
 
     /**
-     * Whether this notification was posted by a headless system app.
-     *
-     * If we don't have enough information to figure this out, this will return false. Therefore,
-     * false negatives are possible, but false positives should not be.
-     *
-     * @hide
-     */
-    public boolean belongsToHeadlessSystemApp(Context context) {
-        Trace.beginSection("Notification#belongsToHeadlessSystemApp");
-
-        try {
-            if (mBelongsToHeadlessSystemApp != null) {
-                return mBelongsToHeadlessSystemApp;
-            }
-
-            if (context == null) {
-                // Without a valid context, we don't know exactly. Let's assume it doesn't belong to
-                // a system app, but not cache the value.
-                return false;
-            }
-
-            ApplicationInfo info = getApplicationInfo(context);
-            if (info != null) {
-                if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
-                    // It's not a system app at all.
-                    mBelongsToHeadlessSystemApp = false;
-                } else {
-                    // If there's no launch intent, it's probably a headless app.
-                    final PackageManager pm = context.getPackageManager();
-                    mBelongsToHeadlessSystemApp = pm.getLaunchIntentForPackage(info.packageName)
-                            == null;
-                }
-            } else {
-                // If for some reason we don't have the app info, we don't know; best assume it's
-                // not a system app.
-                return false;
-            }
-            return mBelongsToHeadlessSystemApp;
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    /**
-     * Get the resource ID of the app icon from application info.
-     * @hide
-     */
-    public int getHeaderAppIconRes(Context context) {
-        ApplicationInfo info = getApplicationInfo(context);
-        if (info != null) {
-            return info.icon;
-        }
-        return 0;
-    }
-
-    /**
-     * Load the app icon drawable from the package manager. This could result in a binder call.
-     * @hide
-     */
-    public Drawable loadHeaderAppIcon(Context context) {
-        Trace.beginSection("Notification#loadHeaderAppIcon");
-
-        try {
-            if (context == null) {
-                Log.e(TAG, "Cannot load the app icon drawable with a null context");
-                return null;
-            }
-            final PackageManager pm = context.getPackageManager();
-            ApplicationInfo info = getApplicationInfo(context);
-            if (info == null) {
-                Log.e(TAG, "Cannot load the app icon drawable: no application info");
-                return null;
-            }
-            return pm.getApplicationIcon(info);
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    /**
      * Fetch the application info from the notification, or the context if that isn't available.
      */
     private ApplicationInfo getApplicationInfo(Context context) {
@@ -4361,55 +4277,6 @@
     }
 
     /**
-     * The colored app icon that can replace the small icon in the notification starting in V.
-     *
-     * Before using this value, you should first check whether it's actually being used by the
-     * notification by calling {@link Notification#shouldUseAppIcon()}.
-     *
-     * @hide
-     */
-    public Icon getAppIcon() {
-        if (mAppIcon != null) {
-            return mAppIcon;
-        }
-        // If the app icon hasn't been loaded yet, check if we can load it without a context.
-        if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
-            final ApplicationInfo info = extras.getParcelable(
-                    EXTRA_BUILDER_APPLICATION_INFO,
-                    ApplicationInfo.class);
-            if (info != null) {
-                int appIconRes = info.icon;
-                if (appIconRes == 0) {
-                    Log.w(TAG, "Failed to get the app icon: no icon in application info");
-                    return null;
-                }
-                mAppIcon = Icon.createWithResource(info.packageName, appIconRes);
-                return mAppIcon;
-            } else {
-                Log.e(TAG, "Failed to get the app icon: "
-                        + "there's an EXTRA_BUILDER_APPLICATION_INFO in extras but it's null");
-            }
-        } else {
-            Log.w(TAG, "Failed to get the app icon: no application info in extras");
-        }
-        return null;
-    }
-
-    /**
-     * Whether the notification is using the app icon instead of the small icon.
-     * @hide
-     */
-    public boolean shouldUseAppIcon() {
-        if (Flags.notificationsUseAppIconInRow()) {
-            if (belongsToHeadlessSystemApp(/* context = */ null)) {
-                return false;
-            }
-            return getAppIcon() != null;
-        }
-        return false;
-    }
-
-    /**
      * The large icon shown in this notification's content view.
      * @see Builder#getLargeIcon()
      * @see Builder#setLargeIcon(Icon)
@@ -4566,19 +4433,6 @@
         private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
                 SystemProperties.getBoolean("notifications.only_title", true);
 
-        /**
-         * The lightness difference that has to be added to the primary text color to obtain the
-         * secondary text color when the background is light.
-         */
-        private static final int LIGHTNESS_TEXT_DIFFERENCE_LIGHT = 20;
-
-        /**
-         * The lightness difference that has to be added to the primary text color to obtain the
-         * secondary text color when the background is dark.
-         * A bit less then the above value, since it looks better on dark backgrounds.
-         */
-        private static final int LIGHTNESS_TEXT_DIFFERENCE_DARK = -10;
-
         private Context mContext;
         private Notification mN;
         private Bundle mUserExtras = new Bundle();
@@ -6451,36 +6305,12 @@
         }
 
         private void bindSmallIcon(RemoteViews contentView, StandardTemplateParams p) {
-            if (Flags.notificationsUseAppIcon()) {
-                // Override small icon with app icon
-                mN.mSmallIcon = Icon.createWithResource(mContext,
-                        mN.getHeaderAppIconRes(mContext));
-            } else if (mN.mSmallIcon == null && mN.icon != 0) {
+            if (mN.mSmallIcon == null && mN.icon != 0) {
                 mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
             }
-
-            boolean usingAppIcon = false;
-            if (Flags.notificationsUseAppIconInRow() && !mN.belongsToHeadlessSystemApp(mContext)) {
-                // Use the app icon in the view
-                int appIconRes = mN.getHeaderAppIconRes(mContext);
-                if (appIconRes != 0) {
-                    mN.mAppIcon = Icon.createWithResource(mContext, appIconRes);
-                    contentView.setImageViewIcon(R.id.icon, mN.mAppIcon);
-                    contentView.setBoolean(R.id.icon, "setShouldShowAppIcon", true);
-                    usingAppIcon = true;
-                } else {
-                    Log.w(TAG, "bindSmallIcon: could not get the app icon");
-                }
-            }
-            if (!usingAppIcon) {
-                contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
-            }
+            contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
             contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
-
-            // Don't change color if we're using the app icon.
-            if (!Flags.notificationsUseAppIcon() && !usingAppIcon) {
-                processSmallIconColor(mN.mSmallIcon, contentView, p);
-            }
+            processSmallIconColor(mN.mSmallIcon, contentView, p);
         }
 
         /**
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 04a9d13..fee071b 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -359,3 +359,11 @@
     description: "Enables coexistence support for Setting MTE policy."
     bug: "376213673"
 }
+
+flag {
+    name: "enable_supervision_service_sync"
+    is_exported: true
+    namespace: "enterprise"
+    description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder."
+    bug: "376213673"
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionException.aidl b/core/java/android/app/appfunctions/AppFunctionException.aidl
new file mode 100644
index 0000000..7d43224
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionException.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.app.appfunctions;
+
+import android.app.appfunctions.AppFunctionException;
+
+parcelable AppFunctionException;
\ No newline at end of file
diff --git a/core/java/android/app/appfunctions/AppFunctionException.java b/core/java/android/app/appfunctions/AppFunctionException.java
new file mode 100644
index 0000000..d33b505
--- /dev/null
+++ b/core/java/android/app/appfunctions/AppFunctionException.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/** Represents an app function related errors. */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public final class AppFunctionException extends Exception implements Parcelable {
+    /**
+     * The caller does not have the permission to execute an app function.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int ERROR_DENIED = 1000;
+
+    /**
+     * The caller supplied invalid arguments to the execution request.
+     *
+     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int ERROR_INVALID_ARGUMENT = 1001;
+
+    /**
+     * The caller tried to execute a disabled app function.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int ERROR_DISABLED = 1002;
+
+    /**
+     * The caller tried to execute a function that does not exist.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int ERROR_FUNCTION_NOT_FOUND = 1003;
+
+    /**
+     * An internal unexpected error coming from the system.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int ERROR_SYSTEM_ERROR = 2000;
+
+    /**
+     * The operation was cancelled. Use this error code to report that a cancellation is done after
+     * receiving a cancellation signal.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int ERROR_CANCELLED = 2001;
+
+    /**
+     * An unknown error occurred while processing the call in the AppFunctionService.
+     *
+     * <p>This error is thrown when the service is connected in the remote application but an
+     * unexpected error is thrown from the bound application.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
+     */
+    public static final int ERROR_APP_UNKNOWN_ERROR = 3000;
+
+    /**
+     * The error category is unknown.
+     *
+     * <p>This is the default value for {@link #getErrorCategory}.
+     */
+    public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+    /**
+     * The error is caused by the app requesting a function execution.
+     *
+     * <p>For example, the caller provided invalid parameters in the execution request e.g. an
+     * invalid function ID.
+     *
+     * <p>Errors in the category fall in the range 1000-1999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+    /**
+     * The error is caused by an issue in the system.
+     *
+     * <p>For example, the AppFunctionService implementation is not found by the system.
+     *
+     * <p>Errors in the category fall in the range 2000-2999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_SYSTEM = 2;
+
+    /**
+     * The error is caused by the app providing the function.
+     *
+     * <p>For example, the app crashed when the system is executing the request.
+     *
+     * <p>Errors in the category fall in the range 3000-3999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_APP = 3;
+
+    private final int mErrorCode;
+    @Nullable private final String mErrorMessage;
+    @NonNull private final Bundle mExtras;
+
+    /**
+     * @param errorCode The error code.
+     * @param errorMessage The error message.
+     */
+    public AppFunctionException(@ErrorCode int errorCode, @Nullable String errorMessage) {
+        this(errorCode, errorMessage, Bundle.EMPTY);
+    }
+
+    /**
+     * @param errorCode The error code.
+     * @param errorMessage The error message.
+     * @param extras The extras associated with this error.
+     */
+    public AppFunctionException(
+            @ErrorCode int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) {
+        mErrorCode = errorCode;
+        mErrorMessage = errorMessage;
+        mExtras = Objects.requireNonNull(extras);
+    }
+
+    private AppFunctionException(@NonNull Parcel in) {
+        mErrorCode = in.readInt();
+        mErrorMessage = in.readString8();
+        mExtras = Objects.requireNonNull(in.readBundle(getClass().getClassLoader()));
+    }
+
+    /** Returns one of the {@code ERROR} constants. */
+    @ErrorCode
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /** Returns the error message. */
+    @Nullable
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    /**
+     * Returns the error category.
+     *
+     * <p>This method categorizes errors based on their underlying cause, allowing developers to
+     * implement targeted error handling and provide more informative error messages to users. It
+     * maps ranges of error codes to specific error categories.
+     *
+     * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to
+     * any error category.
+     *
+     * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
+     * error code ranges.
+     */
+    @ErrorCategory
+    public int getErrorCategory() {
+        if (mErrorCode >= 1000 && mErrorCode < 2000) {
+            return ERROR_CATEGORY_REQUEST_ERROR;
+        }
+        if (mErrorCode >= 2000 && mErrorCode < 3000) {
+            return ERROR_CATEGORY_SYSTEM;
+        }
+        if (mErrorCode >= 3000 && mErrorCode < 4000) {
+            return ERROR_CATEGORY_APP;
+        }
+        return ERROR_CATEGORY_UNKNOWN;
+    }
+
+    /** Returns any extras associated with this error. */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mErrorCode);
+        dest.writeString8(mErrorMessage);
+        dest.writeBundle(mExtras);
+    }
+
+    /**
+     * Error codes.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"ERROR_"},
+            value = {
+                ERROR_DENIED,
+                ERROR_APP_UNKNOWN_ERROR,
+                ERROR_FUNCTION_NOT_FOUND,
+                ERROR_SYSTEM_ERROR,
+                ERROR_INVALID_ARGUMENT,
+                ERROR_DISABLED,
+                ERROR_CANCELLED
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCode {}
+
+    /**
+     * Error categories.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"ERROR_CATEGORY_"},
+            value = {
+                ERROR_CATEGORY_UNKNOWN,
+                ERROR_CATEGORY_REQUEST_ERROR,
+                ERROR_CATEGORY_APP,
+                ERROR_CATEGORY_SYSTEM
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCategory {}
+
+    @NonNull
+    public static final Creator<AppFunctionException> CREATOR =
+            new Creator<>() {
+                @Override
+                public AppFunctionException createFromParcel(Parcel in) {
+                    return new AppFunctionException(in);
+                }
+
+                @Override
+                public AppFunctionException[] newArray(int size) {
+                    return new AppFunctionException[size];
+                }
+            };
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 5ddb590..ed088fe 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -16,7 +16,7 @@
 
 package android.app.appfunctions;
 
-import static android.app.appfunctions.ExecuteAppFunctionResponse.getResultCode;
+import static android.app.appfunctions.AppFunctionException.ERROR_SYSTEM_ERROR;
 import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
 
 import android.Manifest;
@@ -39,7 +39,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 
 /**
  * Provides access to app functions.
@@ -147,16 +146,16 @@
      * @param request the request to execute the app function
      * @param executor the executor to run the callback
      * @param cancellationSignal the cancellation signal to cancel the execution.
-     * @param callback the callback to receive the function execution result.
+     * @param callback the callback to receive the function execution result or error.
      *     <p>If the calling app does not own the app function or does not have {@code
      *     android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
      *     android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
-     *     ExecuteAppFunctionResponse.RESULT_DENIED}.
+     *     AppFunctionException.ERROR_DENIED}.
      *     <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS} but the
      *     function requires {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}, the execution
-     *     result will contain {@code ExecuteAppFunctionResponse.RESULT_DENIED}
+     *     result will contain {@code AppFunctionException.ERROR_DENIED}
      *     <p>If the function requested for execution is disabled, then the execution result will
-     *     contain {@code ExecuteAppFunctionResponse.RESULT_DISABLED}
+     *     contain {@code AppFunctionException.ERROR_DISABLED}
      *     <p>If the cancellation signal is issued, the operation is cancelled and no response is
      *     returned to the caller.
      */
@@ -171,7 +170,9 @@
             @NonNull ExecuteAppFunctionRequest request,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull CancellationSignal cancellationSignal,
-            @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+            @NonNull
+                    OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+                            callback) {
         Objects.requireNonNull(request);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
@@ -186,20 +187,25 @@
                             aidlRequest,
                             new IExecuteAppFunctionCallback.Stub() {
                                 @Override
-                                public void onResult(ExecuteAppFunctionResponse result) {
+                                public void onSuccess(ExecuteAppFunctionResponse result) {
                                     try {
-                                        executor.execute(() -> callback.accept(result));
+                                        executor.execute(() -> callback.onResult(result));
                                     } catch (RuntimeException e) {
                                         // Ideally shouldn't happen since errors are wrapped into
-                                        // the
-                                        // response, but we catch it here for additional safety.
-                                        callback.accept(
-                                                ExecuteAppFunctionResponse.newFailure(
-                                                        getResultCode(e),
-                                                        e.getMessage(),
-                                                        /* extras= */ null));
+                                        // the response, but we catch it here for additional safety.
+                                        executor.execute(
+                                                () ->
+                                                        callback.onError(
+                                                                new AppFunctionException(
+                                                                        ERROR_SYSTEM_ERROR,
+                                                                        e.getMessage())));
                                     }
                                 }
+
+                                @Override
+                                public void onError(AppFunctionException exception) {
+                                    executor.execute(() -> callback.onError(exception));
+                                }
                             });
             if (cancellationTransport != null) {
                 cancellationSignal.setRemote(cancellationTransport);
diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
index 06d95f5..3ddda22 100644
--- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
+++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java
@@ -213,9 +213,7 @@
             setEnabled(original.getEnabled());
         }
 
-        /**
-         * Sets an indicator specifying the function enabled state.
-         */
+        /** Sets an indicator specifying the function enabled state. */
         @NonNull
         public Builder setEnabled(@EnabledState int enabledState) {
             if (enabledState != APP_FUNCTION_STATE_DEFAULT
diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java
index 63d187a..85b6ab2 100644
--- a/core/java/android/app/appfunctions/AppFunctionService.java
+++ b/core/java/android/app/appfunctions/AppFunctionService.java
@@ -17,7 +17,6 @@
 package android.app.appfunctions;
 
 import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
-import static android.app.appfunctions.ExecuteAppFunctionResponse.getResultCode;
 import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 
@@ -32,10 +31,8 @@
 import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
+import android.os.OutcomeReceiver;
 import android.os.RemoteException;
-import android.util.Log;
-
-import java.util.function.Consumer;
 
 /**
  * Abstract base class to provide app functions to the system.
@@ -80,7 +77,9 @@
                 @NonNull ExecuteAppFunctionRequest request,
                 @NonNull String callingPackage,
                 @NonNull CancellationSignal cancellationSignal,
-                @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+                @NonNull
+                        OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+                                callback);
     }
 
     /** @hide */
@@ -105,13 +104,22 @@
                             request,
                             callingPackage,
                             buildCancellationSignal(cancellationCallback),
-                            safeCallback::onResult);
+                            new OutcomeReceiver<>() {
+                                @Override
+                                public void onResult(ExecuteAppFunctionResponse result) {
+                                    safeCallback.onResult(result);
+                                }
+
+                                @Override
+                                public void onError(AppFunctionException exception) {
+                                    safeCallback.onError(exception);
+                                }
+                            });
                 } catch (Exception ex) {
                     // Apps should handle exceptions. But if they don't, report the error on
                     // behalf of them.
-                    safeCallback.onResult(
-                            ExecuteAppFunctionResponse.newFailure(
-                                    getResultCode(ex), ex.getMessage(), /* extras= */ null));
+                    safeCallback.onError(
+                            new AppFunctionException(toErrorCode(ex), ex.getMessage()));
                 }
             }
         };
@@ -164,12 +172,26 @@
      * @param request The function execution request.
      * @param callingPackage The package name of the app that is requesting the execution.
      * @param cancellationSignal A signal to cancel the execution.
-     * @param callback A callback to report back the result.
+     * @param callback A callback to report back the result or error.
      */
     @MainThread
     public abstract void onExecuteFunction(
             @NonNull ExecuteAppFunctionRequest request,
             @NonNull String callingPackage,
             @NonNull CancellationSignal cancellationSignal,
-            @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+            @NonNull
+                    OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+                            callback);
+
+    /**
+     * Returns result codes from throwable.
+     *
+     * @hide
+     */
+    private static @AppFunctionException.ErrorCode int toErrorCode(@NonNull Throwable t) {
+        if (t instanceof IllegalArgumentException) {
+            return AppFunctionException.ERROR_INVALID_ARGUMENT;
+        }
+        return AppFunctionException.ERROR_APP_UNKNOWN_ERROR;
+    }
 }
diff --git a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
index a23f842..1869d22 100644
--- a/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
+++ b/core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java
@@ -38,7 +38,7 @@
     public static final String STATIC_SCHEMA_TYPE = "AppFunctionStaticMetadata";
     public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault";
     public static final String STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS =
-        "restrictCallersWithExecuteAppFunctions";
+            "restrictCallersWithExecuteAppFunctions";
 
     public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions";
     public static final String PROPERTY_FUNCTION_ID = "functionId";
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index 41bb622..1557815 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -111,8 +111,8 @@
      * Returns the function parameters. The key is the parameter name, and the value is the
      * parameter value.
      *
-     * <p>The bundle may have missing parameters. Developers are advised to implement defensive
-     * handling measures.
+     * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to
+     * implement defensive handling measures.
      *
      * @see AppFunctionManager on how to determine the expected parameters.
      */
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index cdf02e6..f703026 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -19,7 +19,6 @@
 import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
 
 import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.appsearch.GenericDocument;
@@ -27,8 +26,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /** The response to an app function execution. */
@@ -45,10 +42,7 @@
                     Bundle extras =
                             Objects.requireNonNull(
                                     parcel.readBundle(Bundle.class.getClassLoader()));
-                    int resultCode = parcel.readInt();
-                    String errorMessage = parcel.readString8();
-                    return new ExecuteAppFunctionResponse(
-                            resultWrapper, extras, resultCode, errorMessage);
+                    return new ExecuteAppFunctionResponse(resultWrapper.getValue(), extras);
                 }
 
                 @Override
@@ -71,113 +65,7 @@
      *
      * <p>See {@link #getResultDocument} for more information on extracting the return value.
      */
-    public static final String PROPERTY_RETURN_VALUE = "returnValue";
-
-    /**
-     * The call was successful.
-     *
-     * <p>This result code does not belong in an error category.
-     */
-    public static final int RESULT_OK = 0;
-
-    /**
-     * The caller does not have the permission to execute an app function.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
-     */
-    public static final int RESULT_DENIED = 1000;
-
-    /**
-     * The caller supplied invalid arguments to the execution request.
-     *
-     * <p>This error may be considered similar to {@link IllegalArgumentException}.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
-     */
-    public static final int RESULT_INVALID_ARGUMENT = 1001;
-
-    /**
-     * The caller tried to execute a disabled app function.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
-     */
-    public static final int RESULT_DISABLED = 1002;
-
-    /**
-     * The caller tried to execute a function that does not exist.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
-     */
-    public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
-
-    /**
-     * An internal unexpected error coming from the system.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
-     */
-    public static final int RESULT_SYSTEM_ERROR = 2000;
-
-    /**
-     * The operation was cancelled. Use this error code to report that a cancellation is done after
-     * receiving a cancellation signal.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
-     */
-    public static final int RESULT_CANCELLED = 2001;
-
-    /**
-     * An unknown error occurred while processing the call in the AppFunctionService.
-     *
-     * <p>This error is thrown when the service is connected in the remote application but an
-     * unexpected error is thrown from the bound application.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
-     */
-    public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
-
-    /**
-     * The error category is unknown.
-     *
-     * <p>This is the default value for {@link #getErrorCategory}.
-     */
-    public static final int ERROR_CATEGORY_UNKNOWN = 0;
-
-    /**
-     * The error is caused by the app requesting a function execution.
-     *
-     * <p>For example, the caller provided invalid parameters in the execution request e.g. an
-     * invalid function ID.
-     *
-     * <p>Errors in the category fall in the range 1000-1999 inclusive.
-     */
-    public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
-
-    /**
-     * The error is caused by an issue in the system.
-     *
-     * <p>For example, the AppFunctionService implementation is not found by the system.
-     *
-     * <p>Errors in the category fall in the range 2000-2999 inclusive.
-     */
-    public static final int ERROR_CATEGORY_SYSTEM = 2;
-
-    /**
-     * The error is caused by the app providing the function.
-     *
-     * <p>For example, the app crashed when the system is executing the request.
-     *
-     * <p>Errors in the category fall in the range 3000-3999 inclusive.
-     */
-    public static final int ERROR_CATEGORY_APP = 3;
-
-    /** The result code of the app function execution. */
-    @ResultCode private final int mResultCode;
-
-    /**
-     * The error message associated with the result, if any. This is {@code null} if the result code
-     * is {@link #RESULT_OK}.
-     */
-    @Nullable private final String mErrorMessage;
+    public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
 
     /**
      * Returns the return value of the executed function.
@@ -192,103 +80,21 @@
     /** Returns the additional metadata data relevant to this function execution response. */
     @NonNull private final Bundle mExtras;
 
-    private ExecuteAppFunctionResponse(
-            @NonNull GenericDocumentWrapper resultDocumentWrapper,
-            @NonNull Bundle extras,
-            @ResultCode int resultCode,
-            @Nullable String errorMessage) {
-        mResultDocumentWrapper = Objects.requireNonNull(resultDocumentWrapper);
-        mExtras = Objects.requireNonNull(extras);
-        mResultCode = resultCode;
-        mErrorMessage = errorMessage;
-    }
-
     /**
-     * Returns result codes from throwable.
-     *
-     * @hide
+     * @param resultDocument The return value of the executed function.
      */
-    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
-    static @ResultCode int getResultCode(@NonNull Throwable t) {
-        if (t instanceof IllegalArgumentException) {
-            return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
-        }
-        return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
+    public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) {
+        this(resultDocument, Bundle.EMPTY);
     }
 
     /**
-     * Returns a successful response.
-     *
      * @param resultDocument The return value of the executed function.
      * @param extras The additional metadata for this function execution response.
      */
-    @NonNull
-    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
-    public static ExecuteAppFunctionResponse newSuccess(
-            @NonNull GenericDocument resultDocument, @Nullable Bundle extras) {
-        Objects.requireNonNull(resultDocument);
-        Bundle actualExtras = getActualExtras(extras);
-        GenericDocumentWrapper resultDocumentWrapper = new GenericDocumentWrapper(resultDocument);
-
-        return new ExecuteAppFunctionResponse(
-                resultDocumentWrapper, actualExtras, RESULT_OK, /* errorMessage= */ null);
-    }
-
-    /**
-     * Returns a failure response.
-     *
-     * @param resultCode The result code of the app function execution.
-     * @param extras The additional metadata for this function execution response.
-     * @param errorMessage The error message associated with the result, if any.
-     */
-    @NonNull
-    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
-    public static ExecuteAppFunctionResponse newFailure(
-            @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) {
-        if (resultCode == RESULT_OK) {
-            throw new IllegalArgumentException("resultCode must not be RESULT_OK");
-        }
-        Bundle actualExtras = getActualExtras(extras);
-        GenericDocumentWrapper emptyWrapper =
-                new GenericDocumentWrapper(new GenericDocument.Builder<>("", "", "").build());
-        return new ExecuteAppFunctionResponse(emptyWrapper, actualExtras, resultCode, errorMessage);
-    }
-
-    private static Bundle getActualExtras(@Nullable Bundle extras) {
-        if (extras == null) {
-            return Bundle.EMPTY;
-        }
-        return extras;
-    }
-
-    /**
-     * Returns the error category of the {@link ExecuteAppFunctionResponse}.
-     *
-     * <p>This method categorizes errors based on their underlying cause, allowing developers to
-     * implement targeted error handling and provide more informative error messages to users. It
-     * maps ranges of result codes to specific error categories.
-     *
-     * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
-     * ensure correct categorization of the failed response.
-     *
-     * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
-     * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
-     *
-     * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
-     * result code ranges.
-     */
-    @ErrorCategory
-    public int getErrorCategory() {
-        if (mResultCode >= 1000 && mResultCode < 2000) {
-            return ERROR_CATEGORY_REQUEST_ERROR;
-        }
-        if (mResultCode >= 2000 && mResultCode < 3000) {
-            return ERROR_CATEGORY_SYSTEM;
-        }
-        if (mResultCode >= 3000 && mResultCode < 4000) {
-            return ERROR_CATEGORY_APP;
-        }
-        return ERROR_CATEGORY_UNKNOWN;
+    public ExecuteAppFunctionResponse(
+            @NonNull GenericDocument resultDocument, @NonNull Bundle extras) {
+        mResultDocumentWrapper = new GenericDocumentWrapper(Objects.requireNonNull(resultDocument));
+        mExtras = Objects.requireNonNull(extras);
     }
 
     /**
@@ -296,9 +102,6 @@
      *
      * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
      *
-     * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
-     * function does not produce a return value.
-     *
      * <p>Sample code for extracting the return value:
      *
      * <pre>
@@ -324,32 +127,6 @@
         return mExtras;
     }
 
-    /**
-     * Returns {@code true} if {@link #getResultCode} equals {@link
-     * ExecuteAppFunctionResponse#RESULT_OK}.
-     */
-    public boolean isSuccess() {
-        return getResultCode() == RESULT_OK;
-    }
-
-    /**
-     * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
-     */
-    @ResultCode
-    public int getResultCode() {
-        return mResultCode;
-    }
-
-    /**
-     * Returns the error message associated with this result.
-     *
-     * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
-     */
-    @Nullable
-    public String getErrorMessage() {
-        return mErrorMessage;
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -359,43 +136,5 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         mResultDocumentWrapper.writeToParcel(dest, flags);
         dest.writeBundle(mExtras);
-        dest.writeInt(mResultCode);
-        dest.writeString8(mErrorMessage);
     }
-
-    /**
-     * Result codes.
-     *
-     * @hide
-     */
-    @IntDef(
-            prefix = {"RESULT_"},
-            value = {
-                RESULT_OK,
-                RESULT_DENIED,
-                RESULT_APP_UNKNOWN_ERROR,
-                RESULT_FUNCTION_NOT_FOUND,
-                RESULT_SYSTEM_ERROR,
-                RESULT_INVALID_ARGUMENT,
-                RESULT_DISABLED,
-                RESULT_CANCELLED
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    /**
-     * Error categories.
-     *
-     * @hide
-     */
-    @IntDef(
-            prefix = {"ERROR_CATEGORY_"},
-            value = {
-                ERROR_CATEGORY_UNKNOWN,
-                ERROR_CATEGORY_REQUEST_ERROR,
-                ERROR_CATEGORY_APP,
-                ERROR_CATEGORY_SYSTEM
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ErrorCategory {}
 }
diff --git a/core/java/android/app/appfunctions/GenericDocumentWrapper.java b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
index b29b64e..541ca74 100644
--- a/core/java/android/app/appfunctions/GenericDocumentWrapper.java
+++ b/core/java/android/app/appfunctions/GenericDocumentWrapper.java
@@ -34,9 +34,9 @@
  * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder
  * directly or Android shared memory if the data is large.
  *
- * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled
- * from the underlying `Parcel` when {@link #getValue()} is called. This optimization
- * allows the system server to pass through the generic document, without unparcel and parcel it.
+ * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled from the
+ * underlying `Parcel` when {@link #getValue()} is called. This optimization allows the system
+ * server to pass through the generic document, without unparcel and parcel it.
  *
  * @hide
  * @see Parcel#writeBlob(byte[])
@@ -45,8 +45,11 @@
     @Nullable
     @GuardedBy("mLock")
     private GenericDocument mGenericDocument;
+
     @GuardedBy("mLock")
-    @Nullable private Parcel mParcel;
+    @Nullable
+    private Parcel mParcel;
+
     private final Object mLock = new Object();
 
     public static final Creator<GenericDocumentWrapper> CREATOR =
diff --git a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
index 5323f9b..69bbc0e 100644
--- a/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
+++ b/core/java/android/app/appfunctions/IExecuteAppFunctionCallback.aidl
@@ -17,8 +17,10 @@
 package android.app.appfunctions;
 
 import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.AppFunctionException;
 
 /** {@hide} */
 oneway interface IExecuteAppFunctionCallback {
-    void onResult(in ExecuteAppFunctionResponse result);
+    void onSuccess(in ExecuteAppFunctionResponse result);
+    void onError(in AppFunctionException exception);
 }
diff --git a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
index 0018244..2426daf 100644
--- a/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
+++ b/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
@@ -17,17 +17,16 @@
 package android.app.appfunctions;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.os.RemoteException;
 import android.util.Log;
 
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Consumer;
 
 /**
  * A wrapper of IExecuteAppFunctionCallback which swallows the {@link RemoteException}. This
- * callback is intended for one-time use only. Subsequent calls to onResult() will be ignored.
+ * callback is intended for one-time use only. Subsequent calls to onResult() or onError() will be
+ * ignored.
  *
  * @hide
  */
@@ -38,44 +37,41 @@
 
     @NonNull private final IExecuteAppFunctionCallback mCallback;
 
-    @Nullable private final Consumer<ExecuteAppFunctionResponse> mOnDispatchCallback;
-
     public SafeOneTimeExecuteAppFunctionCallback(@NonNull IExecuteAppFunctionCallback callback) {
-        this(callback, /* onDispatchCallback= */ null);
-    }
-
-    /**
-     * @param callback The callback to wrap.
-     * @param onDispatchCallback An optional callback invoked after the wrapped callback has been
-     *     dispatched with a result. This callback receives the result that has been dispatched.
-     */
-    public SafeOneTimeExecuteAppFunctionCallback(
-            @NonNull IExecuteAppFunctionCallback callback,
-            @Nullable Consumer<ExecuteAppFunctionResponse> onDispatchCallback) {
         mCallback = Objects.requireNonNull(callback);
-        mOnDispatchCallback = onDispatchCallback;
     }
 
     /** Invoke wrapped callback with the result. */
     public void onResult(@NonNull ExecuteAppFunctionResponse result) {
         if (!mOnResultCalled.compareAndSet(false, true)) {
-            Log.w(TAG, "Ignore subsequent calls to onResult()");
+            Log.w(TAG, "Ignore subsequent calls to onResult/onError()");
             return;
         }
         try {
-            mCallback.onResult(result);
+            mCallback.onSuccess(result);
         } catch (RemoteException ex) {
             // Failed to notify the other end. Ignore.
             Log.w(TAG, "Failed to invoke the callback", ex);
         }
-        if (mOnDispatchCallback != null) {
-            mOnDispatchCallback.accept(result);
+    }
+
+    /** Invoke wrapped callback with the error. */
+    public void onError(@NonNull AppFunctionException error) {
+        if (!mOnResultCalled.compareAndSet(false, true)) {
+            Log.w(TAG, "Ignore subsequent calls to onResult/onError()");
+            return;
+        }
+        try {
+            mCallback.onError(error);
+        } catch (RemoteException ex) {
+            // Failed to notify the other end. Ignore.
+            Log.w(TAG, "Failed to invoke the callback", ex);
         }
     }
 
     /**
-     * Disables this callback. Subsequent calls to {@link #onResult(ExecuteAppFunctionResponse)}
-     * will be ignored.
+     * Disables this callback. Subsequent calls to {@link #onResult(ExecuteAppFunctionResponse)} or
+     * {@link #onError(AppFunctionException)} will be ignored.
      */
     public void disable() {
         mOnResultCalled.set(true);
diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java
index a488689..43a46ba 100644
--- a/core/java/android/app/assist/AssistContent.java
+++ b/core/java/android/app/assist/AssistContent.java
@@ -1,5 +1,6 @@
 package android.app.assist;
 
+import android.annotation.FlaggedApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ClipData;
 import android.content.Intent;
@@ -15,6 +16,20 @@
  * {@link android.app.Activity#onProvideAssistContent Activity.onProvideAssistContent}.
  */
 public class AssistContent implements Parcelable {
+    /**
+     * Extra for a {@link Bundle} that provides contextual AppFunction's information about the
+     * content currently being viewed in the application.
+     * <p>
+     * This extra can be optionally supplied in the {@link AssistContent#getExtras()} bundle.
+     * <p>
+     * The schema of the {@link Bundle} in this extra is defined in the AppFunction SDK.
+     *
+     * @see android.app.appfunctions.AppFunctionManager
+     */
+    @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+    public static final String EXTRA_APP_FUNCTION_DATA =
+            "android.app.assist.extra.APP_FUNCTION_DATA";
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private boolean mIsAppProvidedIntent = false;
     private boolean mIsAppProvidedWebUri = false;
diff --git a/core/java/android/app/multitasking.aconfig b/core/java/android/app/multitasking.aconfig
index 9a64519..c8455c1 100644
--- a/core/java/android/app/multitasking.aconfig
+++ b/core/java/android/app/multitasking.aconfig
@@ -8,3 +8,11 @@
     description: "Enables PiP UI state callback on entering"
     bug: "303718131"
 }
+
+flag {
+    name: "enable_tv_implicit_enter_pip_restriction"
+    is_exported: true
+    namespace: "tv_system_ui"
+    description: "Enables restrictions to PiP entry on TV for setAutoEnterEnabled and lifecycle methods"
+    bug: "283115999"
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 0fc4291..ee93870 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -8,8 +8,7 @@
 flag {
   name: "notifications_redesign_app_icons"
   namespace: "systemui"
-  description: "Notifications Redesign: Use app icons in notification rows (not to be confused with"
-    " notifications_use_app_icons, notifications_use_app_icon_in_row which are just experiments)."
+  description: "Notifications Redesign: Use app icons in notification rows"
   bug: "371174789"
 }
 
@@ -110,31 +109,6 @@
   }
 }
 
-# vvv Prototypes for using app icons in notifications vvv
-
-flag {
-  name: "notifications_use_app_icon"
-  namespace: "systemui"
-  description: "Experiment to replace the small icon in a notification with the app icon. This includes the status bar, AOD, shelf and notification row itself."
-  bug: "335211019"
-}
-
-flag {
-  name: "notifications_use_app_icon_in_row"
-  namespace: "systemui"
-  description: "Experiment to replace the small icon in a notification row with the app icon."
-  bug: "335211019"
-}
-
-flag {
-  name: "notifications_use_monochrome_app_icon"
-  namespace: "systemui"
-  description: "Experiment to replace the notification icon in the status bar and shelf with the monochrome app icon, if available."
-  bug: "335211019"
-}
-
-# ^^^ Prototypes for using app icons in notifications ^^^
-
 flag {
   name: "notification_expansion_optional"
   namespace: "systemui"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 9af2016..c47fe23 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -153,3 +153,10 @@
     bug: "371173368"
     is_exported: true
 }
+
+flag {
+    name: "vdm_settings"
+    namespace: "virtual_devices"
+    description: "Show virtual devices in Settings"
+    bug: "338974320"
+}
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index ff0bb25..cc57dc0 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -398,6 +398,7 @@
          * Retrieve the raw Intent contained in this Item.
          */
         public Intent getIntent() {
+            Intent.maybeMarkAsMissingCreatorToken(mIntent);
             return mIntent;
         }
 
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 186f7b3..6086f24 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -6802,6 +6802,12 @@
     public static final String MEDIA_QUALITY_SERVICE = "media_quality";
 
     /**
+     * Service to perform operations needed for dynamic instrumentation.
+     * @hide
+     */
+    public static final String DYNAMIC_INSTRUMENTATION_SERVICE = "dynamic_instrumentation";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 6fa5a9b..c054b79 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -87,6 +87,7 @@
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.expresslog.Counter;
 
@@ -108,6 +109,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.TimeZone;
+import java.util.function.Consumer;
 
 /**
  * An intent is an abstract description of an operation to be performed.  It
@@ -892,6 +894,20 @@
     public static void maybeMarkAsMissingCreatorToken(Object object) {
         if (object instanceof Intent intent) {
             maybeMarkAsMissingCreatorTokenInternal(intent);
+        } else if (object instanceof Parcelable[] parcelables) {
+            for (Parcelable p : parcelables) {
+                if (p instanceof Intent intent) {
+                    maybeMarkAsMissingCreatorTokenInternal(intent);
+                }
+            }
+        } else if (object instanceof ArrayList parcelables) {
+            int N = parcelables.size();
+            for (int i = 0; i < N; i++) {
+                Object p = parcelables.get(i);
+                if (p instanceof Intent intent) {
+                    maybeMarkAsMissingCreatorTokenInternal(intent);
+                }
+            }
         }
     }
 
@@ -12204,7 +12220,70 @@
         // Stores a creator token for an intent embedded as an extra intent in a top level intent,
         private IBinder mCreatorToken;
         // Stores all extra keys whose values are intents for a top level intent.
-        private ArraySet<String> mExtraIntentKeys;
+        private ArraySet<NestedIntentKey> mNestedIntentKeys;
+    }
+
+    /**
+     * @hide
+     */
+    public static class NestedIntentKey {
+        /** @hide */
+        @IntDef(flag = true, prefix = {"NESTED_INTENT_KEY_TYPE"}, value = {
+                NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL,
+                NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY,
+                NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST,
+                NESTED_INTENT_KEY_TYPE_CLIP_DATA,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        private @interface NestedIntentKeyType {
+        }
+
+        /**
+         * This flag indicates the key is for an extra parcel in mExtras.
+         */
+        private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL = 1 << 0;
+
+        /**
+         * This flag indicates the key is for an extra parcel array in mExtras and the index is the
+         * index of that array.
+         */
+        private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY = 1 << 1;
+
+        /**
+         * This flag indicates the key is for an extra parcel list in mExtras and the index is the
+         * index of that list.
+         */
+        private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST = 1 << 2;
+
+        /**
+         * This flag indicates the key is for an extra parcel in mClipData.mItems.
+         */
+        private static final int NESTED_INTENT_KEY_TYPE_CLIP_DATA = 1 << 3;
+
+        // type can be a short or even byte. But then probably cannot use @IntDef?? Also not sure
+        // if it is necessary.
+        private final @NestedIntentKeyType int mType;
+        private final String mKey;
+        private final int mIndex;
+
+        private NestedIntentKey(@NestedIntentKeyType int type, String key, int index) {
+            this.mType = type;
+            this.mKey = key;
+            this.mIndex = index;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            NestedIntentKey that = (NestedIntentKey) o;
+            return mType == that.mType && mIndex == that.mIndex && Objects.equals(mKey, that.mKey);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mType, mKey, mIndex);
+        }
     }
 
     private @Nullable CreatorTokenInfo mCreatorTokenInfo;
@@ -12227,8 +12306,9 @@
     }
 
     /** @hide */
-    public Set<String> getExtraIntentKeys() {
-        return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys;
+    @VisibleForTesting
+    public Set<NestedIntentKey> getExtraIntentKeys() {
+        return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mNestedIntentKeys;
     }
 
     /** @hide */
@@ -12246,45 +12326,168 @@
      * @hide
      */
     public void collectExtraIntentKeys() {
-        if (!preventIntentRedirect()) return;
+        if (preventIntentRedirect()) {
+            collectNestedIntentKeysRecur(new ArraySet<>());
+        }
+    }
 
-        if (mExtras != null && !mExtras.isEmpty()) {
+    private void collectNestedIntentKeysRecur(Set<Intent> visited) {
+        if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
             for (String key : mExtras.keySet()) {
-                if (mExtras.get(key) instanceof Intent) {
-                    if (mCreatorTokenInfo == null) {
-                        mCreatorTokenInfo = new CreatorTokenInfo();
-                    }
-                    if (mCreatorTokenInfo.mExtraIntentKeys == null) {
-                        mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>();
-                    }
-                    mCreatorTokenInfo.mExtraIntentKeys.add(key);
+                Object value = mExtras.get(key);
+
+                if (value instanceof Intent intent && !visited.contains(intent)) {
+                    handleNestedIntent(intent, visited, new NestedIntentKey(
+                            NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
+                } else if (value instanceof Parcelable[] parcelables) {
+                    handleParcelableArray(parcelables, key, visited);
+                } else if (value instanceof ArrayList<?> parcelables) {
+                    handleParcelableList(parcelables, key, visited);
+                }
+            }
+        }
+
+        if (mClipData != null) {
+            for (int i = 0; i < mClipData.getItemCount(); i++) {
+                Intent intent = mClipData.getItemAt(i).mIntent;
+                if (intent != null && !visited.contains(intent)) {
+                    handleNestedIntent(intent, visited, new NestedIntentKey(
+                            NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i));
                 }
             }
         }
     }
 
+    private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key) {
+        visited.add(intent);
+        if (mCreatorTokenInfo == null) {
+            mCreatorTokenInfo = new CreatorTokenInfo();
+        }
+        if (mCreatorTokenInfo.mNestedIntentKeys == null) {
+            mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>();
+        }
+        mCreatorTokenInfo.mNestedIntentKeys.add(key);
+        intent.collectNestedIntentKeysRecur(visited);
+    }
+
+    private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited) {
+        for (int i = 0; i < parcelables.length; i++) {
+            if (parcelables[i] instanceof Intent intent && !visited.contains(intent)) {
+                handleNestedIntent(intent, visited, new NestedIntentKey(
+                        NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i));
+            }
+        }
+    }
+
+    private void handleParcelableList(ArrayList<?> parcelables, String key, Set<Intent> visited) {
+        for (int i = 0; i < parcelables.size(); i++) {
+            if (parcelables.get(i) instanceof Intent intent && !visited.contains(intent)) {
+                handleNestedIntent(intent, visited, new NestedIntentKey(
+                        NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i));
+            }
+        }
+    }
+
+    private static final Consumer<Intent> CHECK_CREATOR_TOKEN_ACTION = intent -> {
+        intent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT;
+        if (intent.mExtras != null) {
+            intent.mExtras.enableTokenVerification();
+        }
+    };
+
     /** @hide */
     public void checkCreatorToken() {
-        if (mExtras == null) return;
-        if (mCreatorTokenInfo != null && mCreatorTokenInfo.mExtraIntentKeys != null) {
-            for (String key : mCreatorTokenInfo.mExtraIntentKeys) {
-                try {
-                    Intent extraIntent = mExtras.getParcelable(key, Intent.class);
-                    if (extraIntent == null) {
-                        Log.w(TAG, "The key {" + key
-                                + "} does not correspond to an intent in the bundle.");
-                        continue;
-                    }
-                    extraIntent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT;
-                } catch (Exception e) {
-                    Log.e(TAG, "Failed to validate creator token. key: " + key + ".", e);
+        forEachNestedCreatorToken(CHECK_CREATOR_TOKEN_ACTION);
+
+        if (mExtras != null) {
+            // mark the bundle as intent extras after calls to getParcelable.
+            // otherwise, the logic to mark missing token would run before
+            // mark trusted creator token present.
+            mExtras.enableTokenVerification();
+        }
+    }
+
+    /** @hide */
+    public void forEachNestedCreatorToken(Consumer<? super Intent> action) {
+        if (mExtras == null && mClipData == null) return;
+
+        if (mCreatorTokenInfo != null && mCreatorTokenInfo.mNestedIntentKeys != null) {
+            int N = mCreatorTokenInfo.mNestedIntentKeys.size();
+            for (int i = 0; i < N; i++) {
+                NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
+                Intent extraIntent = extractIntentFromKey(key);
+
+                if (extraIntent != null) {
+                    action.accept(extraIntent);
+                    extraIntent.forEachNestedCreatorToken(action);
+                } else {
+                    Log.w(TAG, getLogMessageForKey(key));
                 }
             }
         }
-        // mark the bundle as intent extras after calls to getParcelable.
-        // otherwise, the logic to mark missing token would run before
-        // mark trusted creator token present.
-        mExtras.setIsIntentExtra();
+    }
+
+    private Intent extractIntentFromKey(NestedIntentKey key) {
+        switch (key.mType) {
+            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL:
+                return mExtras == null ? null : mExtras.getParcelable(key.mKey, Intent.class);
+            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY:
+                if (mExtras == null) return null;
+                Intent[] extraIntents = mExtras.getParcelableArray(key.mKey, Intent.class);
+                if (extraIntents != null && key.mIndex < extraIntents.length) {
+                    return extraIntents[key.mIndex];
+                }
+                break;
+            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST:
+                if (mExtras == null) return null;
+                ArrayList<Intent> extraIntentsList = mExtras.getParcelableArrayList(key.mKey,
+                        Intent.class);
+                if (extraIntentsList != null && key.mIndex < extraIntentsList.size()) {
+                    return extraIntentsList.get(key.mIndex);
+                }
+                break;
+            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA:
+                if (mClipData == null) return null;
+                if (key.mIndex < mClipData.getItemCount()) {
+                    ClipData.Item item = mClipData.getItemAt(key.mIndex);
+                    if (item != null) {
+                        return item.mIntent;
+                    }
+                }
+                break;
+        }
+        return null;
+    }
+
+    private String getLogMessageForKey(NestedIntentKey key) {
+        switch (key.mType) {
+            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL:
+                return "The key {" + key + "} does not correspond to an intent in the bundle.";
+            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY:
+                if (mExtras.getParcelableArray(key.mKey, Intent.class) == null) {
+                    return "The key {" + key
+                            + "} does not correspond to a Parcelable[] in the bundle.";
+                } else {
+                    return "Parcelable[" + key.mIndex + "] for key {" + key + "} is not an intent.";
+                }
+            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST:
+                if (mExtras.getParcelableArrayList(key.mKey, Intent.class) == null) {
+                    return "The key {" + key
+                            + "} does not correspond to an ArrayList<Parcelable> in the bundle.";
+                } else {
+                    return "List.get(" + key.mIndex + ") for key {" + key + "} is not an intent.";
+                }
+            case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA:
+                if (key.mIndex >= mClipData.getItemCount()) {
+                    return "Index out of range for clipData items. index: " + key.mIndex
+                            + ". item counts: " + mClipData.getItemCount();
+                } else {
+                    return "clipData items at index [" + key.mIndex
+                            + "] is null or does not contain an intent.";
+                }
+            default:
+                return "Unknown key type: " + key.mType;
+        }
     }
 
     /**
@@ -12357,7 +12560,19 @@
             } else {
                 out.writeInt(1);
                 out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken);
-                out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys);
+
+                if (mCreatorTokenInfo.mNestedIntentKeys != null) {
+                    final int N = mCreatorTokenInfo.mNestedIntentKeys.size();
+                    out.writeInt(N);
+                    for (int i = 0; i < N; i++) {
+                        NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
+                        out.writeInt(key.mType);
+                        out.writeString8(key.mKey);
+                        out.writeInt(key.mIndex);
+                    }
+                } else {
+                    out.writeInt(0);
+                }
             }
         }
     }
@@ -12422,7 +12637,18 @@
             if (in.readInt() != 0) {
                 mCreatorTokenInfo = new CreatorTokenInfo();
                 mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
-                mCreatorTokenInfo.mExtraIntentKeys = (ArraySet<String>) in.readArraySet(null);
+
+                N = in.readInt();
+                if (N > 0) {
+                    mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(N);
+                    for (int i = 0; i < N; i++) {
+                        int type = in.readInt();
+                        String key = in.readString8();
+                        int index = in.readInt();
+                        mCreatorTokenInfo.mNestedIntentKeys.append(
+                                new NestedIntentKey(type, key, index));
+                    }
+                }
             }
         }
     }
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index fff980f..c424229 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -353,7 +353,7 @@
 flag {
     name: "cloud_compilation_pm"
     is_exported: true
-    namespace: "package_manager_service"
+    namespace: "art_mainline"
     description: "Feature flag to enable the Cloud Compilation support on the package manager side."
     bug: "377474232"
     is_fixed_read_only: true
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 26ecbd1..f23c193 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -95,3 +95,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "layout_readwrite_flags"
+    is_exported: true
+    namespace: "resource_manager"
+    description: "Feature flag for allowing read/write flags in layout files"
+    bug: "377974898"
+    # This flag is used to control aapt2 behavior.
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index ef59e0a..93ef5c3 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -45,7 +45,7 @@
  * </p>
  */
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("CursorWindow_host")
+@RavenwoodRedirectionClass("CursorWindow_ravenwood")
 public class CursorWindow extends SQLiteClosable implements Parcelable {
     private static final String STATS_TAG = "CursorWindowStats";
 
diff --git a/ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java b/core/java/android/database/CursorWindow_ravenwood.java
similarity index 89%
rename from ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java
rename to core/java/android/database/CursorWindow_ravenwood.java
index e21a9cd..990ec5e 100644
--- a/ravenwood/runtime-helper-src/framework/android/database/CursorWindow_host.java
+++ b/core/java/android/database/CursorWindow_ravenwood.java
@@ -17,6 +17,7 @@
 
 import android.database.sqlite.SQLiteException;
 import android.os.Parcel;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.util.Base64;
 
 import java.text.DecimalFormat;
@@ -26,9 +27,10 @@
 import java.util.HashMap;
 import java.util.List;
 
-public class CursorWindow_host {
+@RavenwoodKeepWholeClass
+class CursorWindow_ravenwood {
 
-    private static final HashMap<Long, CursorWindow_host> sInstances = new HashMap<>();
+    private static final HashMap<Long, CursorWindow_ravenwood> sInstances = new HashMap<>();
     private static long sNextId = 1;
 
     private String mName;
@@ -41,7 +43,7 @@
     private final List<Row> mRows = new ArrayList<>();
 
     public static long nativeCreate(String name, int cursorWindowSize) {
-        CursorWindow_host instance = new CursorWindow_host();
+        CursorWindow_ravenwood instance = new CursorWindow_ravenwood();
         instance.mName = name;
         long instanceId = sNextId++;
         sInstances.put(instanceId, instance);
@@ -66,7 +68,7 @@
     }
 
     public static boolean nativeAllocRow(long windowPtr) {
-        CursorWindow_host instance = sInstances.get(windowPtr);
+        CursorWindow_ravenwood instance = sInstances.get(windowPtr);
         Row row = new Row();
         row.mFields = new String[instance.mColumnNum];
         row.mTypes = new int[instance.mColumnNum];
@@ -76,7 +78,7 @@
     }
 
     private static boolean put(long windowPtr, String value, int type, int row, int column) {
-        CursorWindow_host instance = sInstances.get(windowPtr);
+        CursorWindow_ravenwood instance = sInstances.get(windowPtr);
         if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
             return false;
         }
@@ -87,7 +89,7 @@
     }
 
     public static int nativeGetType(long windowPtr, int row, int column) {
-        CursorWindow_host instance = sInstances.get(windowPtr);
+        CursorWindow_ravenwood instance = sInstances.get(windowPtr);
         if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
             return Cursor.FIELD_TYPE_NULL;
         }
@@ -101,7 +103,7 @@
     }
 
     public static String nativeGetString(long windowPtr, int row, int column) {
-        CursorWindow_host instance = sInstances.get(windowPtr);
+        CursorWindow_ravenwood instance = sInstances.get(windowPtr);
         if (row >= instance.mRows.size() || column >= instance.mColumnNum) {
             return null;
         }
@@ -164,7 +166,7 @@
     }
 
     public static void nativeWriteToParcel(long windowPtr, Parcel parcel) {
-        CursorWindow_host window = sInstances.get(windowPtr);
+        CursorWindow_ravenwood window = sInstances.get(windowPtr);
         parcel.writeString(window.mName);
         parcel.writeInt(window.mColumnNum);
         parcel.writeInt(window.mRows.size());
@@ -176,7 +178,7 @@
 
     public static long nativeCreateFromParcel(Parcel parcel) {
         long windowPtr = nativeCreate(null, 0);
-        CursorWindow_host window = sInstances.get(windowPtr);
+        CursorWindow_ravenwood window = sInstances.get(windowPtr);
         window.mName = parcel.readString();
         window.mColumnNum = parcel.readInt();
         int rowCount = parcel.readInt();
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 86bbd4a..987e2ad 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -4289,6 +4289,39 @@
      */
     public static final int SYNC_FRAME_NUMBER_UNKNOWN = -2;
 
+    //
+    // Enumeration values for CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR
+    //
+
+    /**
+     * <p>The camera can't accurately assess the scene's lighting to determine if a Night Mode
+     * Camera Extension capture would improve the photo. This can happen when the current
+     * camera configuration doesn't support night mode indicator detection, such as when
+     * the auto exposure mode is ON_AUTO_FLASH, ON_ALWAYS_FLASH, ON_AUTO_FLASH_REDEYE, or
+     * ON_EXTERNAL_FLASH.</p>
+     * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR
+     */
+    @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR)
+    public static final int EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN = 0;
+
+    /**
+     * <p>The camera has detected lighting conditions that are sufficiently bright. Night
+     * Mode Camera Extensions is available but may not be able to optimize the camera
+     * settings to take a higher quality photo.</p>
+     * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR
+     */
+    @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR)
+    public static final int EXTENSION_NIGHT_MODE_INDICATOR_OFF = 1;
+
+    /**
+     * <p>The camera has detected low-light conditions. It is recommended to use Night Mode
+     * Camera Extension to optimize the camera settings to take a high-quality photo in
+     * the dark.</p>
+     * @see CaptureResult#EXTENSION_NIGHT_MODE_INDICATOR
+     */
+    @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR)
+    public static final int EXTENSION_NIGHT_MODE_INDICATOR_ON = 2;
+
     /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
      * End generated code
      *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index ae72ca4..bf3a072 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -6016,6 +6016,38 @@
     public static final Key<Integer> EXTENSION_STRENGTH =
             new Key<Integer>("android.extension.strength", int.class);
 
+    /**
+     * <p>Indicates when to activate Night Mode Camera Extension for high-quality
+     * still captures in low-light conditions.</p>
+     * <p>Provides awareness to the application when the current scene can benefit from using a
+     * Night Mode Camera Extension to take a high-quality photo.</p>
+     * <p>Support for this capture result can be queried via
+     * {@link android.hardware.camera2.CameraCharacteristics#getAvailableCaptureResultKeys }.</p>
+     * <p>If the device supports this capability then it will also support
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT}
+     * and will be available in both
+     * {@link android.hardware.camera2.CameraCaptureSession sessions} and
+     * {@link android.hardware.camera2.CameraExtensionSession sessions}.</p>
+     * <p>The value will be {@code UNKNOWN} in the following auto exposure modes: ON_AUTO_FLASH,
+     * ON_ALWAYS_FLASH, ON_AUTO_FLASH_REDEYE, or ON_EXTERNAL_FLASH.</p>
+     * <p><b>Possible values:</b></p>
+     * <ul>
+     *   <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN UNKNOWN}</li>
+     *   <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_OFF OFF}</li>
+     *   <li>{@link #EXTENSION_NIGHT_MODE_INDICATOR_ON ON}</li>
+     * </ul>
+     *
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     * @see #EXTENSION_NIGHT_MODE_INDICATOR_UNKNOWN
+     * @see #EXTENSION_NIGHT_MODE_INDICATOR_OFF
+     * @see #EXTENSION_NIGHT_MODE_INDICATOR_ON
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_NIGHT_MODE_INDICATOR)
+    public static final Key<Integer> EXTENSION_NIGHT_MODE_INDICATOR =
+            new Key<Integer>("android.extension.nightModeIndicator", int.class);
+
     /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
      * End generated code
      *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index e22c263..1cc0856 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -437,7 +437,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public synchronized void writeToParcel(Parcel dest, int flags) {
         nativeWriteToParcel(dest, mMetadataPtr);
     }
 
@@ -479,7 +479,7 @@
         return getBase(key);
     }
 
-    public void readFromParcel(Parcel in) {
+    public synchronized void readFromParcel(Parcel in) {
         nativeReadFromParcel(in, mMetadataPtr);
         updateNativeAllocation();
     }
@@ -592,28 +592,33 @@
     }
 
     private <T> T getBase(Key<T> key) {
-        int tag;
-        if (key.hasTag()) {
-            tag = key.getTag();
-        } else {
-            tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName());
-            key.cacheTag(tag);
-        }
-        byte[] values = readValues(tag);
-        if (values == null) {
-            // If the key returns null, use the fallback key if exists.
-            // This is to support old key names for the newly published keys.
-            if (key.mFallbackName == null) {
-                return null;
+        int tag, nativeType;
+        byte[] values = null;
+        synchronized (this) {
+            if (key.hasTag()) {
+                tag = key.getTag();
+            } else {
+                tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.getName());
+                key.cacheTag(tag);
             }
-            tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName);
             values = readValues(tag);
             if (values == null) {
-                return null;
+                // If the key returns null, use the fallback key if exists.
+                // This is to support old key names for the newly published keys.
+                if (key.mFallbackName == null) {
+                    return null;
+                }
+                tag = nativeGetTagFromKeyLocal(mMetadataPtr, key.mFallbackName);
+                values = readValues(tag);
+                if (values == null) {
+                    return null;
+                }
             }
-        }
 
-        int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
+            nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
+        }
+        // This block of code doesn't need to be synchronized since we aren't writing or reading
+        // from the metadata buffer for this instance of CameraMetadataNative.
         Marshaler<T> marshaler = getMarshalerForKey(key, nativeType);
         ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder());
         return marshaler.unmarshal(buffer);
@@ -1945,8 +1950,12 @@
         setBase(key.getNativeKey(), value);
     }
 
-    private <T> void setBase(Key<T> key, T value) {
-        int tag;
+    // The whole method needs to be synchronized since we're making
+    // multiple calls to the native layer. From one call to the other (within setBase)
+    // we expect the metadata's properties such as vendor id etc to
+    // stay the same and as a result the whole method should be synchronized for safety.
+    private synchronized <T> void setBase(Key<T> key, T value) {
+        int tag, nativeType;
         if (key.hasTag()) {
             tag = key.getTag();
         } else {
@@ -1959,7 +1968,7 @@
             return;
         } // else update the entry to a new value
 
-        int nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
+        nativeType = nativeGetTypeFromTagLocal(mMetadataPtr, tag);
         Marshaler<T> marshaler = getMarshalerForKey(key, nativeType);
         int size = marshaler.calculateMarshalSize(value);
 
@@ -2162,7 +2171,7 @@
         return true;
     }
 
-    private void updateNativeAllocation() {
+    private synchronized void updateNativeAllocation() {
         long currentBufferSize = nativeGetBufferSize(mMetadataPtr);
 
         if (currentBufferSize != mBufferSize) {
@@ -2245,6 +2254,11 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private long mMetadataPtr; // native std::shared_ptr<CameraMetadata>*
 
+    // FastNative doesn't work with synchronized methods and we can do synchronization
+    // wherever needed in the java layer (caller). At some places in java such as
+    // setBase() / getBase(), we do need to synchronize the whole method, so leaving
+    // synchronized out for these native methods.
+
     @FastNative
     private static native long nativeAllocate();
     @FastNative
@@ -2254,28 +2268,41 @@
 
     @FastNative
     private static native void nativeUpdate(long dst, long src);
-    private static synchronized native void nativeWriteToParcel(Parcel dest, long ptr);
-    private static synchronized native void nativeReadFromParcel(Parcel source, long ptr);
-    private static synchronized native void nativeSwap(long ptr, long otherPtr)
+    @FastNative
+    private static native void nativeWriteToParcel(Parcel dest, long ptr);
+    @FastNative
+    private static native void nativeReadFromParcel(Parcel source, long ptr);
+    @FastNative
+    private static native void nativeSwap(long ptr, long otherPtr)
             throws NullPointerException;
     @FastNative
     private static native void nativeSetVendorId(long ptr, long vendorId);
-    private static synchronized native void nativeClose(long ptr);
-    private static synchronized native boolean nativeIsEmpty(long ptr);
-    private static synchronized native int nativeGetEntryCount(long ptr);
-    private static synchronized native long nativeGetBufferSize(long ptr);
+    @FastNative
+    private static native void nativeClose(long ptr);
+    @FastNative
+    private static native boolean nativeIsEmpty(long ptr);
+    @FastNative
+    private static native int nativeGetEntryCount(long ptr);
+    @FastNative
+    private static native long nativeGetBufferSize(long ptr);
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private static synchronized native byte[] nativeReadValues(int tag, long ptr);
-    private static synchronized native void nativeWriteValues(int tag, byte[] src, long ptr);
-    private static synchronized native void nativeDump(long ptr) throws IOException; // dump to LOGD
+    @FastNative
+    private static native byte[] nativeReadValues(int tag, long ptr);
+    @FastNative
+    private static native void nativeWriteValues(int tag, byte[] src, long ptr);
+    @FastNative
+    private static native void nativeDump(long ptr) throws IOException; // dump to LOGD
 
-    private static synchronized native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass);
+    @FastNative
+    private static native ArrayList nativeGetAllVendorKeys(long ptr, Class keyClass);
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private static synchronized native int nativeGetTagFromKeyLocal(long ptr, String keyName)
+    @FastNative
+    private static native int nativeGetTagFromKeyLocal(long ptr, String keyName)
             throws IllegalArgumentException;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private static synchronized native int nativeGetTypeFromTagLocal(long ptr, int tag)
+    @FastNative
+    private static native int nativeGetTypeFromTagLocal(long ptr, int tag)
             throws IllegalArgumentException;
     @FastNative
     private static native int nativeGetTagFromKey(String keyName, long vendorId)
@@ -2293,7 +2320,7 @@
      * @throws NullPointerException if other was null
      * @hide
      */
-    public void swap(CameraMetadataNative other) {
+    public synchronized void swap(CameraMetadataNative other) {
         nativeSwap(mMetadataPtr, other.mMetadataPtr);
         mCameraId = other.mCameraId;
         mHasMandatoryConcurrentStreams = other.mHasMandatoryConcurrentStreams;
@@ -2308,14 +2335,14 @@
      *
      * @hide
      */
-    public void setVendorId(long vendorId) {
+    public synchronized void setVendorId(long vendorId) {
         nativeSetVendorId(mMetadataPtr, vendorId);
     }
 
     /**
      * @hide
      */
-    public int getEntryCount() {
+    public synchronized int getEntryCount() {
         return nativeGetEntryCount(mMetadataPtr);
     }
 
@@ -2324,7 +2351,7 @@
      *
      * @hide
      */
-    public boolean isEmpty() {
+    public synchronized boolean isEmpty() {
         return nativeIsEmpty(mMetadataPtr);
     }
 
@@ -2343,7 +2370,7 @@
      *
      * @hide
      */
-    public <K>  ArrayList<K> getAllVendorKeys(Class<K> keyClass) {
+    public synchronized <K> ArrayList<K> getAllVendorKeys(Class<K> keyClass) {
         if (keyClass == null) {
             throw new NullPointerException();
         }
@@ -2398,7 +2425,7 @@
      *
      * @hide
      */
-    public void writeValues(int tag, byte[] src) {
+    public synchronized void writeValues(int tag, byte[] src) {
         nativeWriteValues(tag, src, mMetadataPtr);
     }
 
@@ -2413,7 +2440,7 @@
      * @return {@code null} if there were 0 entries for this tag, a byte[] otherwise.
      * @hide
      */
-    public byte[] readValues(int tag) {
+    public synchronized byte[] readValues(int tag) {
         // TODO: Optimization. Native code returns a ByteBuffer instead.
         return nativeReadValues(tag, mMetadataPtr);
     }
@@ -2426,7 +2453,7 @@
      *
      * @hide
      */
-    public void dumpToLog() {
+    public synchronized void dumpToLog() {
         try {
             nativeDump(mMetadataPtr);
         } catch (IOException e) {
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index 6a96a54..529ee91 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -113,16 +113,24 @@
      */
     public final int brightnessMaxReason;
 
+    /**
+     * Whether the current brightness value is overridden by the application window via
+     * {@link android.view.WindowManager.LayoutParams#screenBrightness}.
+     */
+    public final boolean isBrightnessOverrideByWindow;
+
     public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum,
             @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint,
             @BrightnessMaxReason int brightnessMaxReason) {
         this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode,
-                highBrightnessTransitionPoint, brightnessMaxReason);
+                highBrightnessTransitionPoint, brightnessMaxReason,
+                false /* isBrightnessOverrideByWindow */);
     }
 
     public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum,
             float brightnessMaximum, @HighBrightnessMode int highBrightnessMode,
-            float highBrightnessTransitionPoint, @BrightnessMaxReason int brightnessMaxReason) {
+            float highBrightnessTransitionPoint, @BrightnessMaxReason int brightnessMaxReason,
+            boolean isBrightnessOverrideByWindow) {
         this.brightness = brightness;
         this.adjustedBrightness = adjustedBrightness;
         this.brightnessMinimum = brightnessMinimum;
@@ -130,6 +138,7 @@
         this.highBrightnessMode = highBrightnessMode;
         this.highBrightnessTransitionPoint = highBrightnessTransitionPoint;
         this.brightnessMaxReason =  brightnessMaxReason;
+        this.isBrightnessOverrideByWindow = isBrightnessOverrideByWindow;
     }
 
     /**
@@ -178,6 +187,7 @@
         dest.writeInt(highBrightnessMode);
         dest.writeFloat(highBrightnessTransitionPoint);
         dest.writeInt(brightnessMaxReason);
+        dest.writeBoolean(isBrightnessOverrideByWindow);
     }
 
     public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR =
@@ -201,6 +211,7 @@
         highBrightnessMode = source.readInt();
         highBrightnessTransitionPoint = source.readFloat();
         brightnessMaxReason = source.readInt();
+        isBrightnessOverrideByWindow = source.readBoolean();
     }
 
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index a81bcbc..28da644 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -16,6 +16,7 @@
 
 package android.hardware.display;
 
+import static android.Manifest.permission.MANAGE_DISPLAYS;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.HdrCapabilities.HdrType;
 import static android.view.Display.INVALID_DISPLAY;
@@ -575,14 +576,22 @@
             EVENT_FLAG_DISPLAY_ADDED,
             EVENT_FLAG_DISPLAY_CHANGED,
             EVENT_FLAG_DISPLAY_REMOVED,
-            EVENT_FLAG_DISPLAY_BRIGHTNESS,
-            EVENT_FLAG_HDR_SDR_RATIO_CHANGED,
-            EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventFlag {}
 
     /**
+     * @hide
+     */
+    @LongDef(flag = true, prefix = {"PRIVATE_EVENT_FLAG_"}, value = {
+            PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
+            PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED,
+            PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PrivateEventFlag {}
+
+    /**
      * Event type for when a new display is added.
      *
      * @see #registerDisplayListener(DisplayListener, Handler, long)
@@ -618,7 +627,7 @@
      *
      * @hide
      */
-    public static final long EVENT_FLAG_DISPLAY_BRIGHTNESS = 1L << 3;
+    public static final long PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS = 1L << 0;
 
     /**
      * Event flag to register for a display's hdr/sdr ratio changes. This notification is sent
@@ -631,14 +640,16 @@
      *
      * @hide
      */
-    public static final long EVENT_FLAG_HDR_SDR_RATIO_CHANGED = 1L << 4;
+    public static final long PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED = 1L << 1;
 
     /**
      * Event flag to register for a display's connection changed.
      *
+     * @see #registerDisplayListener(DisplayListener, Handler, long)
      * @hide
      */
-    public static final long EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5;
+    public static final long PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 2;
+
 
     /** @hide */
     public DisplayManager(Context context) {
@@ -774,20 +785,49 @@
      * @param listener The listener to register.
      * @param handler The handler on which the listener should be invoked, or null
      * if the listener should be invoked on the calling thread's looper.
-     * @param eventFlagsMask A bitmask of the event types for which this listener is subscribed.
+     * @param eventFlags A bitmask of the event types for which this listener is subscribed.
      *
      * @see #EVENT_FLAG_DISPLAY_ADDED
      * @see #EVENT_FLAG_DISPLAY_CHANGED
      * @see #EVENT_FLAG_DISPLAY_REMOVED
-     * @see #EVENT_FLAG_DISPLAY_BRIGHTNESS
      * @see #registerDisplayListener(DisplayListener, Handler)
      * @see #unregisterDisplayListener
      *
      * @hide
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
-            @Nullable Handler handler, @EventFlag long eventFlagsMask) {
-        mGlobal.registerDisplayListener(listener, handler, eventFlagsMask,
+            @Nullable Handler handler, @EventFlag long eventFlags) {
+        mGlobal.registerDisplayListener(listener, handler,
+                mGlobal.mapFlagsToInternalEventFlag(eventFlags, 0),
+                ActivityThread.currentPackageName());
+    }
+
+    /**
+     * Registers a display listener to receive notifications about given display event types.
+     *
+     * @param listener The listener to register.
+     * @param handler The handler on which the listener should be invoked, or null
+     * if the listener should be invoked on the calling thread's looper.
+     * @param eventFlags A bitmask of the event types for which this listener is subscribed.
+     * @param privateEventFlags A bitmask of the private event types for which this listener
+     *                          is subscribed.
+     *
+     * @see #EVENT_FLAG_DISPLAY_ADDED
+     * @see #EVENT_FLAG_DISPLAY_CHANGED
+     * @see #EVENT_FLAG_DISPLAY_REMOVED
+     * @see #PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS
+     * @see #PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED
+     * @see #PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED
+     * @see #registerDisplayListener(DisplayListener, Handler)
+     * @see #unregisterDisplayListener
+     *
+     * @hide
+     */
+    public void registerDisplayListener(@NonNull DisplayListener listener,
+            @Nullable Handler handler, @EventFlag long eventFlags,
+            @PrivateEventFlag long privateEventFlags) {
+        mGlobal.registerDisplayListener(listener, handler,
+                mGlobal.mapFlagsToInternalEventFlag(eventFlags, privateEventFlags),
                 ActivityThread.currentPackageName());
     }
 
@@ -1725,6 +1765,29 @@
     }
 
     /**
+     * @return The current display topology that represents the relative positions of extended
+     * displays.
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_DISPLAYS)
+    @Nullable
+    public DisplayTopology getDisplayTopology() {
+        return mGlobal.getDisplayTopology();
+    }
+
+    /**
+     * Set the relative positions between extended displays (display topology).
+     * @param topology The display topology to be set
+     *
+     * @hide
+     */
+    @RequiresPermission(MANAGE_DISPLAYS)
+    public void setDisplayTopology(DisplayTopology topology) {
+        mGlobal.setDisplayTopology(topology);
+    }
+
+    /**
      * Listens for changes in available display devices.
      */
     public interface DisplayListener {
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 56307ae..03b44f6 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -18,12 +18,14 @@
 
 
 import static android.hardware.display.DisplayManager.EventFlag;
+import static android.Manifest.permission.MANAGE_DISPLAYS;
 import static android.view.Display.HdrCapabilities.HdrType;
 
 import android.Manifest;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
+import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -118,6 +120,24 @@
     public static final int EVENT_DISPLAY_CONNECTED = 6;
     public static final int EVENT_DISPLAY_DISCONNECTED = 7;
 
+    @LongDef(prefix = {"INTERNAL_EVENT_DISPLAY"}, flag = true, value = {
+            INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
+            INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+            INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
+            INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
+            INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
+            INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface InternalEventFlag {}
+
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_ADDED = 1L << 0;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_CHANGED = 1L << 1;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_REMOVED = 1L << 2;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED = 1L << 3;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED = 1L << 4;
+    public static final long INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED = 1L << 5;
+
     @UnsupportedAppUsage
     private static DisplayManagerGlobal sInstance;
 
@@ -130,7 +150,7 @@
     private final IDisplayManager mDm;
 
     private DisplayManagerCallback mCallback;
-    private @EventFlag long mRegisteredEventFlagsMask = 0;
+    private @InternalEventFlag long mRegisteredInternalEventFlag = 0;
     private final CopyOnWriteArrayList<DisplayListenerDelegate> mDisplayListeners =
             new CopyOnWriteArrayList<>();
 
@@ -346,11 +366,11 @@
      * @param packageName of the calling package.
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
-            @Nullable Handler handler, @EventFlag long eventFlagsMask,
+            @Nullable Handler handler, @InternalEventFlag long internalEventFlagsMask,
             String packageName) {
         Looper looper = getLooperForHandler(handler);
         Handler springBoard = new Handler(looper);
-        registerDisplayListener(listener, new HandlerExecutor(springBoard), eventFlagsMask,
+        registerDisplayListener(listener, new HandlerExecutor(springBoard), internalEventFlagsMask,
                 packageName);
     }
 
@@ -359,32 +379,34 @@
      *
      * @param listener The listener that will be called when display changes occur.
      * @param executor Executor for the thread that will be receiving the callbacks. Cannot be null.
-     * @param eventFlagsMask Flag of events to be listened to.
+     * @param internalEventFlagsMask Mask of events to be listened to.
      * @param packageName of the calling package.
      */
     public void registerDisplayListener(@NonNull DisplayListener listener,
-            @NonNull Executor executor, @EventFlag long eventFlagsMask, String packageName) {
+            @NonNull Executor executor, @InternalEventFlag long internalEventFlagsMask,
+            String packageName) {
         if (listener == null) {
             throw new IllegalArgumentException("listener must not be null");
         }
 
-        if (eventFlagsMask == 0) {
+        if (internalEventFlagsMask == 0) {
             throw new IllegalArgumentException("The set of events to listen to must not be empty.");
         }
 
         if (extraLogging()) {
             Slog.i(TAG, "Registering Display Listener: "
-                    + Long.toBinaryString(eventFlagsMask) + ", packageName: " + packageName);
+                    + Long.toBinaryString(internalEventFlagsMask)
+                    + ", packageName: " + packageName);
         }
 
         synchronized (mLock) {
             int index = findDisplayListenerLocked(listener);
             if (index < 0) {
                 mDisplayListeners.add(new DisplayListenerDelegate(listener, executor,
-                        eventFlagsMask, packageName));
+                        internalEventFlagsMask, packageName));
                 registerCallbackIfNeededLocked();
             } else {
-                mDisplayListeners.get(index).setEventFlagsMask(eventFlagsMask);
+                mDisplayListeners.get(index).setEventsMask(internalEventFlagsMask);
             }
             updateCallbackIfNeededLocked();
             maybeLogAllDisplayListeners();
@@ -456,17 +478,17 @@
         return -1;
     }
 
-    @EventFlag
-    private int calculateEventFlagsMaskLocked() {
-        int mask = 0;
+    @InternalEventFlag
+    private long calculateEventsMaskLocked() {
+        long mask = 0;
         final int numListeners = mDisplayListeners.size();
         for (int i = 0; i < numListeners; i++) {
-            mask |= mDisplayListeners.get(i).mEventFlagsMask;
+            mask |= mDisplayListeners.get(i).mInternalEventFlagsMask;
         }
         if (mDispatchNativeCallbacks) {
-            mask |= DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+            mask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                    | INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                    | INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         }
         return mask;
     }
@@ -479,14 +501,14 @@
     }
 
     private void updateCallbackIfNeededLocked() {
-        int mask = calculateEventFlagsMaskLocked();
+        long mask = calculateEventsMaskLocked();
         if (DEBUG) {
-            Log.d(TAG, "Flag for listener: " + mask);
+            Log.d(TAG, "Mask for listener: " + mask);
         }
-        if (mask != mRegisteredEventFlagsMask) {
+        if (mask != mRegisteredInternalEventFlag) {
             try {
                 mDm.registerCallbackWithEventMask(mCallback, mask);
-                mRegisteredEventFlagsMask = mask;
+                mRegisteredInternalEventFlag = mask;
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -1264,12 +1286,37 @@
         }
     }
 
+    /**
+     * @see DisplayManager#getDisplayTopology
+     */
+    @RequiresPermission(MANAGE_DISPLAYS)
+    @Nullable
+    public DisplayTopology getDisplayTopology() {
+        try {
+            return mDm.getDisplayTopology();
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see DisplayManager#setDisplayTopology
+     */
+    @RequiresPermission(MANAGE_DISPLAYS)
+    public void setDisplayTopology(DisplayTopology topology) {
+        try {
+            mDm.setDisplayTopology(topology);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
     private final class DisplayManagerCallback extends IDisplayManagerCallback.Stub {
         @Override
         public void onDisplayEvent(int displayId, @DisplayEvent int event) {
             if (DEBUG) {
-                Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event=" + eventToString(
-                        event));
+                Log.d(TAG, "onDisplayEvent: displayId=" + displayId + ", event="
+                        + eventToString(event));
             }
             handleDisplayEvent(displayId, event, false /* forceUpdate */);
         }
@@ -1277,7 +1324,7 @@
 
     private static final class DisplayListenerDelegate {
         public final DisplayListener mListener;
-        public volatile long mEventFlagsMask;
+        public volatile long mInternalEventFlagsMask;
 
         private final DisplayInfo mDisplayInfo = new DisplayInfo();
         private final Executor mExecutor;
@@ -1285,10 +1332,10 @@
         private final String mPackageName;
 
         DisplayListenerDelegate(DisplayListener listener, @NonNull Executor executor,
-                @EventFlag long eventFlag, String packageName) {
+                @InternalEventFlag long internalEventFlag, String packageName) {
             mExecutor = executor;
             mListener = listener;
-            mEventFlagsMask = eventFlag;
+            mInternalEventFlagsMask = internalEventFlag;
             mPackageName = packageName;
         }
 
@@ -1310,16 +1357,16 @@
             mGenerationId.incrementAndGet();
         }
 
-        void setEventFlagsMask(@EventFlag long newEventsFlag) {
-            mEventFlagsMask = newEventsFlag;
+        void setEventsMask(@InternalEventFlag long newInternalEventFlagsMask) {
+            mInternalEventFlagsMask = newInternalEventFlagsMask;
         }
 
-        private void handleDisplayEventInner(int displayId, @DisplayEvent int eventFlagsMask,
+        private void handleDisplayEventInner(int displayId, @DisplayEvent int event,
                 @Nullable DisplayInfo info, boolean forceUpdate) {
             if (extraLogging()) {
-                Slog.i(TAG, "DLD(" + eventToString(eventFlagsMask)
+                Slog.i(TAG, "DLD(" + eventToString(event)
                         + ", display=" + displayId
-                        + ", mEventsFlagMask=" + Long.toBinaryString(mEventFlagsMask)
+                        + ", mEventsMask=" + Long.toBinaryString(mInternalEventFlagsMask)
                         + ", mPackageName=" + mPackageName
                         + ", displayInfo=" + info
                         + ", listener=" + mListener.getClass() + ")");
@@ -1327,18 +1374,19 @@
             if (DEBUG) {
                 Trace.beginSection(
                         TextUtils.trimToSize(
-                                "DLD(" + eventToString(eventFlagsMask)
+                                "DLD(" + eventToString(event)
                                 + ", display=" + displayId
                                 + ", listener=" + mListener.getClass() + ")", 127));
             }
-            switch (eventFlagsMask) {
+            switch (event) {
                 case EVENT_DISPLAY_ADDED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) {
+                    if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0) {
                         mListener.onDisplayAdded(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_CHANGED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
+                    if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_CHANGED)
+                            != 0) {
                         if (info != null && (forceUpdate || !info.equals(mDisplayInfo))) {
                             if (extraLogging()) {
                                 Slog.i(TAG, "Sending onDisplayChanged: Display Changed. Info: "
@@ -1350,29 +1398,32 @@
                     }
                     break;
                 case EVENT_DISPLAY_BRIGHTNESS_CHANGED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) {
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED) != 0) {
                         mListener.onDisplayChanged(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_REMOVED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) {
+                    if ((mInternalEventFlagsMask & INTERNAL_EVENT_FLAG_DISPLAY_REMOVED)
+                            != 0) {
                         mListener.onDisplayRemoved(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0) {
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED) != 0) {
                         mListener.onDisplayChanged(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_CONNECTED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
-                            != 0) {
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
                         mListener.onDisplayConnected(displayId);
                     }
                     break;
                 case EVENT_DISPLAY_DISCONNECTED:
-                    if ((mEventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
-                            != 0) {
+                    if ((mInternalEventFlagsMask
+                            & INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
                         mListener.onDisplayDisconnected(displayId);
                     }
                     break;
@@ -1384,7 +1435,7 @@
 
         @Override
         public String toString() {
-            return "mEventFlagsMask: {" + mEventFlagsMask + "}, for " + mListener.getClass();
+            return "flag: {" + mInternalEventFlagsMask + "}, for " + mListener.getClass();
         }
     }
 
@@ -1532,4 +1583,53 @@
     private static boolean extraLogging() {
         return sExtraDisplayListenerLogging;
     }
+
+
+    /**
+     * Maps the supplied public and private event flags to a unified InternalEventFlag
+     * @param eventFlags A bitmask of the event types for which this listener is subscribed.
+     * @param privateEventFlags A bitmask of the private event types for which this listener
+     *                          is subscribed.
+     * @return returns the bitmask of both public and private event flags unified to
+     * InternalEventFlag
+     */
+    public @InternalEventFlag long mapFlagsToInternalEventFlag(@EventFlag long eventFlags,
+            @DisplayManager.PrivateEventFlag long privateEventFlags) {
+        return mapPrivateEventFlags(privateEventFlags) | mapPublicEventFlags(eventFlags);
+    }
+
+    private long mapPrivateEventFlags(@DisplayManager.PrivateEventFlag long privateEventFlags) {
+        long baseEventMask = 0;
+        if ((privateEventFlags & DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED;
+        }
+
+        if ((privateEventFlags & DisplayManager.PRIVATE_EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED;
+        }
+
+        if ((privateEventFlags
+                & DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
+        }
+        return baseEventMask;
+    }
+
+    private long mapPublicEventFlags(@EventFlag long eventFlags) {
+        long baseEventMask = 0;
+        if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_ADDED;
+        }
+
+        if ((eventFlags & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_CHANGED;
+        }
+
+        if ((eventFlags
+                & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0) {
+            baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
+        }
+
+        return baseEventMask;
+    }
 }
diff --git a/core/java/android/hardware/display/DisplayTopology.aidl b/core/java/android/hardware/display/DisplayTopology.aidl
new file mode 100644
index 0000000..e69b777
--- /dev/null
+++ b/core/java/android/hardware/display/DisplayTopology.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display;
+
+parcelable DisplayTopology;
diff --git a/services/core/java/com/android/server/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
similarity index 73%
rename from services/core/java/com/android/server/display/DisplayTopology.java
rename to core/java/android/hardware/display/DisplayTopology.java
index fdadafe..e349b81 100644
--- a/services/core/java/com/android/server/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -14,25 +14,34 @@
  * limitations under the License.
  */
 
-package com.android.server.display;
+package android.hardware.display;
 
-import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM;
-import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_LEFT;
-import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP;
-import static com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT;
+import static android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM;
+import static android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT;
+import static android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT;
+import static android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
 import android.util.Slog;
 import android.view.Display;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
@@ -42,24 +51,59 @@
 /**
  * Represents the relative placement of extended displays.
  * Does not support concurrent calls, so a lock should be held when calling into this class.
+ *
+ * @hide
  */
-class DisplayTopology {
+public final class DisplayTopology implements Parcelable {
     private static final String TAG = "DisplayTopology";
     private static final float EPSILON = 0.0001f;
 
+    @android.annotation.NonNull
+    public static final Creator<DisplayTopology> CREATOR =
+            new Creator<>() {
+                @Override
+                public DisplayTopology createFromParcel(Parcel source) {
+                    return new DisplayTopology(source);
+                }
+
+                @Override
+                public DisplayTopology[] newArray(int size) {
+                    return new DisplayTopology[size];
+                }
+            };
+
     /**
      * The topology tree
      */
     @Nullable
-    @VisibleForTesting
-    TreeNode mRoot;
+    private TreeNode mRoot;
 
     /**
      * The logical display ID of the primary display that will show certain UI elements.
      * This is not necessarily the same as the default display.
      */
+    private int mPrimaryDisplayId = Display.INVALID_DISPLAY;
+
+    public DisplayTopology() {}
+
     @VisibleForTesting
-    int mPrimaryDisplayId = Display.INVALID_DISPLAY;
+    public DisplayTopology(TreeNode root, int primaryDisplayId) {
+        mRoot = root;
+        mPrimaryDisplayId = primaryDisplayId;
+    }
+
+    public DisplayTopology(Parcel source) {
+        this(source.readTypedObject(TreeNode.CREATOR), source.readInt());
+    }
+
+    @Nullable
+    public TreeNode getRoot() {
+        return mRoot;
+    }
+
+    public int getPrimaryDisplayId() {
+        return mPrimaryDisplayId;
+    }
 
     /**
      * Add a display to the topology.
@@ -69,7 +113,7 @@
      * @param width The width of the display
      * @param height The height of the display
      */
-    void addDisplay(int displayId, float width, float height) {
+    public void addDisplay(int displayId, float width, float height) {
         addDisplay(displayId, width, height, /* shouldLog= */ true);
     }
 
@@ -79,7 +123,7 @@
      * one by one.
      * @param displayId The logical display ID
      */
-    void removeDisplay(int displayId) {
+    public void removeDisplay(int displayId) {
         if (findDisplay(displayId, mRoot) == null) {
             return;
         }
@@ -106,11 +150,22 @@
         }
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeTypedObject(mRoot, flags);
+        dest.writeInt(mPrimaryDisplayId);
+    }
+
     /**
      * Print the object's state and debug information into the given stream.
      * @param pw The stream to dump information to.
      */
-    void dump(PrintWriter pw) {
+    public void dump(PrintWriter pw) {
         pw.println("DisplayTopology:");
         pw.println("--------------------");
         IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
@@ -126,13 +181,21 @@
         }
     }
 
+    @Override
+    public String toString() {
+        StringWriter out = new StringWriter();
+        PrintWriter writer = new PrintWriter(out);
+        dump(writer);
+        return out.toString();
+    }
+
     private void addDisplay(int displayId, float width, float height, boolean shouldLog) {
         if (findDisplay(displayId, mRoot) != null) {
             throw new IllegalArgumentException(
                     "DisplayTopology: attempting to add a display that already exists");
         }
         if (mRoot == null) {
-            mRoot = new TreeNode(displayId, width, height, /* position= */ null, /* offset= */ 0);
+            mRoot = new TreeNode(displayId, width, height, /* position= */ 0, /* offset= */ 0);
             mPrimaryDisplayId = displayId;
             if (shouldLog) {
                 Slog.i(TAG, "First display added: " + mRoot);
@@ -241,7 +304,7 @@
      * Update the topology to remove any overlaps between displays.
      */
     @VisibleForTesting
-    void normalize() {
+    public void normalize() {
         if (mRoot == null) {
             return;
         }
@@ -341,6 +404,8 @@
                 case POSITION_RIGHT -> floatEquals(parentBounds.right, childBounds.left);
                 case POSITION_TOP -> floatEquals(parentBounds.top, childBounds.bottom);
                 case POSITION_BOTTOM -> floatEquals(parentBounds.bottom, childBounds.top);
+                default -> throw new IllegalStateException(
+                        "Unexpected value: " + targetDisplay.mPosition);
             };
             // Check that the offset is within bounds
             areTouching &= switch (targetDisplay.mPosition) {
@@ -350,6 +415,8 @@
                 case POSITION_TOP, POSITION_BOTTOM ->
                         childBounds.right + EPSILON >= parentBounds.left
                                 && childBounds.left <= parentBounds.right + EPSILON;
+                default -> throw new IllegalStateException(
+                        "Unexpected value: " + targetDisplay.mPosition);
             };
 
             if (!areTouching) {
@@ -379,36 +446,56 @@
      * @param b second float to compare
      * @return whether the two values are within a small enough tolerance value
      */
-    public static boolean floatEquals(float a, float b) {
-        return a == b || Float.isNaN(a) && Float.isNaN(b) || Math.abs(a - b) < EPSILON;
+    private static boolean floatEquals(float a, float b) {
+        return a == b || (Float.isNaN(a) && Float.isNaN(b)) || Math.abs(a - b) < EPSILON;
     }
 
-    @VisibleForTesting
-    static class TreeNode {
+    public static final class TreeNode implements Parcelable {
+        public static final int POSITION_LEFT = 0;
+        public static final int POSITION_TOP = 1;
+        public static final int POSITION_RIGHT = 2;
+        public static final int POSITION_BOTTOM = 3;
+
+        @IntDef(prefix = { "POSITION_" }, value = {
+                POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Position{}
+
+        @android.annotation.NonNull
+        public static final Creator<TreeNode> CREATOR =
+                new Creator<>() {
+                    @Override
+                    public TreeNode createFromParcel(Parcel source) {
+                        return new TreeNode(source);
+                    }
+
+                    @Override
+                    public TreeNode[] newArray(int size) {
+                        return new TreeNode[size];
+                    }
+                };
 
         /**
          * The logical display ID
          */
-        @VisibleForTesting
-        final int mDisplayId;
+        private final int mDisplayId;
 
         /**
          * The width of the display in density-independent pixels (dp).
          */
-        @VisibleForTesting
-        float mWidth;
+        private final float mWidth;
 
         /**
          * The height of the display in density-independent pixels (dp).
          */
-        @VisibleForTesting
-        float mHeight;
+        private final float mHeight;
 
         /**
          * The position of this display relative to its parent.
          */
-        @VisibleForTesting
-        Position mPosition;
+        @Position
+        private int mPosition;
 
         /**
          * The distance from the top edge of the parent display to the top edge of this display (in
@@ -416,13 +503,13 @@
          * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit
          * used is density-independent pixels (dp).
          */
-        @VisibleForTesting
-        float mOffset;
+        private float mOffset;
+
+        private final List<TreeNode> mChildren = new ArrayList<>();
 
         @VisibleForTesting
-        final List<TreeNode> mChildren = new ArrayList<>();
-
-        TreeNode(int displayId, float width, float height, Position position, float offset) {
+        public TreeNode(int displayId, float width, float height, @Position int position,
+                float offset) {
             mDisplayId = displayId;
             mWidth = width;
             mHeight = height;
@@ -430,11 +517,76 @@
             mOffset = offset;
         }
 
+        public TreeNode(Parcel source) {
+            this(source.readInt(), source.readFloat(), source.readFloat(), source.readInt(),
+                    source.readFloat());
+            source.readTypedList(mChildren, CREATOR);
+        }
+
+        public int getDisplayId() {
+            return mDisplayId;
+        }
+
+        public float getWidth() {
+            return mWidth;
+        }
+
+        public float getHeight() {
+            return mHeight;
+        }
+
+        public int getPosition() {
+            return mPosition;
+        }
+
+        public float getOffset() {
+            return mOffset;
+        }
+
+        public List<TreeNode> getChildren() {
+            return Collections.unmodifiableList(mChildren);
+        }
+
+        @Override
+        public String toString() {
+            return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight
+                    + ", position=" + positionToString(mPosition) + ", offset=" + mOffset + "}";
+        }
+
+        /**
+         * @param position The position
+         * @return The string representation
+         */
+        public static String positionToString(@Position int position) {
+            return switch (position) {
+                case POSITION_LEFT -> "left";
+                case POSITION_TOP -> "top";
+                case POSITION_RIGHT -> "right";
+                case POSITION_BOTTOM -> "bottom";
+                default -> throw new IllegalStateException("Unexpected value: " + position);
+            };
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeInt(mDisplayId);
+            dest.writeFloat(mWidth);
+            dest.writeFloat(mHeight);
+            dest.writeInt(mPosition);
+            dest.writeFloat(mOffset);
+            dest.writeTypedList(mChildren);
+        }
+
         /**
          * Print the object's state and debug information into the given stream.
          * @param ipw The stream to dump information to.
          */
-        void dump(IndentingPrintWriter ipw) {
+        public void dump(IndentingPrintWriter ipw) {
             ipw.println(this);
             ipw.increaseIndent();
             for (TreeNode child : mChildren) {
@@ -443,15 +595,12 @@
             ipw.decreaseIndent();
         }
 
-        @Override
-        public String toString() {
-            return "Display {id=" + mDisplayId + ", width=" + mWidth + ", height=" + mHeight
-                    + ", position=" + mPosition + ", offset=" + mOffset + "}";
-        }
-
+        /**
+         * @param child The child to add
+         */
         @VisibleForTesting
-        enum Position {
-            POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+        public void addChild(TreeNode child) {
+            mChildren.add(child);
         }
     }
 }
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index b612bca..4fbdf7f 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -23,6 +23,7 @@
 import android.hardware.display.BrightnessInfo;
 import android.hardware.display.Curve;
 import android.hardware.graphics.common.DisplayDecorationSupport;
+import android.hardware.display.DisplayTopology;
 import android.hardware.display.HdrConversionMode;
 import android.hardware.display.IDisplayManagerCallback;
 import android.hardware.display.IVirtualDisplayCallback;
@@ -254,4 +255,13 @@
     // Get the default doze brightness
     @EnforcePermission("CONTROL_DISPLAY_BRIGHTNESS")
     float getDefaultDozeBrightness(int displayId);
+
+    // Get the display topology
+    @EnforcePermission("MANAGE_DISPLAYS")
+    @nullable
+    DisplayTopology getDisplayTopology();
+
+    // Set the display topology
+    @EnforcePermission("MANAGE_DISPLAYS")
+    void setDisplayTopology(in DisplayTopology topology);
 }
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1b96224..3284761 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -276,9 +276,9 @@
     @PermissionManuallyEnforced
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
-    void removeAllCustomInputGestures(int userId);
+    void removeAllCustomInputGestures(int userId, int tag);
 
-    AidlInputGestureData[] getCustomInputGestures(int userId);
+    AidlInputGestureData[] getCustomInputGestures(int userId, int tag);
 
     AidlInputGestureData[] getAppLaunchBookmarks();
 }
diff --git a/core/java/android/hardware/input/InputGestureData.java b/core/java/android/hardware/input/InputGestureData.java
index ee0a2a9..f41550f 100644
--- a/core/java/android/hardware/input/InputGestureData.java
+++ b/core/java/android/hardware/input/InputGestureData.java
@@ -296,4 +296,35 @@
     public record Action(@KeyGestureEvent.KeyGestureType int keyGestureType,
                          @Nullable AppLaunchData appLaunchData) {
     }
+
+    /** Filter definition for InputGestureData */
+    public enum Filter {
+        KEY(AidlInputGestureData.Trigger.Tag.key),
+        TOUCHPAD(AidlInputGestureData.Trigger.Tag.touchpadGesture);
+
+        @AidlInputGestureData.Trigger.Tag
+        private final int mTag;
+
+        Filter(@AidlInputGestureData.Trigger.Tag int tag) {
+            mTag = tag;
+        }
+
+        @Nullable
+        public static Filter of(@AidlInputGestureData.Trigger.Tag int tag) {
+            return switch (tag) {
+                case AidlInputGestureData.Trigger.Tag.key -> KEY;
+                case AidlInputGestureData.Trigger.Tag.touchpadGesture -> TOUCHPAD;
+                default -> null;
+            };
+        }
+
+        @AidlInputGestureData.Trigger.Tag
+        public int getTag() {
+            return mTag;
+        }
+
+        public boolean matches(@NonNull InputGestureData inputGestureData) {
+            return mTag == inputGestureData.mInputGestureData.trigger.getTag();
+        }
+    }
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9050ae2..f824192 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1526,16 +1526,20 @@
 
     /** Removes all custom input gestures
      *
+     * @param filter for removing all gestures of a category. If {@code null}, all custom input
+     *               gestures will be removed
+     *
      * @hide
      */
     @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
     @UserHandleAware
-    public void removeAllCustomInputGestures() {
+    public void removeAllCustomInputGestures(@Nullable InputGestureData.Filter filter) {
         if (!enableCustomizableInputGestures()) {
             return;
         }
         try {
-            mIm.removeAllCustomInputGestures(mContext.getUserId());
+            mIm.removeAllCustomInputGestures(mContext.getUserId(),
+                    filter == null ? -1 : filter.getTag());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1543,16 +1547,20 @@
 
     /** Get all custom input gestures
      *
+     * @param filter for fetching all gestures of a category. If {@code null}, then will return
+     *               all custom input gestures
+     *
      * @hide
      */
     @UserHandleAware
-    public List<InputGestureData> getCustomInputGestures() {
+    public List<InputGestureData> getCustomInputGestures(@Nullable InputGestureData.Filter filter) {
         List<InputGestureData> result = new ArrayList<>();
         if (!enableCustomizableInputGestures()) {
             return result;
         }
         try {
-            for (AidlInputGestureData data : mIm.getCustomInputGestures(mContext.getUserId())) {
+            for (AidlInputGestureData data : mIm.getCustomInputGestures(mContext.getUserId(),
+                    filter == null ? -1 : filter.getTag())) {
                 result.add(new InputGestureData(data));
             }
         } catch (RemoteException e) {
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 38e32c6..a8eb11d 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -30,14 +30,6 @@
 
 flag {
     namespace: "input_native"
-    name: "emoji_and_screenshot_keycodes_available"
-    is_exported: true
-    description: "Add new KeyEvent keycodes for opening Emoji Picker and Taking Screenshots"
-    bug: "315307777"
-}
-
-flag {
-    namespace: "input_native"
     name: "keyboard_a11y_slow_keys_flag"
     description: "Controls if the slow keys accessibility feature for physical keyboard is available to the user"
     bug: "294546335"
@@ -153,6 +145,13 @@
 }
 
 flag {
+    name: "enable_new_25q2_keycodes"
+    namespace: "input"
+    description: "Enables new 25Q2 keycodes"
+    bug: "365920375"
+}
+
+flag {
   name: "override_power_key_behavior_in_focused_window"
   namespace: "input_native"
   description: "Allows privileged focused windows to capture power key events."
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index 858ec23..af715e4 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -15,7 +15,6 @@
  */
 package android.hardware.location;
 
-import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 6284e70..494bfc9 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -18,6 +18,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,7 +32,6 @@
 import android.app.PendingIntent;
 import android.chre.flags.Flags;
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.hardware.contexthub.ErrorCode;
 import android.os.Handler;
@@ -484,15 +484,33 @@
         }
     }
 
-   /**
-    * Helper function to generate a stub for a query transaction callback.
-    *
-    * @param transaction the transaction to unblock when complete
-    *
-    * @return the callback
-    *
-    * @hide
-    */
+    /**
+     * Returns the list of HubInfo objects describing the available hubs (including ContextHub and
+     * VendorHub). This method is primarily used for debugging purposes as most clients care about
+     * endpoints and services more than hubs.
+     *
+     * @return the list of HubInfo objects
+     * @see HubInfo
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @NonNull
+    @FlaggedApi(Flags.FLAG_OFFLOAD_API)
+    public List<HubInfo> getHubs() {
+        try {
+            return mService.getHubs();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Helper function to generate a stub for a query transaction callback.
+     *
+     * @param transaction the transaction to unblock when complete
+     * @return the callback
+     * @hide
+     */
     private IContextHubTransactionCallback createQueryCallback(
             ContextHubTransaction<List<NanoAppState>> transaction) {
         return new IContextHubTransactionCallback.Stub() {
diff --git a/core/java/android/hardware/location/HubInfo.aidl b/core/java/android/hardware/location/HubInfo.aidl
new file mode 100644
index 0000000..25b5b0a
--- /dev/null
+++ b/core/java/android/hardware/location/HubInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+/** @hide */
+parcelable HubInfo;
diff --git a/core/java/android/hardware/location/HubInfo.java b/core/java/android/hardware/location/HubInfo.java
new file mode 100644
index 0000000..f7de127
--- /dev/null
+++ b/core/java/android/hardware/location/HubInfo.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.chre.flags.Flags;
+import android.os.BadParcelableException;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Union type for {@link ContextHubInfo} and {@link VendorHubInfo}
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public final class HubInfo implements Parcelable {
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {TYPE_CONTEXT_HUB, TYPE_VENDOR_HUB})
+    private @interface HubType {}
+
+    public static final int TYPE_CONTEXT_HUB = 0;
+    public static final int TYPE_VENDOR_HUB = 1;
+
+    private final long mId;
+    @HubType private final int mType;
+    @Nullable private final ContextHubInfo mContextHubInfo;
+    @Nullable private final VendorHubInfo mVendorHubInfo;
+
+    /** @hide */
+    public HubInfo(long id, @NonNull ContextHubInfo contextHubInfo) {
+        mId = id;
+        mType = TYPE_CONTEXT_HUB;
+        mContextHubInfo = contextHubInfo;
+        mVendorHubInfo = null;
+    }
+
+    /** @hide */
+    public HubInfo(long id, @NonNull VendorHubInfo vendorHubInfo) {
+        mId = id;
+        mType = TYPE_VENDOR_HUB;
+        mContextHubInfo = null;
+        mVendorHubInfo = vendorHubInfo;
+    }
+
+    private HubInfo(Parcel in) {
+        mId = in.readLong();
+        mType = in.readInt();
+
+        switch (mType) {
+            case TYPE_CONTEXT_HUB:
+                mContextHubInfo = ContextHubInfo.CREATOR.createFromParcel(in);
+                mVendorHubInfo = null;
+                break;
+            case TYPE_VENDOR_HUB:
+                mVendorHubInfo = VendorHubInfo.CREATOR.createFromParcel(in);
+                mContextHubInfo = null;
+                break;
+            default:
+                throw new BadParcelableException("Parcelable has invalid type");
+        }
+    }
+
+    /** Get the hub unique identifier */
+    public long getId() {
+        return mId;
+    }
+
+    /** Get the hub type. The type can be {@link TYPE_CONTEXT_HUB} or {@link TYPE_VENDOR_HUB} */
+    public int getType() {
+        return mType;
+    }
+
+    /** Get the {@link ContextHubInfo} object, null if type is not {@link TYPE_CONTEXT_HUB} */
+    @Nullable
+    public ContextHubInfo getContextHubInfo() {
+        return mContextHubInfo;
+    }
+
+    /** Parcel implementation details */
+    public int describeContents() {
+        if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) {
+            return mContextHubInfo.describeContents();
+        }
+        if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) {
+            return mVendorHubInfo.describeContents();
+        }
+        return 0;
+    }
+
+    /** Parcel implementation details */
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeLong(mId);
+        out.writeInt(mType);
+
+        if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) {
+            mContextHubInfo.writeToParcel(out, flags);
+        }
+
+        if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) {
+            mVendorHubInfo.writeToParcel(out, flags);
+        }
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder();
+        out.append("HubInfo ID: 0x");
+        out.append(Long.toHexString(mId));
+        out.append("\n");
+        if (mType == TYPE_CONTEXT_HUB && mContextHubInfo != null) {
+            out.append(" ContextHubDetails: ");
+            out.append(mContextHubInfo);
+        }
+        if (mType == TYPE_VENDOR_HUB && mVendorHubInfo != null) {
+            out.append(" VendorHubDetails: ");
+            out.append(mVendorHubInfo);
+        }
+        return out.toString();
+    }
+
+    public static final @NonNull Creator<HubInfo> CREATOR =
+            new Creator<>() {
+                public HubInfo createFromParcel(Parcel in) {
+                    return new HubInfo(in);
+                }
+
+                public HubInfo[] newArray(int size) {
+                    return new HubInfo[size];
+                }
+            };
+}
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index 11f30461..b0cc763 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -18,6 +18,7 @@
 
 // Declare any non-default types here with import statements
 import android.app.PendingIntent;
+import android.hardware.location.HubInfo;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
 import android.hardware.location.NanoApp;
@@ -82,6 +83,10 @@
     @EnforcePermission("ACCESS_CONTEXT_HUB")
     List<ContextHubInfo> getContextHubs();
 
+    // Returns a list of HubInfo objects of available hubs (including ContextHub and VendorHub)
+    @EnforcePermission("ACCESS_CONTEXT_HUB")
+    List<HubInfo> getHubs();
+
     // Loads a nanoapp at the specified hub (new API)
     @EnforcePermission("ACCESS_CONTEXT_HUB")
     void loadNanoAppOnHub(
diff --git a/core/java/android/hardware/location/VendorHubInfo.aidl b/core/java/android/hardware/location/VendorHubInfo.aidl
new file mode 100644
index 0000000..a7936ac
--- /dev/null
+++ b/core/java/android/hardware/location/VendorHubInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.location;
+
+/** @hide */
+parcelable VendorHubInfo;
\ No newline at end of file
diff --git a/core/java/android/hardware/location/VendorHubInfo.java b/core/java/android/hardware/location/VendorHubInfo.java
new file mode 100644
index 0000000..26772b1
--- /dev/null
+++ b/core/java/android/hardware/location/VendorHubInfo.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.location;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.chre.flags.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelableHolder;
+
+/**
+ * Information about a VendorHub. VendorHub is similar to ContextHub, but it does not run the
+ * Context Hub Runtime Environment (or nano apps). It provides a unified endpoint messaging API
+ * through the ContextHub V4 HAL.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OFFLOAD_API)
+public final class VendorHubInfo implements Parcelable {
+    private final String mName;
+    private final int mVersion;
+    private final ParcelableHolder mExtendedInfo;
+
+    /** @hide */
+    public VendorHubInfo(android.hardware.contexthub.VendorHubInfo halHubInfo) {
+        mName = halHubInfo.name;
+        mVersion = halHubInfo.version;
+        mExtendedInfo = halHubInfo.extendedInfo;
+    }
+
+    private VendorHubInfo(Parcel in) {
+        mName = in.readString();
+        mVersion = in.readInt();
+        mExtendedInfo = ParcelableHolder.CREATOR.createFromParcel(in);
+    }
+
+    /** Get the hub name */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /** Get the hub version */
+    public int getVersion() {
+        return mVersion;
+    }
+
+    /** Parcel implementation details */
+    public int describeContents() {
+        return mExtendedInfo.describeContents();
+    }
+
+    /** Parcel implementation details */
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeString(mName);
+        out.writeInt(mVersion);
+        mExtendedInfo.writeToParcel(out, flags);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder();
+        out.append("VendorHub Name : ");
+        out.append(mName);
+        out.append(", Version : ");
+        out.append(mVersion);
+        return out.toString();
+    }
+
+    public static final @NonNull Creator<VendorHubInfo> CREATOR =
+            new Creator<>() {
+                public VendorHubInfo createFromParcel(Parcel in) {
+                    return new VendorHubInfo(in);
+                }
+
+                public VendorHubInfo[] newArray(int size) {
+                    return new VendorHubInfo[size];
+                }
+            };
+}
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index 1adefe5..1b2c575 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -18,13 +18,6 @@
 }
 
 flag {
-    name: "safe_mode_timeout_config"
-    namespace: "vcn"
-    description: "Feature flag for adjustable safe mode timeout"
-    bug: "317406085"
-}
-
-flag {
     name: "fix_config_garbage_collection"
     namespace: "vcn"
     description: "Handle race condition in subscription change"
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index c18fb0c..99e7d166 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -281,7 +281,7 @@
     }
 
     /** {@hide} */
-    public void setIsIntentExtra() {
+    public void enableTokenVerification() {
         mFlags |= FLAG_VERIFY_TOKENS_PRESENT;
     }
 
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index acda0c5..69bd668 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -55,7 +55,7 @@
  * {@link Looper#myQueue() Looper.myQueue()}.
  */
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
 public final class MessageQueue {
     private static final String TAG_L = "LegacyMessageQueue";
     private static final String TAG_C = "ConcurrentMessageQueue";
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index 9db88d1..c2a47d7 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -54,7 +54,7 @@
  * {@link Looper#myQueue() Looper.myQueue()}.
  */
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
 public final class MessageQueue {
     private static final String TAG = "ConcurrentMessageQueue";
     private static final boolean DEBUG = false;
diff --git a/core/java/android/os/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl
index 6aa9852..ecb5e6f 100644
--- a/core/java/android/os/IVibratorManagerService.aidl
+++ b/core/java/android/os/IVibratorManagerService.aidl
@@ -17,13 +17,17 @@
 package android.os;
 
 import android.os.CombinedVibration;
+import android.os.ICancellationSignal;
 import android.os.IVibratorStateListener;
 import android.os.VibrationAttributes;
 import android.os.VibratorInfo;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
 
 /** {@hide} */
 interface IVibratorManagerService {
     int[] getVibratorIds();
+    int getCapabilities();
     VibratorInfo getVibratorInfo(int vibratorId);
     @EnforcePermission("ACCESS_VIBRATOR_STATE")
     boolean isVibrating(int vibratorId);
@@ -50,4 +54,9 @@
     oneway void performHapticFeedbackForInputDevice(int uid, int deviceId, String opPkg,
             int constant, int inputDeviceId, int inputSource, String reason, int flags,
             int privFlags);
+
+    @EnforcePermission(allOf={"VIBRATE", "VIBRATE_VENDOR_EFFECTS", "START_VIBRATION_SESSIONS"})
+    ICancellationSignal startVendorVibrationSession(int uid, int deviceId, String opPkg,
+            in int[] vibratorIds, in VibrationAttributes attributes, String reason,
+            in IVibrationSessionCallback callback);
 }
diff --git a/core/java/android/os/LegacyMessageQueue/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
index 9f7b0b7..cae82d0 100644
--- a/core/java/android/os/LegacyMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -45,7 +45,7 @@
  * {@link Looper#myQueue() Looper.myQueue()}.
  */
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
 public final class MessageQueue {
     private static final String TAG = "MessageQueue";
     private static final boolean DEBUG = false;
diff --git a/core/java/android/os/LockedMessageQueue/MessageQueue.java b/core/java/android/os/LockedMessageQueue/MessageQueue.java
index f3eec13..2401f3d 100644
--- a/core/java/android/os/LockedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/LockedMessageQueue/MessageQueue.java
@@ -48,7 +48,7 @@
  * {@link Looper#myQueue() Looper.myQueue()}.
  */
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
 public final class MessageQueue {
     private static final String TAG = "LockedMessageQueue";
     private static final boolean DEBUG = false;
diff --git a/ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java b/core/java/android/os/MessageQueue_ravenwood.java
similarity index 87%
rename from ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java
rename to core/java/android/os/MessageQueue_ravenwood.java
index 1b63adc..4033707 100644
--- a/ravenwood/runtime-helper-src/framework/android/os/MessageQueue_host.java
+++ b/core/java/android/os/MessageQueue_ravenwood.java
@@ -16,13 +16,16 @@
 
 package android.os;
 
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
+
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicLong;
 
-public class MessageQueue_host {
+@RavenwoodKeepWholeClass
+class MessageQueue_ravenwood {
     private static final AtomicLong sNextId = new AtomicLong(1);
-    private static final Map<Long, MessageQueue_host> sInstances = new ConcurrentHashMap<>();
+    private static final Map<Long, MessageQueue_ravenwood> sInstances = new ConcurrentHashMap<>();
 
     private boolean mDeleted = false;
 
@@ -37,8 +40,8 @@
         }
     }
 
-    private static MessageQueue_host getInstance(long id) {
-        MessageQueue_host q = sInstances.get(id);
+    private static MessageQueue_ravenwood getInstance(long id) {
+        MessageQueue_ravenwood q = sInstances.get(id);
         if (q == null) {
             throw new RuntimeException("MessageQueue doesn't exist with id=" + id);
         }
@@ -48,7 +51,7 @@
 
     public static long nativeInit() {
         final long id = sNextId.getAndIncrement();
-        final MessageQueue_host q = new MessageQueue_host();
+        final MessageQueue_ravenwood q = new MessageQueue_ravenwood();
         sInstances.put(id, q);
         return id;
     }
diff --git a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
index db323dc..435c34f 100644
--- a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
@@ -52,7 +52,7 @@
  * {@link Looper#myQueue() Looper.myQueue()}.
  */
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("MessageQueue_host")
+@RavenwoodRedirectionClass("MessageQueue_ravenwood")
 public final class MessageQueue {
     private static final String TAG = "SemiConcurrentMessageQueue";
     private static final boolean DEBUG = false;
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 011a3ee..c3cddf3 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -18,8 +18,11 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
+import android.hardware.vibrator.IVibratorManager;
+import android.os.vibrator.VendorVibrationSession;
 import android.os.vibrator.VibratorInfoFactory;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -53,6 +56,7 @@
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private VibratorInfo mVibratorInfo;
+    private int[] mVibratorIds;
 
     @UnsupportedAppUsage
     public SystemVibrator(Context context) {
@@ -71,7 +75,11 @@
                 Log.w(TAG, "Failed to retrieve vibrator info; no vibrator manager.");
                 return VibratorInfo.EMPTY_VIBRATOR_INFO;
             }
-            int[] vibratorIds = mVibratorManager.getVibratorIds();
+            int[] vibratorIds = getVibratorIds();
+            if (vibratorIds == null) {
+                Log.w(TAG, "Failed to retrieve vibrator info; error retrieving vibrator ids.");
+                return VibratorInfo.EMPTY_VIBRATOR_INFO;
+            }
             if (vibratorIds.length == 0) {
                 // It is known that the device has no vibrator, so cache and return info that
                 // reflects the lack of support for effects/primitives.
@@ -95,20 +103,22 @@
 
     @Override
     public boolean hasVibrator() {
-        if (mVibratorManager == null) {
+        int[] vibratorIds = getVibratorIds();
+        if (vibratorIds == null) {
             Log.w(TAG, "Failed to check if vibrator exists; no vibrator manager.");
             return false;
         }
-        return mVibratorManager.getVibratorIds().length > 0;
+        return vibratorIds.length > 0;
     }
 
     @Override
     public boolean isVibrating() {
-        if (mVibratorManager == null) {
+        int[] vibratorIds = getVibratorIds();
+        if (vibratorIds == null) {
             Log.w(TAG, "Failed to vibrate; no vibrator manager.");
             return false;
         }
-        for (int vibratorId : mVibratorManager.getVibratorIds()) {
+        for (int vibratorId : vibratorIds) {
             if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
                 return true;
             }
@@ -136,6 +146,11 @@
             Log.w(TAG, "Failed to add vibrate state listener; no vibrator manager.");
             return;
         }
+        int[] vibratorIds = getVibratorIds();
+        if (vibratorIds == null) {
+            Log.w(TAG, "Failed to add vibrate state listener; error retrieving vibrator ids.");
+            return;
+        }
         MultiVibratorStateListener delegate = null;
         try {
             synchronized (mRegisteredListeners) {
@@ -145,7 +160,7 @@
                     return;
                 }
                 delegate = new MultiVibratorStateListener(executor, listener);
-                delegate.register(mVibratorManager);
+                delegate.register(mVibratorManager, vibratorIds);
                 mRegisteredListeners.put(listener, delegate);
                 delegate = null;
             }
@@ -184,6 +199,11 @@
     }
 
     @Override
+    public boolean areVendorSessionsSupported() {
+        return mVibratorManager.hasCapabilities(IVibratorManager.CAP_START_SESSIONS);
+    }
+
+    @Override
     public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
             VibrationAttributes attrs) {
         if (mVibratorManager == null) {
@@ -243,6 +263,41 @@
         mVibratorManager.cancel(usageFilter);
     }
 
+    @Override
+    public void startVendorSession(@NonNull VibrationAttributes attrs, @Nullable String reason,
+            @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor,
+            @NonNull VendorVibrationSession.Callback callback) {
+        if (mVibratorManager == null) {
+            Log.w(TAG, "Failed to start vibration session; no vibrator manager.");
+            executor.execute(
+                    () -> callback.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR));
+            return;
+        }
+        int[] vibratorIds = getVibratorIds();
+        if (vibratorIds == null) {
+            Log.w(TAG, "Failed to start vibration session; error retrieving vibrator ids.");
+            executor.execute(
+                    () -> callback.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR));
+            return;
+        }
+        mVibratorManager.startVendorSession(vibratorIds, attrs, reason, cancellationSignal,
+                executor, callback);
+    }
+
+    @Nullable
+    private int[] getVibratorIds() {
+        synchronized (mLock) {
+            if (mVibratorIds != null) {
+                return mVibratorIds;
+            }
+            if (mVibratorManager == null) {
+                Log.w(TAG, "Failed to retrieve vibrator ids; no vibrator manager.");
+                return null;
+            }
+            return mVibratorIds = mVibratorManager.getVibratorIds();
+        }
+    }
+
     /**
      * Tries to unregister individual {@link android.os.Vibrator.OnVibratorStateChangedListener}
      * that were left registered to vibrators after failures to register them to all vibrators.
@@ -319,8 +374,7 @@
         }
 
         /** Registers a listener to all individual vibrators in {@link VibratorManager}. */
-        public void register(VibratorManager vibratorManager) {
-            int[] vibratorIds = vibratorManager.getVibratorIds();
+        public void register(VibratorManager vibratorManager, @NonNull int[] vibratorIds) {
             synchronized (mLock) {
                 for (int i = 0; i < vibratorIds.length; i++) {
                     int vibratorId = vibratorIds[i];
diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java
index a5697fb..f9935d2 100644
--- a/core/java/android/os/SystemVibratorManager.java
+++ b/core/java/android/os/SystemVibratorManager.java
@@ -22,6 +22,10 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.vibrator.IVibratorManager;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
+import android.os.vibrator.VendorVibrationSession;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
@@ -47,6 +51,8 @@
     @GuardedBy("mLock")
     private int[] mVibratorIds;
     @GuardedBy("mLock")
+    private int mCapabilities;
+    @GuardedBy("mLock")
     private final SparseArray<Vibrator> mVibrators = new SparseArray<>();
 
     @GuardedBy("mLock")
@@ -84,6 +90,11 @@
         }
     }
 
+    @Override
+    public boolean hasCapabilities(int capabilities) {
+        return (getCapabilities() & capabilities) == capabilities;
+    }
+
     @NonNull
     @Override
     public Vibrator getVibrator(int vibratorId) {
@@ -173,7 +184,7 @@
             int inputSource, String reason, int flags, int privFlags) {
         if (mService == null) {
             Log.w(TAG, "Failed to perform haptic feedback for input device;"
-                            + " no vibrator manager service.");
+                    + " no vibrator manager service.");
             return;
         }
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "performHapticFeedbackForInputDevice");
@@ -197,6 +208,50 @@
         cancelVibration(usageFilter);
     }
 
+    @Override
+    public void startVendorSession(@NonNull int[] vibratorIds, @NonNull VibrationAttributes attrs,
+            @Nullable String reason, @Nullable CancellationSignal cancellationSignal,
+            @NonNull Executor executor, @NonNull VendorVibrationSession.Callback callback) {
+        Objects.requireNonNull(vibratorIds);
+        VendorVibrationSessionCallbackDelegate callbackDelegate =
+                new VendorVibrationSessionCallbackDelegate(executor, callback);
+        if (mService == null) {
+            Log.w(TAG, "Failed to start vibration session; no vibrator manager service.");
+            callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR);
+            return;
+        }
+        try {
+            ICancellationSignal remoteCancellationSignal = mService.startVendorVibrationSession(
+                    mUid, mContext.getDeviceId(), mPackageName, vibratorIds, attrs, reason,
+                    callbackDelegate);
+            if (cancellationSignal != null) {
+                cancellationSignal.setRemote(remoteCancellationSignal);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to start vibration session.", e);
+            callbackDelegate.onFinished(VendorVibrationSession.STATUS_UNKNOWN_ERROR);
+        }
+    }
+
+    private int getCapabilities() {
+        synchronized (mLock) {
+            if (mCapabilities != 0) {
+                return mCapabilities;
+            }
+            try {
+                if (mService == null) {
+                    Log.w(TAG, "Failed to retrieve vibrator manager capabilities;"
+                            + " no vibrator manager service.");
+                } else {
+                    return mCapabilities = mService.getCapabilities();
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            return 0;
+        }
+    }
+
     private void cancelVibration(int usageFilter) {
         if (mService == null) {
             Log.w(TAG, "Failed to cancel vibration; no vibrator manager service.");
@@ -228,12 +283,45 @@
         }
     }
 
+    /** Callback for vendor vibration sessions. */
+    private static class VendorVibrationSessionCallbackDelegate extends
+            IVibrationSessionCallback.Stub {
+        private final Executor mExecutor;
+        private final VendorVibrationSession.Callback mCallback;
+
+        VendorVibrationSessionCallbackDelegate(
+                @NonNull Executor executor,
+                @NonNull VendorVibrationSession.Callback callback) {
+            Objects.requireNonNull(executor);
+            Objects.requireNonNull(callback);
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onStarted(IVibrationSession session) {
+            mExecutor.execute(() -> mCallback.onStarted(new VendorVibrationSession(session)));
+        }
+
+        @Override
+        public void onFinishing() {
+            mExecutor.execute(() -> mCallback.onFinishing());
+        }
+
+        @Override
+        public void onFinished(int status) {
+            mExecutor.execute(() -> mCallback.onFinished(status));
+        }
+    }
+
     /** Controls vibrations on a single vibrator. */
     private final class SingleVibrator extends Vibrator {
         private final VibratorInfo mVibratorInfo;
+        private final int[] mVibratorId;
 
         SingleVibrator(@NonNull VibratorInfo vibratorInfo) {
             mVibratorInfo = vibratorInfo;
+            mVibratorId = new int[]{mVibratorInfo.getId()};
         }
 
         @Override
@@ -252,6 +340,11 @@
         }
 
         @Override
+        public boolean areVendorSessionsSupported() {
+            return SystemVibratorManager.this.hasCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        }
+
+        @Override
         public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
                 @Nullable VibrationEffect effect, @Nullable VibrationAttributes attrs) {
             CombinedVibration combined = CombinedVibration.startParallel()
@@ -369,5 +462,13 @@
                 }
             }
         }
+
+        @Override
+        public void startVendorSession(@NonNull VibrationAttributes attrs, String reason,
+                @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor,
+                @NonNull VendorVibrationSession.Callback callback) {
+            SystemVibratorManager.this.startVendorSession(mVibratorId, attrs, reason,
+                    cancellationSignal, executor, callback);
+        }
     }
 }
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index c4c4580..53f8a92 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -33,6 +33,7 @@
 import android.hardware.vibrator.IVibrator;
 import android.media.AudioAttributes;
 import android.os.vibrator.Flags;
+import android.os.vibrator.VendorVibrationSession;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibratorFrequencyProfile;
 import android.os.vibrator.VibratorFrequencyProfileLegacy;
@@ -247,6 +248,34 @@
     }
 
     /**
+     * Check whether the vibrator has support for vendor-specific effects.
+     *
+     * <p>Vendor vibration effects can be created via {@link VibrationEffect#createVendorEffect}.
+     *
+     * @return True if the hardware can play vendor-specific vibration effects, false otherwise.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public boolean areVendorEffectsSupported() {
+        return getInfo().hasCapability(IVibrator.CAP_PERFORM_VENDOR_EFFECTS);
+    }
+
+    /**
+     * Check whether the vibrator has support for vendor-specific vibration sessions.
+     *
+     * <p>Vendor vibration sessions can be started via {@link #startVendorSession}.
+     *
+     * @return True if the hardware can play vendor-specific vibration sessions, false otherwise.
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public boolean areVendorSessionsSupported() {
+        return false;
+    }
+
+    /**
      * Check whether the vibrator can be controlled by an external service with the
      * {@link IExternalVibratorService}.
      *
@@ -922,4 +951,44 @@
     @RequiresPermission(android.Manifest.permission.ACCESS_VIBRATOR_STATE)
     public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) {
     }
+
+    /**
+     * Starts a vibration session in this vibrator.
+     *
+     * <p>The session will start asynchronously once the vibrator control can be acquired. Once it's
+     * started the {@link VendorVibrationSession} will be provided to the callback. This session
+     * should be used to play vibrations until the session is ended or canceled.
+     *
+     * <p>The vendor app will have exclusive control over the vibrator during this session. This
+     * control can be revoked by the vibrator service, which will be notified to the same session
+     * callback with the {@link VendorVibrationSession#STATUS_CANCELED}.
+     *
+     * <p>The {@link VibrationAttributes} will be used to decide the priority of the vendor
+     * vibrations that will be performed in this session. All vibrations within this session will
+     * apply the same attributes.
+     *
+     * @param attrs    The {@link VibrationAttributes} corresponding to the vibrations that will be
+     *                 performed in the session. This will be used to decide the priority of this
+     *                 session against other system vibrations.
+     * @param reason   The description for this session, used for debugging purposes.
+     * @param cancellationSignal A signal to cancel the session before it starts.
+     * @param executor The executor for the session callbacks.
+     * @param callback The {@link VendorVibrationSession.Callback} for the started session.
+     *
+     * @see VendorVibrationSession
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.VIBRATE,
+            android.Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+            android.Manifest.permission.START_VIBRATION_SESSIONS,
+    })
+    public void startVendorSession(@NonNull VibrationAttributes attrs, @Nullable String reason,
+            @Nullable CancellationSignal cancellationSignal, @NonNull Executor executor,
+            @NonNull VendorVibrationSession.Callback callback) {
+        Log.w(TAG, "startVendorSession is not supported");
+        executor.execute(() -> callback.onFinished(VendorVibrationSession.STATUS_UNSUPPORTED));
+    }
 }
diff --git a/core/java/android/os/VibratorManager.java b/core/java/android/os/VibratorManager.java
index 0428876..0072bc2 100644
--- a/core/java/android/os/VibratorManager.java
+++ b/core/java/android/os/VibratorManager.java
@@ -22,9 +22,12 @@
 import android.annotation.SystemService;
 import android.app.ActivityThread;
 import android.content.Context;
+import android.os.vibrator.VendorVibrationSession;
 import android.util.Log;
 import android.view.HapticFeedbackConstants;
 
+import java.util.concurrent.Executor;
+
 /**
  * Provides access to all vibrators from the device, as well as the ability to run them
  * in a synchronized fashion.
@@ -62,6 +65,14 @@
     public abstract int[] getVibratorIds();
 
     /**
+     * Return true if the vibrator manager has all capabilities, false otherwise.
+     * @hide
+     */
+    public boolean hasCapabilities(int capabilities) {
+        return false;
+    }
+
+    /**
      * Retrieve a single vibrator by id.
      *
      * @param vibratorId The id of the vibrator to be retrieved.
@@ -190,4 +201,30 @@
      */
     @RequiresPermission(android.Manifest.permission.VIBRATE)
     public abstract void cancel(int usageFilter);
+
+
+    /**
+     * Starts a vibration session on given vibrators.
+     *
+     * @param vibratorIds The vibrators that will be controlled by this session.
+     * @param attrs       The {@link VibrationAttributes} corresponding to the vibrations that will
+     *                    be performed in the session. This will be used to decide the priority of
+     *                    this session against other system vibrations.
+     * @param reason      The description for this session, used for debugging purposes.
+     * @param cancellationSignal A signal to cancel the session before it starts.
+     * @param executor    The executor for the session callbacks.
+     * @param callback    The {@link VendorVibrationSession.Callback} for the started session.
+     * @see Vibrator#startVendorSession
+     * @hide
+     */
+    @RequiresPermission(allOf = {
+            android.Manifest.permission.VIBRATE,
+            android.Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+            android.Manifest.permission.START_VIBRATION_SESSIONS,
+    })
+    public void startVendorSession(@NonNull int[] vibratorIds, @NonNull VibrationAttributes attrs,
+            @Nullable String reason, @Nullable CancellationSignal cancellationSignal,
+            @NonNull Executor executor, @NonNull VendorVibrationSession.Callback callback) {
+        Log.w(TAG, "startVendorSession is not supported");
+    }
 }
diff --git a/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl
new file mode 100644
index 0000000..dbe5489
--- /dev/null
+++ b/core/java/android/os/instrumentation/ExecutableMethodFileOffsets.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.instrumentation;
+
+/**
+ * Represents the location of the code for a compiled method within a process'
+ * memory.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable ExecutableMethodFileOffsets {
+  /**
+   * The OS path of the containing file (could be virtual).
+   */
+  @utf8InCpp String containerPath;
+  /**
+   * The offset of the containing file within the process' memory.
+   */
+  long containerOffset;
+  /**
+   * The offset of the method within the containing file.
+   */
+  long methodOffset;
+}
diff --git a/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
new file mode 100644
index 0000000..c45c51d
--- /dev/null
+++ b/core/java/android/os/instrumentation/IDynamicInstrumentationManager.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.instrumentation;
+
+import android.os.instrumentation.ExecutableMethodFileOffsets;
+import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.TargetProcess;
+
+/**
+ * System private API for managing the dynamic attachment of instrumentation.
+ *
+ * {@hide}
+ */
+interface IDynamicInstrumentationManager {
+    /** Provides ART metadata about the described compiled method within the target process */
+    @PermissionManuallyEnforced
+    @nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets(
+            in TargetProcess targetProcess, in MethodDescriptor methodDescriptor);
+}
diff --git a/core/java/android/os/instrumentation/MethodDescriptor.aidl b/core/java/android/os/instrumentation/MethodDescriptor.aidl
new file mode 100644
index 0000000..055d0ec
--- /dev/null
+++ b/core/java/android/os/instrumentation/MethodDescriptor.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.instrumentation;
+
+/**
+ * Represents a JVM method, where class fields that make up its signature.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable MethodDescriptor {
+  /**
+    * Fully qualified class in reverse.domain.Naming
+    */
+  @utf8InCpp String fullyQualifiedClassName;
+  /**
+    * Name of the method.
+    */
+  @utf8InCpp String methodName;
+  /**
+    * Fully qualified types of method parameters, or string representations if primitive e.g. "int".
+    */
+  @utf8InCpp String[] fullyQualifiedParameters;
+}
diff --git a/core/java/android/os/instrumentation/TargetProcess.aidl b/core/java/android/os/instrumentation/TargetProcess.aidl
new file mode 100644
index 0000000..e90780d
--- /dev/null
+++ b/core/java/android/os/instrumentation/TargetProcess.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.instrumentation;
+
+/**
+ * Addresses a process that would run on the device.
+ * Helps disambiguate targeted processes in cases of pid re-use.
+ * {@hide}
+ */
+@JavaDerive(toString=true)
+parcelable TargetProcess {
+  int uid;
+  int pid;
+  @utf8InCpp String processName;
+}
diff --git a/core/java/android/os/vibrator/IVibrationSession.aidl b/core/java/android/os/vibrator/IVibrationSession.aidl
new file mode 100644
index 0000000..e829549
--- /dev/null
+++ b/core/java/android/os/vibrator/IVibrationSession.aidl
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.vibrator;
+
+import android.os.CombinedVibration;
+
+/**
+ * The communication channel by which an app control the system vibrators.
+ *
+ * In order to synchronize the places where vibrations might be controlled we provide this interface
+ * so the vibrator subsystem has a chance to:
+ *
+ * 1) Decide whether the current session should have the vibrator control.
+ * 2) Stop any on-going session for a new session/vibration, based on current system policy.
+ * {@hide}
+ */
+interface IVibrationSession {
+    const int STATUS_UNKNOWN = 0;
+    const int STATUS_SUCCESS = 1;
+    const int STATUS_IGNORED = 2;
+    const int STATUS_UNSUPPORTED = 3;
+    const int STATUS_CANCELED = 4;
+    const int STATUS_UNKNOWN_ERROR = 5;
+
+    /**
+     * A method called to start a vibration within this session. This will fail if the session
+     * is finishing or was canceled.
+     */
+    void vibrate(in CombinedVibration vibration, String reason);
+
+    /**
+     * A method called by the app to stop this session gracefully. The vibrator will complete any
+     * ongoing vibration before the session is ended.
+     */
+    void finishSession();
+
+    /**
+     * A method called by the app to stop this session immediatelly by interrupting any ongoing
+     * vibration.
+     */
+    void cancelSession();
+}
diff --git a/core/java/android/os/vibrator/IVibrationSessionCallback.aidl b/core/java/android/os/vibrator/IVibrationSessionCallback.aidl
new file mode 100644
index 0000000..36c3695
--- /dev/null
+++ b/core/java/android/os/vibrator/IVibrationSessionCallback.aidl
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.vibrator;
+
+import android.os.vibrator.IVibrationSession;
+
+/**
+ * Callback for vibration session state.
+ * {@hide}
+ */
+oneway interface IVibrationSessionCallback {
+
+    /**
+     * A method called by the service after a vibration session has successfully started. After this
+     * is called the app has control over the vibrator through this given session.
+     */
+    void onStarted(in IVibrationSession session);
+
+    /**
+     * A method called by the service to indicate the session is ending and should no longer receive
+     * vibration requests.
+     */
+    void onFinishing();
+
+    /**
+     * A method called by the service after the session has ended. This might be triggered by the
+     * app or the service. The status code indicates the end reason.
+     */
+    void onFinished(int status);
+}
diff --git a/core/java/android/os/vibrator/VendorVibrationSession.java b/core/java/android/os/vibrator/VendorVibrationSession.java
new file mode 100644
index 0000000..c23f2ed
--- /dev/null
+++ b/core/java/android/os/vibrator/VendorVibrationSession.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.vibrator;
+
+import static android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.CombinedVibration;
+import android.os.RemoteException;
+import android.os.VibrationEffect;
+import android.os.Vibrator;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A vendor session that temporarily gains control over the system vibrators.
+ *
+ * <p>Vibration effects can be played by the vibrator in a vendor session via {@link #vibrate}. The
+ * effects will be forwarded to the vibrator hardware immediately. Any concurrency support is
+ * defined and controlled by the vibrator hardware implementation.
+ *
+ * <p>The session should be ended by {@link #close()}, which will wait until the last vibration ends
+ * and the vibrator is released. The end of the session will be notified to the {@link Callback}
+ * provided when the session was created.
+ *
+ * <p>Any ongoing session can be immediately interrupted by the vendor app via {@link #cancel()},
+ * including after {@link #close()} was called and the session is tearing down. A session can also
+ * be canceled by the vibrator service when it needs to regain control of the system vibrators.
+ *
+ * @see Vibrator#startVendorSession
+ * @hide
+ */
+@FlaggedApi(FLAG_VENDOR_VIBRATION_EFFECTS)
+@SystemApi
+public final class VendorVibrationSession implements AutoCloseable {
+    private static final String TAG = "VendorVibrationSession";
+
+    /**
+     * The session ended successfully.
+     */
+    public static final int STATUS_SUCCESS = IVibrationSession.STATUS_SUCCESS;
+
+    /**
+     * The session was ignored.
+     *
+     * <p>This might be caused by user settings, vibration policies or the device state that
+     * prevents the app from performing vibrations for the requested
+     * {@link android.os.VibrationAttributes}.
+     */
+    public static final int STATUS_IGNORED = IVibrationSession.STATUS_IGNORED;
+
+    /**
+     * The session is not supported.
+     *
+     * <p>The support for vendor vibration sessions can be checked via
+     * {@link Vibrator#areVendorSessionsSupported()}.
+     */
+    public static final int STATUS_UNSUPPORTED = IVibrationSession.STATUS_UNSUPPORTED;
+
+    /**
+     * The session was canceled.
+     *
+     * <p>This might be triggered by the app after a session starts via {@link #cancel()}, or it
+     * can be triggered by the platform before or after the session has started.
+     */
+    public static final int STATUS_CANCELED = IVibrationSession.STATUS_CANCELED;
+
+    /**
+     * The session status is unknown.
+     */
+    public static final int STATUS_UNKNOWN = IVibrationSession.STATUS_UNKNOWN;
+
+    /**
+     * The session failed with unknown error.
+     *
+     * <p>This can be caused by a failure to start a vibration session or after it has started, to
+     * indicate it has ended unexpectedly because of a system failure.
+     */
+    public static final int STATUS_UNKNOWN_ERROR = IVibrationSession.STATUS_UNKNOWN_ERROR;
+
+    /** @hide */
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_SUCCESS,
+            STATUS_IGNORED,
+            STATUS_UNSUPPORTED,
+            STATUS_CANCELED,
+            STATUS_UNKNOWN,
+            STATUS_UNKNOWN_ERROR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Status{}
+
+    private final IVibrationSession mSession;
+
+    /** @hide */
+    public VendorVibrationSession(@NonNull IVibrationSession session) {
+        Objects.requireNonNull(session);
+        mSession = session;
+    }
+
+    /**
+     * Vibrate with a given effect.
+     *
+     * <p>The vibration will be sent to the vibrator hardware immediately, without waiting for any
+     * previous vibration completion. The vendor should control the concurrency behavior at the
+     * hardware level (e.g. queueing, mixing, interrupting).
+     *
+     * <p>If the provided effect is played by the vibrator service with controlled timings (e.g.
+     * effects created via {@link VibrationEffect#createWaveform}), then triggering a new vibration
+     * will cause the ongoing playback to be interrupted in favor of the new vibration. If the
+     * effect is broken down into multiple consecutive commands (e.g. large primitive compositions)
+     * then the hardware commands will be triggered in succession without waiting for the completion
+     * callback.
+     *
+     * <p>The vendor app is responsible for timing the session requests and the vibrator hardware
+     * implementation is free to handle concurrency with different policies.
+     *
+     * @param effect The {@link VibrationEffect} describing the vibration to be performed.
+     * @param reason The description for the vibration reason, for debugging purposes.
+     */
+    @RequiresPermission(android.Manifest.permission.VIBRATE)
+    public void vibrate(@NonNull VibrationEffect effect, @Nullable String reason) {
+        try {
+            mSession.vibrate(CombinedVibration.createParallel(effect), reason);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to vibrate in a vendor vibration session.", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Cancel ongoing session.
+     *
+     * <p>This will stop the vibration immediately and return the vibrator control to the
+     * platform. This can also be triggered after {@link #close()} to immediately release the
+     * vibrator.
+     *
+     * <p>This will trigger {@link VendorVibrationSession.Callback#onFinished} directly with
+     * {@link #STATUS_CANCELED}.
+     */
+    public void cancel() {
+        try {
+            mSession.cancelSession();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to cancel vendor vibration session.", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * End ongoing session gracefully.
+     *
+     * <p>This might continue the vibration while it's ramping down and wrapping up the session
+     * in the vibrator hardware. No more vibration commands can be sent through this session
+     * after this method is called.
+     *
+     * <p>This will trigger {@link VendorVibrationSession.Callback#onFinishing()}.
+     */
+    @Override
+    public void close() {
+        try {
+            mSession.finishSession();
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to finish vendor vibration session.", e);
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Callbacks for {@link VendorVibrationSession} events.
+     *
+     * @see Vibrator#startVendorSession
+     * @see VendorVibrationSession
+     */
+    public interface Callback {
+
+        /**
+         * New session was successfully started.
+         *
+         * <p>The vendor app can interact with the vibrator using the
+         * {@link VendorVibrationSession} provided.
+         */
+        void onStarted(@NonNull VendorVibrationSession session);
+
+        /**
+         * The session is ending and finishing any pending vibrations.
+         *
+         * <p>This is only invoked after {@link #onStarted(VendorVibrationSession)}. It will be
+         * triggered by both {@link VendorVibrationSession#cancel()} and
+         * {@link VendorVibrationSession#close()}. This might also be triggered if the platform
+         * cancels the ongoing session.
+         *
+         * <p>Session vibrations might be still ongoing in the vibrator hardware but the app can
+         * no longer send commands through the session. A finishing session can still be immediately
+         * stopped via calls to {@link VendorVibrationSession.Callback#cancel()}.
+         */
+        void onFinishing();
+
+        /**
+         * The session is finished.
+         *
+         * <p>The vibrator has finished any vibration and returned to the platform's control. This
+         * might be triggered by the vendor app or by the vibrator service.
+         *
+         * <p>If this is triggered before {@link #onStarted} then the session was finished before
+         * starting, either because it was cancelled or failed to start. If the session has already
+         * started then this will be triggered after {@link #onFinishing()} to indicate all session
+         * vibrations are complete and the vibrator is no longer under the session's control.
+         *
+         * @param status The session status.
+         */
+        void onFinished(@VendorVibrationSession.Status int status);
+    }
+}
diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig
index 72f2de8..dfc11dc 100644
--- a/core/java/android/service/dreams/flags.aconfig
+++ b/core/java/android/service/dreams/flags.aconfig
@@ -67,3 +67,13 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "cleanup_dream_settings_on_uninstall"
+    namespace: "systemui"
+    description: "Cleans up dream settings if dream package is uninstalled."
+    bug: "338210427"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/service/settings/preferences/GetValueRequest.aidl b/core/java/android/service/settings/preferences/GetValueRequest.aidl
new file mode 100644
index 0000000..2a0eb09
--- /dev/null
+++ b/core/java/android/service/settings/preferences/GetValueRequest.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable GetValueRequest;
\ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/GetValueRequest.java b/core/java/android/service/settings/preferences/GetValueRequest.java
new file mode 100644
index 0000000..4f82800
--- /dev/null
+++ b/core/java/android/service/settings/preferences/GetValueRequest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * Request parameters to retrieve the current value of a Settings Preference.
+ *
+ * <p>This object passed to {@link SettingsPreferenceService#onGetPreferenceValue} will result
+ * in a {@link GetValueResult}.
+ *
+ * <ul>
+ *   <li>{@link #getScreenKey} is a parameter to distinguish the container screen
+ *   of a preference as a preference key may not be unique within its application.
+ *   <li>{@link #getPreferenceKey} is a parameter to identify the preference for which the value is
+ *   being requested. These keys will be unique with their Preference Screen, but may not be unique
+ *   within their application, so it is required to pair this with {@link #getScreenKey} to
+ *   ensure this request matches the intended target.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class GetValueRequest implements Parcelable {
+
+    @NonNull
+    private final String mScreenKey;
+    @NonNull
+    private final String mPreferenceKey;
+
+    /**
+     * Returns the screen key of requested Preference.
+     */
+    @NonNull
+    public String getScreenKey() {
+        return mScreenKey;
+    }
+
+    /**
+     * Returns the key of requested Preference.
+     */
+    @NonNull
+    public String getPreferenceKey() {
+        return mPreferenceKey;
+    }
+
+    private GetValueRequest(@NonNull Builder builder) {
+        mScreenKey = builder.mScreenKey;
+        mPreferenceKey = builder.mPreferenceKey;
+    }
+
+    private GetValueRequest(@NonNull Parcel in) {
+        mScreenKey = Objects.requireNonNull(in.readString8());
+        mPreferenceKey = Objects.requireNonNull(in.readString8());
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mScreenKey);
+        dest.writeString8(mPreferenceKey);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Parcelable Creator for {@link GetValueRequest}.
+     */
+    @NonNull
+    public static final Creator<GetValueRequest> CREATOR = new Creator<GetValueRequest>() {
+        @Override
+        public GetValueRequest createFromParcel(@NonNull Parcel in) {
+            return new GetValueRequest(in);
+        }
+
+        @Override
+        public GetValueRequest[] newArray(int size) {
+            return new GetValueRequest[size];
+        }
+    };
+
+    /**
+     * Builder to construct {@link GetValueRequest}.
+     */
+    public static final class Builder {
+        private final String mScreenKey;
+        private final String mPreferenceKey;
+
+        /**
+         * Create Builder instance.
+         * @param screenKey required to be not empty
+         * @param preferenceKey required to be not empty
+         */
+        public Builder(@NonNull String screenKey, @NonNull String preferenceKey) {
+            if (TextUtils.isEmpty(screenKey)) {
+                throw new IllegalArgumentException("screenKey cannot be empty");
+            }
+            if (TextUtils.isEmpty(preferenceKey)) {
+                throw new IllegalArgumentException("preferenceKey cannot be empty");
+            }
+            mScreenKey = screenKey;
+            mPreferenceKey = preferenceKey;
+        }
+
+        /**
+         * Constructs an immutable {@link GetValueRequest} object.
+         */
+        @NonNull
+        public GetValueRequest build() {
+            return new GetValueRequest(this);
+        }
+    }
+}
diff --git a/core/java/android/service/settings/preferences/GetValueResult.aidl b/core/java/android/service/settings/preferences/GetValueResult.aidl
new file mode 100644
index 0000000..b5ebd35
--- /dev/null
+++ b/core/java/android/service/settings/preferences/GetValueResult.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable GetValueResult;
\ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/GetValueResult.java b/core/java/android/service/settings/preferences/GetValueResult.java
new file mode 100644
index 0000000..369dea7
--- /dev/null
+++ b/core/java/android/service/settings/preferences/GetValueResult.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result object given a corresponding {@link GetValueRequest}.
+ * <ul>
+ *   <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK},
+ *   {@link #getValue} will be populated with the settings preference value and
+ *   {@link #getMetadata} will be populated with its metadata.
+ *   <li>If the request is unsuccessful, {@link #getResultCode} be a value other than
+ *   {@link #RESULT_OK} - see documentation for those possibilities to understand the cause
+ *   of the failure.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class GetValueResult implements Parcelable {
+
+    @ResultCode
+    private final int mResultCode;
+    @Nullable
+    private final SettingsPreferenceValue mValue;
+    @Nullable
+    private final SettingsPreferenceMetadata mMetadata;
+
+    /**
+     * Returns the result code indicating status of the request.
+     */
+    @ResultCode
+    public int getResultCode() {
+        return mResultCode;
+    }
+
+    /**
+     * Returns the value of requested Preference if request successful.
+     */
+    @Nullable
+    public SettingsPreferenceValue getValue() {
+        return mValue;
+    }
+
+    /**
+     * Returns the metadata of requested Preference if request successful.
+     */
+    @Nullable
+    public SettingsPreferenceMetadata getMetadata() {
+        return mMetadata;
+    }
+
+    /** @hide */
+    @IntDef(prefix = { "RESULT_" }, value = {
+            RESULT_OK,
+            RESULT_UNSUPPORTED,
+            RESULT_UNAVAILABLE,
+            RESULT_REQUIRE_APP_PERMISSION,
+            RESULT_DISALLOW,
+            RESULT_INVALID_REQUEST,
+            RESULT_INTERNAL_ERROR,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultCode {
+    }
+
+    /** Request is successful. */
+    public static final int RESULT_OK = 0;
+    /**
+     * Requested preference is not supported by this API.
+     * <p>Retry not advised.
+     */
+    public static final int RESULT_UNSUPPORTED = 1;
+    /**
+     * Preference is currently not available, likely due to device state or the state of
+     * a dependency.
+     * <p>Retry may succeed if underlying conditions change.
+     */
+    public static final int RESULT_UNAVAILABLE = 2;
+    /**
+     * Requested preference requires permissions not held by the calling application.
+     * <p>Retry may succeed if necessary permissions are obtained.
+     */
+    public static final int RESULT_REQUIRE_APP_PERMISSION = 3;
+    /**
+     * Requested preference is not allowed for access in this API under the current device policy.
+     * <p>Retry may succeed if underlying conditions change.
+     */
+    public static final int RESULT_DISALLOW = 4;
+    /**
+     * Request object is not valid.
+     * <p>Retry not advised with current parameters.
+     */
+    public static final int RESULT_INVALID_REQUEST = 5;
+    /**
+     * API call failed due to an issue with the service binding.
+     * <p>Retry may succeed.
+     */
+    public static final int RESULT_INTERNAL_ERROR = 6;
+
+
+    private GetValueResult(@NonNull Builder builder) {
+        mResultCode = builder.mResultCode;
+        mValue = builder.mValue;
+        mMetadata = builder.mMetadata;
+    }
+
+    private GetValueResult(@NonNull Parcel in) {
+        mResultCode = in.readInt();
+        mValue = in.readParcelable(SettingsPreferenceValue.class.getClassLoader(),
+                SettingsPreferenceValue.class);
+        mMetadata = in.readParcelable(SettingsPreferenceMetadata.class.getClassLoader(),
+                SettingsPreferenceMetadata.class);
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mResultCode);
+        dest.writeParcelable(mValue, flags);
+        dest.writeParcelable(mMetadata, flags);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Parcelable Creator for {@link GetValueResult}.
+     */
+    @NonNull
+    public static final Creator<GetValueResult> CREATOR = new Creator<>() {
+        @Override
+        public GetValueResult createFromParcel(@NonNull Parcel in) {
+            return new GetValueResult(in);
+        }
+
+        @Override
+        public GetValueResult[] newArray(int size) {
+            return new GetValueResult[size];
+        }
+    };
+
+    /**
+     * Builder to construct {@link GetValueResult}.
+     */
+    public static final class Builder {
+        @ResultCode
+        private final int mResultCode;
+        private SettingsPreferenceValue mValue;
+        private SettingsPreferenceMetadata mMetadata;
+
+        /**
+         * Create Builder instance.
+         * @param resultCode indicates status of the request
+         */
+        public Builder(@ResultCode int resultCode) {
+            mResultCode = resultCode;
+        }
+
+        /**
+         * Sets the preference value on the result.
+         */
+        @NonNull
+        public Builder setValue(@Nullable SettingsPreferenceValue value) {
+            mValue = value;
+            return this;
+        }
+
+        /**
+         * Sets the metadata on the result.
+         */
+        @NonNull
+        public Builder setMetadata(@Nullable SettingsPreferenceMetadata metadata) {
+            mMetadata = metadata;
+            return this;
+        }
+
+        /**
+         * Constructs an immutable {@link GetValueResult} object.
+         */
+        @NonNull
+        public GetValueResult build() {
+            return new GetValueResult(this);
+        }
+    }
+}
diff --git a/core/java/android/service/settings/preferences/IGetValueCallback.aidl b/core/java/android/service/settings/preferences/IGetValueCallback.aidl
new file mode 100644
index 0000000..bbc7423
--- /dev/null
+++ b/core/java/android/service/settings/preferences/IGetValueCallback.aidl
@@ -0,0 +1,9 @@
+package android.service.settings.preferences;
+
+import android.service.settings.preferences.GetValueResult;
+
+/** @hide */
+oneway interface IGetValueCallback {
+    void onSuccess(in GetValueResult result) = 1;
+    void onFailure() = 2;
+}
diff --git a/core/java/android/service/settings/preferences/IMetadataCallback.aidl b/core/java/android/service/settings/preferences/IMetadataCallback.aidl
new file mode 100644
index 0000000..3bd5ebe
--- /dev/null
+++ b/core/java/android/service/settings/preferences/IMetadataCallback.aidl
@@ -0,0 +1,9 @@
+package android.service.settings.preferences;
+
+import android.service.settings.preferences.MetadataResult;
+
+/** @hide */
+oneway interface IMetadataCallback {
+    void onSuccess(in MetadataResult result);
+    void onFailure();
+}
diff --git a/core/java/android/service/settings/preferences/ISetValueCallback.aidl b/core/java/android/service/settings/preferences/ISetValueCallback.aidl
new file mode 100644
index 0000000..0765660
--- /dev/null
+++ b/core/java/android/service/settings/preferences/ISetValueCallback.aidl
@@ -0,0 +1,9 @@
+package android.service.settings.preferences;
+
+import android.service.settings.preferences.SetValueResult;
+
+/** @hide */
+oneway interface ISetValueCallback {
+    void onSuccess(in SetValueResult result);
+    void onFailure();
+}
diff --git a/core/java/android/service/settings/preferences/MetadataRequest.aidl b/core/java/android/service/settings/preferences/MetadataRequest.aidl
new file mode 100644
index 0000000..dc3cbc42
--- /dev/null
+++ b/core/java/android/service/settings/preferences/MetadataRequest.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable MetadataRequest;
\ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/MetadataRequest.java b/core/java/android/service/settings/preferences/MetadataRequest.java
new file mode 100644
index 0000000..ffecc6b
--- /dev/null
+++ b/core/java/android/service/settings/preferences/MetadataRequest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+/**
+ * Request parameters to retrieve all metadata for all available settings preferences within this
+ * application.
+ *
+ * <p>This object passed to {@link SettingsPreferenceService#onGetAllPreferenceMetadata} will result
+ * in a {@link MetadataResult}.
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class MetadataRequest implements Parcelable {
+    private MetadataRequest() {}
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Parcelable Creator for {@link MetadataRequest}.
+     */
+    @NonNull
+    public static final Creator<MetadataRequest> CREATOR = new Creator<>() {
+        @Override
+        public MetadataRequest createFromParcel(@NonNull Parcel in) {
+            return new MetadataRequest();
+        }
+
+        @Override
+        public MetadataRequest[] newArray(int size) {
+            return new MetadataRequest[size];
+        }
+    };
+
+    /**
+     * Builder to construct {@link MetadataRequest}.
+     */
+    public static final class Builder {
+        /** Constructs an immutable {@link MetadataRequest} object. */
+        @NonNull
+        public MetadataRequest build() {
+            return new MetadataRequest();
+        }
+    }
+}
diff --git a/core/java/android/service/settings/preferences/MetadataResult.aidl b/core/java/android/service/settings/preferences/MetadataResult.aidl
new file mode 100644
index 0000000..af9e8a8
--- /dev/null
+++ b/core/java/android/service/settings/preferences/MetadataResult.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable MetadataResult;
\ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/MetadataResult.java b/core/java/android/service/settings/preferences/MetadataResult.java
new file mode 100644
index 0000000..6a65dcc
--- /dev/null
+++ b/core/java/android/service/settings/preferences/MetadataResult.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Result object given a corresponding {@link MetadataRequest}.
+ * <ul>
+ *   <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK} and
+ *   {@link #getMetadataList} will be populated with metadata for all available preferences within
+ *   this application.
+ *   <li>If the request is unsuccessful, {@link #getResultCode} be a value other than
+ *   {@link #RESULT_OK} - see documentation for those possibilities to understand the cause
+ *   of the failure.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class MetadataResult implements Parcelable {
+
+    @ResultCode
+    private final int mResultCode;
+    @NonNull
+    private final List<SettingsPreferenceMetadata> mMetadataList;
+
+    /**
+     * Returns the result code indicating status of the request.
+     */
+    @ResultCode
+    public int getResultCode() {
+        return mResultCode;
+    }
+
+    /**
+     * Returns the list of available Preference Metadata.
+     * <p>This instance is shared so this list should not be modified.
+     */
+    @NonNull
+    public List<SettingsPreferenceMetadata> getMetadataList() {
+        return mMetadataList;
+    }
+
+    /** @hide */
+    @IntDef(prefix = { "RESULT_" }, value = {
+            RESULT_OK,
+            RESULT_UNSUPPORTED,
+            RESULT_INTERNAL_ERROR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultCode {
+    }
+
+    /** Request is successful. */
+    public static final int RESULT_OK = 0;
+    /**
+     * No preferences in this application support this API.
+     * <p>Retry not advised.
+     */
+    public static final int RESULT_UNSUPPORTED = 1;
+    /**
+     * API call failed due to an issue with the service binding.
+     * <p>Retry may succeed.
+     */
+    public static final int RESULT_INTERNAL_ERROR = 2;
+
+    private MetadataResult(@NonNull Builder builder) {
+        mResultCode = builder.mResultCode;
+        mMetadataList = builder.mMetadataList;
+    }
+    private MetadataResult(@NonNull Parcel in) {
+        mResultCode = in.readInt();
+        mMetadataList = new ArrayList<>();
+        in.readTypedList(mMetadataList, SettingsPreferenceMetadata.CREATOR);
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mResultCode);
+        dest.writeTypedList(mMetadataList, flags);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Parcelable Creator for {@link MetadataResult}.
+     */
+    @NonNull
+    public static final Creator<MetadataResult> CREATOR = new Creator<>() {
+        @Override
+        public MetadataResult createFromParcel(@NonNull Parcel in) {
+            return new MetadataResult(in);
+        }
+
+        @Override
+        public MetadataResult[] newArray(int size) {
+            return new MetadataResult[size];
+        }
+    };
+
+    /**
+     * Builder to construct {@link MetadataResult}.
+     */
+    public static final class Builder {
+        @ResultCode
+        private final int mResultCode;
+        private List<SettingsPreferenceMetadata> mMetadataList = Collections.emptyList();
+
+        /**
+         * Create Builder instance.
+         * @param resultCode indicates status of the request
+         */
+        public Builder(@ResultCode int resultCode) {
+            mResultCode = resultCode;
+        }
+
+        /**
+         * Sets the metadata list on the result.
+         */
+        @NonNull
+        public Builder setMetadataList(@NonNull List<SettingsPreferenceMetadata> metadataList) {
+            mMetadataList = metadataList;
+            return this;
+        }
+
+        /**
+         * Constructs an immutable {@link MetadataResult} object.
+         */
+        @NonNull
+        public MetadataResult build() {
+            return new MetadataResult(this);
+        }
+    }
+}
diff --git a/core/java/android/service/settings/preferences/SetValueRequest.aidl b/core/java/android/service/settings/preferences/SetValueRequest.aidl
new file mode 100644
index 0000000..198e333
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SetValueRequest.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable SetValueRequest;
\ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/SetValueRequest.java b/core/java/android/service/settings/preferences/SetValueRequest.java
new file mode 100644
index 0000000..f7600ae
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SetValueRequest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * Request parameters to set the current value to a Settings Preference.
+ * <p>This object passed to {@link SettingsPreferenceService#onSetPreferenceValue} will result in a
+ * {@link SetValueResult}.
+ * <ul>
+ *   <li>{@link #getScreenKey} is a parameter to distinguish the container screen
+ *   of a preference as a preference key may not be unique within its application.
+ *   <li>{@link #getPreferenceKey} is a parameter to identify the preference for which the value is
+ *   being requested. These keys will be unique with their Preference Screen, but may not be unique
+ *   within their application, so it is required to pair this with {@link #getScreenKey} to
+ *   ensure this request matches the intended target.
+ *   <li>{@link #getPreferenceValue} is a parameter to specify the value that this request aims to
+ *   set. If this value is invalid (malformed or does not match the type of the preference) then
+ *   this request will fail.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class SetValueRequest implements Parcelable {
+
+    @NonNull
+    private final String mScreenKey;
+    @NonNull
+    private final String mPreferenceKey;
+    @NonNull
+    private final SettingsPreferenceValue mPreferenceValue;
+
+    /**
+     * Returns the screen key of requested Preference.
+     */
+    @NonNull
+    public String getScreenKey() {
+        return mScreenKey;
+    }
+
+    /**
+     * Returns the key of requested Preference.
+     */
+    @NonNull
+    public String getPreferenceKey() {
+        return mPreferenceKey;
+    }
+
+    /**
+     * Returns the value of requested Preference.
+     */
+    @NonNull
+    public SettingsPreferenceValue getPreferenceValue() {
+        return mPreferenceValue;
+    }
+
+    private SetValueRequest(@NonNull Builder builder) {
+        mScreenKey = builder.mScreenKey;
+        mPreferenceKey = builder.mPreferenceKey;
+        mPreferenceValue = builder.mPreferenceValue;
+    }
+
+    private SetValueRequest(@NonNull Parcel in) {
+        mScreenKey = Objects.requireNonNull(in.readString8());
+        mPreferenceKey = Objects.requireNonNull(in.readString8());
+        mPreferenceValue = Objects.requireNonNull(in.readParcelable(
+                SettingsPreferenceValue.class.getClassLoader(), SettingsPreferenceValue.class));
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mScreenKey);
+        dest.writeString8(mPreferenceKey);
+        dest.writeParcelable(mPreferenceValue, flags);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Parcelable Creator for {@link SetValueRequest}.
+     */
+    @NonNull
+    public static final Creator<SetValueRequest> CREATOR = new Creator<SetValueRequest>() {
+        @Override
+        public SetValueRequest createFromParcel(@NonNull Parcel in) {
+            return new SetValueRequest(in);
+        }
+
+        @Override
+        public SetValueRequest[] newArray(int size) {
+            return new SetValueRequest[size];
+        }
+    };
+
+    /**
+     * Builder to construct {@link SetValueRequest}.
+     */
+    public static final class Builder {
+        private final String mScreenKey;
+        private final String mPreferenceKey;
+        private final SettingsPreferenceValue mPreferenceValue;
+
+        /**
+         * Create Builder instance.
+         * @param screenKey required to be not empty
+         * @param preferenceKey required to be not empty
+         * @param value value to set to requested Preference
+         */
+        public Builder(@NonNull String screenKey, @NonNull String preferenceKey,
+                       @NonNull SettingsPreferenceValue value) {
+            if (TextUtils.isEmpty(screenKey)) {
+                throw new IllegalArgumentException("screenKey cannot be empty");
+            }
+            if (TextUtils.isEmpty(preferenceKey)) {
+                throw new IllegalArgumentException("preferenceKey cannot be empty");
+            }
+            mScreenKey = screenKey;
+            mPreferenceKey = preferenceKey;
+            mPreferenceValue = value;
+        }
+
+        /**
+         * Constructs an immutable {@link SetValueRequest} object.
+         */
+        @NonNull
+        public SetValueRequest build() {
+            return new SetValueRequest(this);
+        }
+    }
+}
diff --git a/core/java/android/service/settings/preferences/SetValueResult.aidl b/core/java/android/service/settings/preferences/SetValueResult.aidl
new file mode 100644
index 0000000..f548134
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SetValueResult.aidl
@@ -0,0 +1,4 @@
+package android.service.settings.preferences;
+
+/** @hide */
+parcelable SetValueResult;
\ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/SetValueResult.java b/core/java/android/service/settings/preferences/SetValueResult.java
new file mode 100644
index 0000000..cb1776a
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SetValueResult.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result object given a corresponding {@link SetValueRequest}.
+ * <ul>
+ *   <li>If the request was successful, {@link #getResultCode} will be {@link #RESULT_OK}.
+ *   <li>If the request is unsuccessful, {@link #getResultCode} be a value other than
+ *   {@link #RESULT_OK} - see documentation for those possibilities to understand the cause
+ *   of the failure.
+ * </ul>
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class SetValueResult implements Parcelable {
+
+    @ResultCode
+    private final int mResultCode;
+
+    /**
+     * Returns the result code indicating status of the request.
+     */
+    @ResultCode
+    public int getResultCode() {
+        return mResultCode;
+    }
+
+    /** @hide */
+    @IntDef(prefix = { "RESULT_" }, value = {
+            RESULT_OK,
+            RESULT_UNSUPPORTED,
+            RESULT_DISABLED,
+            RESULT_RESTRICTED,
+            RESULT_UNAVAILABLE,
+            RESULT_REQUIRE_APP_PERMISSION,
+            RESULT_REQUIRE_USER_CONSENT,
+            RESULT_DISALLOW,
+            RESULT_INVALID_REQUEST,
+            RESULT_INTERNAL_ERROR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultCode {
+    }
+
+    /** Request is successful and the value was set. */
+    public static final int RESULT_OK = 0;
+    /**
+     * Requested preference is not supported by this API.
+     * <p>Retry not advised.
+     */
+    public static final int RESULT_UNSUPPORTED = 1;
+    /**
+     * Requested preference is disabled, thus unable to be set in this state.
+     * <p>Retry may succeed if underlying conditions change.
+     */
+    public static final int RESULT_DISABLED = 2;
+    /**
+     * Requested preference is restricted, thus unable to be set under this policy.
+     * <p>Retry may succeed if underlying conditions change.
+     */
+    public static final int RESULT_RESTRICTED = 3;
+    /**
+     * Preference is currently not available, likely due to device state or the state of
+     * a dependency.
+     * <p>Retry may succeed if underlying conditions change.
+     */
+    public static final int RESULT_UNAVAILABLE = 4;
+    /**
+     * Requested preference requires permissions not held by the calling application.
+     * <p>Retry may succeed if necessary permissions are obtained.
+     */
+    public static final int RESULT_REQUIRE_APP_PERMISSION = 5;
+    /**
+     * User consent was not approved for this operation.
+     * <p>Retry may succeed if user provides consent.
+     */
+    public static final int RESULT_REQUIRE_USER_CONSENT = 6;
+    /**
+     * Requested preference is not allowed for access in this API under the current device policy.
+     * <p>Retry may succeed if underlying conditions change.
+     */
+    public static final int RESULT_DISALLOW = 7;
+    /**
+     * Request object is not valid.
+     * <p>Retry not advised with current parameters.
+     */
+    public static final int RESULT_INVALID_REQUEST = 8;
+    /**
+     * API call failed due to an issue with the service binding.
+     * <p>Retry may succeed.
+     */
+    public static final int RESULT_INTERNAL_ERROR = 9;
+
+    private SetValueResult(@NonNull Builder builder) {
+        mResultCode = builder.mResultCode;
+    }
+
+    private SetValueResult(@NonNull Parcel in) {
+        mResultCode = in.readInt();
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mResultCode);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Parcelable Creator for {@link SetValueResult}.
+     */
+    @NonNull
+    public static final Creator<SetValueResult> CREATOR = new Creator<>() {
+        @Override
+        public SetValueResult createFromParcel(@NonNull Parcel in) {
+            return new SetValueResult(in);
+        }
+
+        @Override
+        public SetValueResult[] newArray(int size) {
+            return new SetValueResult[size];
+        }
+    };
+
+    /**
+     * Builder to construct {@link SetValueResult}.
+     */
+    public static final class Builder {
+        @ResultCode
+        private final int mResultCode;
+
+        /**
+         * Create Builder instance.
+         * @param resultCode indicates status of the request
+         */
+        public Builder(@ResultCode int resultCode) {
+            mResultCode = resultCode;
+        }
+
+        /**
+         * Constructs an immutable {@link SetValueResult} object.
+         */
+        @NonNull
+        public SetValueResult build() {
+            return new SetValueResult(this);
+        }
+    }
+}
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
new file mode 100644
index 0000000..1d08c52
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceMetadata.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Data object representation of a Settings Preference definition and state.
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class SettingsPreferenceMetadata implements Parcelable {
+
+    @NonNull
+    private final String mKey;
+    @NonNull
+    private final String mScreenKey;
+    @Nullable
+    private final String mTitle;
+    @Nullable
+    private final String mSummary;
+    @NonNull
+    private final List<String> mBreadcrumbs;
+    @NonNull
+    private final List<String> mReadPermissions;
+    @NonNull
+    private final List<String> mWritePermissions;
+    private final boolean mEnabled;
+    private final boolean mAvailable;
+    private final boolean mWritable;
+    private final boolean mRestricted;
+    private final int mSensitivity;
+    @Nullable
+    private final PendingIntent mLaunchIntent;
+    @NonNull
+    private final Bundle mExtras;
+
+    /**
+     * Returns the key of Preference.
+     */
+    @NonNull
+    public String getKey() {
+        return mKey;
+    }
+
+    /**
+     * Returns the screen key of Preference.
+     */
+    @NonNull
+    public String getScreenKey() {
+        return mScreenKey;
+    }
+
+    /**
+     * Returns the title of Preference.
+     */
+    @Nullable
+    public String getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Returns the summary of Preference.
+     */
+    @Nullable
+    public String getSummary() {
+        return mSummary;
+    }
+
+    /**
+     * Returns the breadcrumbs (navigation context) of Preference.
+     * <p>May be empty.
+     */
+    @NonNull
+    public List<String> getBreadcrumbs() {
+        return mBreadcrumbs;
+    }
+
+    /**
+     * Returns the permissions required to read this Preference's value.
+     * <p>May be empty.
+     */
+    @NonNull
+    public List<String> getReadPermissions() {
+        return mReadPermissions;
+    }
+
+    /**
+     * Returns the permissions required to write this Preference's value.
+     * <p>May be empty.
+     */
+    @NonNull
+    public List<String> getWritePermissions() {
+        return mWritePermissions;
+    }
+
+    /**
+     * Returns whether Preference is enabled.
+     */
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    /**
+     * Returns whether Preference is available.
+     */
+    public boolean isAvailable() {
+        return mAvailable;
+    }
+
+    /**
+     * Returns whether Preference is writable.
+     */
+    public boolean isWritable() {
+        return mWritable;
+    }
+
+    /**
+     * Returns whether Preference is restricted.
+     */
+    public boolean isRestricted() {
+        return mRestricted;
+    }
+
+    /**
+     * Returns the write-level sensitivity of Preference.
+     */
+    @WriteSensitivity
+    public int getWriteSensitivity() {
+        return mSensitivity;
+    }
+
+    /**
+     * Returns the intent to launch the host app page for this Preference.
+     */
+    @Nullable
+    public PendingIntent getLaunchIntent() {
+        return mLaunchIntent;
+    }
+
+    /**
+     * Returns any additional fields specific to this preference.
+     * <p>Treat all data as optional.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /** @hide */
+    @IntDef(value = {
+            NOT_SENSITIVE,
+            SENSITIVE,
+            INTENT_ONLY
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WriteSensitivity {}
+
+    /**
+     * Preference is not sensitive, thus its value is writable without explicit consent, assuming
+     * all necessary permissions are granted.
+     */
+    public static final int NOT_SENSITIVE = 0;
+    /**
+     * Preference is sensitive, meaning that in addition to necessary permissions, writing its value
+     * will also request explicit user consent.
+     */
+    public static final int SENSITIVE = 1;
+    /**
+     * Preference is not permitted for write-access via API and must be changed via Settings page.
+     */
+    public static final int INTENT_ONLY = 2;
+
+    private SettingsPreferenceMetadata(@NonNull Builder builder) {
+        mKey = builder.mKey;
+        mScreenKey = builder.mScreenKey;
+        mTitle = builder.mTitle;
+        mSummary = builder.mSummary;
+        mBreadcrumbs = builder.mBreadcrumbs;
+        mReadPermissions = builder.mReadPermissions;
+        mWritePermissions = builder.mWritePermissions;
+        mEnabled = builder.mEnabled;
+        mAvailable = builder.mAvailable;
+        mWritable = builder.mWritable;
+        mRestricted = builder.mRestricted;
+        mSensitivity = builder.mSensitivity;
+        mLaunchIntent = builder.mLaunchIntent;
+        mExtras = Objects.requireNonNullElseGet(builder.mExtras, Bundle::new);
+    }
+    @SuppressLint("ParcelClassLoader")
+    private SettingsPreferenceMetadata(@NonNull Parcel in) {
+        mKey = Objects.requireNonNull(in.readString8());
+        mScreenKey = Objects.requireNonNull(in.readString8());
+        mTitle = in.readString8();
+        mSummary = in.readString8();
+        mBreadcrumbs = new ArrayList<>();
+        in.readStringList(mBreadcrumbs);
+        mReadPermissions = new ArrayList<>();
+        in.readStringList(mReadPermissions);
+        mWritePermissions = new ArrayList<>();
+        in.readStringList(mWritePermissions);
+        mEnabled = in.readBoolean();
+        mAvailable = in.readBoolean();
+        mWritable = in.readBoolean();
+        mRestricted = in.readBoolean();
+        mSensitivity = in.readInt();
+        mLaunchIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
+                PendingIntent.class);
+        mExtras = Objects.requireNonNullElseGet(in.readBundle(), Bundle::new);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mKey);
+        dest.writeString8(mScreenKey);
+        dest.writeString8(mTitle);
+        dest.writeString8(mSummary);
+        dest.writeStringList(mBreadcrumbs);
+        dest.writeStringList(mReadPermissions);
+        dest.writeStringList(mWritePermissions);
+        dest.writeBoolean(mEnabled);
+        dest.writeBoolean(mAvailable);
+        dest.writeBoolean(mWritable);
+        dest.writeBoolean(mRestricted);
+        dest.writeInt(mSensitivity);
+        dest.writeParcelable(mLaunchIntent, flags);
+        dest.writeBundle(mExtras);
+    }
+
+    /**
+     * Parcelable Creator for {@link SettingsPreferenceMetadata}.
+     */
+    @NonNull
+    public static final Creator<SettingsPreferenceMetadata> CREATOR = new Creator<>() {
+        @Override
+        public SettingsPreferenceMetadata createFromParcel(@NonNull Parcel in) {
+            return new SettingsPreferenceMetadata(in);
+        }
+
+        @Override
+        public SettingsPreferenceMetadata[] newArray(int size) {
+            return new SettingsPreferenceMetadata[size];
+        }
+    };
+
+    /**
+     * Builder to construct {@link SettingsPreferenceMetadata}.
+     */
+    public static final class Builder {
+        private final String mScreenKey;
+        private final String mKey;
+        private String mTitle;
+        private String mSummary;
+        private List<String> mBreadcrumbs = Collections.emptyList();
+        private List<String> mReadPermissions = Collections.emptyList();
+        private List<String> mWritePermissions = Collections.emptyList();
+        private boolean mEnabled = false;
+        private boolean mAvailable = false;
+        private boolean mWritable = false;
+        private boolean mRestricted = false;
+        @WriteSensitivity private int mSensitivity = INTENT_ONLY;
+        private PendingIntent mLaunchIntent;
+        private Bundle mExtras;
+
+        /**
+         * Create Builder instance.
+         * @param screenKey required to be not empty
+         * @param key required to be not empty
+         */
+        public Builder(@NonNull String screenKey, @NonNull String key) {
+            if (TextUtils.isEmpty(screenKey)) {
+                throw new IllegalArgumentException("screenKey cannot be empty");
+            }
+            if (TextUtils.isEmpty(key)) {
+                throw new IllegalArgumentException("key cannot be empty");
+            }
+            mScreenKey = screenKey;
+            mKey = key;
+        }
+
+        /**
+         * Sets the preference title.
+         */
+        @NonNull
+        public Builder setTitle(@Nullable String title) {
+            mTitle = title;
+            return this;
+        }
+
+        /**
+         * Sets the preference summary.
+         */
+        @NonNull
+        public Builder setSummary(@Nullable String summary) {
+            mSummary = summary;
+            return this;
+        }
+
+        /**
+         * Sets the preference breadcrumbs (navigation context).
+         */
+        @NonNull
+        public Builder setBreadcrumbs(@NonNull List<String> breadcrumbs) {
+            mBreadcrumbs = breadcrumbs;
+            return this;
+        }
+
+        /**
+         * Sets the permissions required for reading this preference.
+         */
+        @NonNull
+        public Builder setReadPermissions(@NonNull List<String> readPermissions) {
+            mReadPermissions = readPermissions;
+            return this;
+        }
+
+        /**
+         * Sets the permissions required for writing this preference.
+         */
+        @NonNull
+        public Builder setWritePermissions(@NonNull List<String> writePermissions) {
+            mWritePermissions = writePermissions;
+            return this;
+        }
+
+        /**
+         * Set whether the preference is enabled.
+         */
+        @NonNull
+        public Builder setEnabled(boolean enabled) {
+            mEnabled = enabled;
+            return this;
+        }
+
+        /**
+         * Sets whether the preference is available.
+         */
+        @NonNull
+        public Builder setAvailable(boolean available) {
+            mAvailable = available;
+            return this;
+        }
+
+        /**
+         * Sets whether the preference is writable.
+         */
+        @NonNull
+        public Builder setWritable(boolean writable) {
+            mWritable = writable;
+            return this;
+        }
+
+        /**
+         * Sets whether the preference is restricted.
+         */
+        @NonNull
+        public Builder setRestricted(boolean restricted) {
+            mRestricted = restricted;
+            return this;
+        }
+
+        /**
+         * Sets the preference write-level sensitivity.
+         */
+        @NonNull
+        public Builder setWriteSensitivity(@WriteSensitivity int sensitivity) {
+            mSensitivity = sensitivity;
+            return this;
+        }
+
+        /**
+         * Sets the intent to launch the host app page for this preference.
+         */
+        @NonNull
+        public Builder setLaunchIntent(@Nullable PendingIntent launchIntent) {
+            mLaunchIntent = launchIntent;
+            return this;
+        }
+
+        /**
+         * Sets additional fields specific to this preference. Treat all data as optional.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Constructs an immutable {@link SettingsPreferenceMetadata} object.
+         */
+        @NonNull
+        public SettingsPreferenceMetadata build() {
+            return new SettingsPreferenceMetadata(this);
+        }
+    }
+}
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
new file mode 100644
index 0000000..f056e34
--- /dev/null
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.service.settings.preferences;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.SuppressLint;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.settingslib.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This objects represents a value that can be used for a particular settings preference.
+ * <p>The data type for the value will correspond to {@link #getType}. For possible types, see
+ * constants below, such as {@link #TYPE_BOOLEAN} and {@link #TYPE_STRING}.
+ * Depending on the type, the corresponding getter will contain its value. All other getters will
+ * return default values (boolean returns false, String returns null) so they should not be used.
+ * <p>See documentation on the constants for which getter method should be used.
+ */
+@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
+public final class SettingsPreferenceValue implements Parcelable {
+
+    @Type
+    private final int mType;
+    private final boolean mBooleanValue;
+    private final long mLongValue;
+    private final double mDoubleValue;
+    @Nullable
+    private final String mStringValue;
+
+    /**
+     * Returns the type indicator for Preference value.
+     */
+    @Type
+    public int getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the boolean value for Preference if type is {@link #TYPE_BOOLEAN}.
+     */
+    public boolean getBooleanValue() {
+        return mBooleanValue;
+    }
+
+    /**
+     * Returns the long value for Preference if type is {@link #TYPE_LONG}.
+     */
+    public long getLongValue() {
+        return mLongValue;
+    }
+
+    /**
+     * Returns the double value for Preference if type is {@link #TYPE_DOUBLE}.
+     */
+    public double getDoubleValue() {
+        return mDoubleValue;
+    }
+
+    /**
+     * Returns the string value for Preference if type is {@link #TYPE_STRING}.
+     */
+    @Nullable
+    public String getStringValue() {
+        return mStringValue;
+    }
+
+    /** @hide */
+    @IntDef(prefix = { "TYPE_" }, value = {
+            TYPE_BOOLEAN,
+            TYPE_LONG,
+            TYPE_DOUBLE,
+            TYPE_STRING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Type {}
+
+    /** Value is of type boolean. Access via {@link #getBooleanValue}. */
+    public static final int TYPE_BOOLEAN = 0;
+    /** Value is of type long. Access via {@link #getLongValue()}. */
+    public static final int TYPE_LONG = 1;
+    /** Value is of type double. Access via {@link #getDoubleValue()}. */
+    public static final int TYPE_DOUBLE = 2;
+    /** Value is of type string. Access via {@link #getStringValue}. */
+    public static final int TYPE_STRING = 3;
+
+    private SettingsPreferenceValue(@NonNull Builder builder) {
+        mType = builder.mType;
+        mBooleanValue = builder.mBooleanValue;
+        mLongValue = builder.mLongValue;
+        mDoubleValue = builder.mDoubleValue;
+        mStringValue = builder.mStringValue;
+    }
+
+    private SettingsPreferenceValue(@NonNull Parcel in) {
+        mType = in.readInt();
+        mBooleanValue = in.readBoolean();
+        mLongValue = in.readLong();
+        mDoubleValue = in.readDouble();
+        mStringValue = in.readString8();
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeBoolean(mBooleanValue);
+        dest.writeLong(mLongValue);
+        dest.writeDouble(mDoubleValue);
+        dest.writeString8(mStringValue);
+    }
+
+    /** @hide */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Parcelable Creator for {@link SettingsPreferenceValue}.
+     */
+    @NonNull
+    public static final Creator<SettingsPreferenceValue> CREATOR = new Creator<>() {
+        @Override
+        public SettingsPreferenceValue createFromParcel(@NonNull Parcel in) {
+            return new SettingsPreferenceValue(in);
+        }
+
+        @Override
+        public SettingsPreferenceValue[] newArray(int size) {
+            return new SettingsPreferenceValue[size];
+        }
+    };
+
+    /**
+     * Builder to construct {@link SettingsPreferenceValue}.
+     */
+    public static final class Builder {
+        @Type
+        private final int mType;
+        private boolean mBooleanValue;
+        private long mLongValue;
+        private double mDoubleValue;
+        private String mStringValue;
+
+        /**
+         * Create Builder instance.
+         * @param type type indicator for preference value
+         */
+        public Builder(@Type int type) {
+            mType = type;
+        }
+
+        /**
+         * Sets boolean value for Preference.
+         */
+        @SuppressLint("MissingGetterMatchingBuilder")
+        @NonNull
+        public Builder setBooleanValue(boolean booleanValue) {
+            mBooleanValue = booleanValue;
+            return this;
+        }
+
+        /**
+         * Sets long value for Preference.
+         */
+        @NonNull
+        public Builder setLongValue(long longValue) {
+            mLongValue = longValue;
+            return this;
+        }
+
+        /**
+         * Sets floating point value for Preference.
+         */
+        @NonNull
+        public Builder setDoubleValue(double doubleValue) {
+            mDoubleValue = doubleValue;
+            return this;
+        }
+
+        /**
+         * Sets string value for Preference.
+         */
+        @NonNull
+        public Builder setStringValue(@Nullable String stringValue) {
+            mStringValue = stringValue;
+            return this;
+        }
+
+        /**
+         * Constructs an immutable {@link SettingsPreferenceValue} object.
+         */
+        @NonNull
+        public SettingsPreferenceValue build() {
+            return new SettingsPreferenceValue(this);
+        }
+    }
+}
diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl
index f76e6ce..bcdd477 100644
--- a/core/java/android/service/wallpaper/IWallpaperService.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperService.aidl
@@ -28,6 +28,6 @@
     void attach(IWallpaperConnection connection,
             IBinder windowToken, int windowType, boolean isPreview,
             int reqWidth, int reqHeight, in Rect padding, int displayId, int which,
-            in WallpaperInfo info, in @nullable WallpaperDescription description);
+            in WallpaperInfo info, in WallpaperDescription description);
     void detach(IBinder windowToken);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 131fdc8..2061aba 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -17,6 +17,7 @@
 package android.service.wallpaper;
 
 import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
+import static android.app.Flags.liveWallpaperContentHandling;
 import static android.app.WallpaperManager.COMMAND_FREEZE;
 import static android.app.WallpaperManager.COMMAND_UNFREEZE;
 import static android.app.WallpaperManager.SetWallpaperFlags;
@@ -2624,7 +2625,7 @@
         private void doAttachEngine() {
             Trace.beginSection("WPMS.onCreateEngine");
             Engine engine;
-            if (mDescription != null) {
+            if (liveWallpaperContentHandling()) {
                 engine = onCreateEngine(mDescription);
             } else {
                 engine = onCreateEngine();
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 910e644..0241e94 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -1549,8 +1549,9 @@
             // Although we only care about the HDR/SDR ratio changing, that can also come in the
             // form of the larger DISPLAY_CHANGED event
             mGlobal.registerDisplayListener(toRegister, executor,
-                    DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED
-                            | DisplayManagerGlobal.EVENT_DISPLAY_CHANGED,
+                    DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                            | DisplayManagerGlobal
+                                    .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED,
                     ActivityThread.currentPackageName());
         }
 
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 8f112f3..4ff04d5 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -447,7 +447,6 @@
                 && Objects.equals(displayCutout, other.displayCutout)
                 && rotation == other.rotation
                 && modeId == other.modeId
-                && renderFrameRate == other.renderFrameRate
                 && hasArrSupport == other.hasArrSupport
                 && Objects.equals(frameRateCategoryRate, other.frameRateCategoryRate)
                 && defaultModeId == other.defaultModeId
@@ -705,6 +704,9 @@
         if (refreshRateOverride > 0) {
             return refreshRateOverride;
         }
+        if (renderFrameRate > 0) {
+            return renderFrameRate;
+        }
         if (supportedModes.length == 0) {
             return 0;
         }
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index dddc408..38e4e27 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -935,7 +935,6 @@
      */
     public static final int KEYCODE_MACRO_4 = 316;
     /** Key code constant: To open emoji picker */
-    @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
     public static final int KEYCODE_EMOJI_PICKER = 317;
     /**
      * Key code constant: To take a screenshot
@@ -944,15 +943,80 @@
      * unlike {@code KEYCODE_SYSRQ} which is sent to the app first and only if the app
      * doesn't handle it, the framework handles it (to take a screenshot).
      */
-    @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
     public static final int KEYCODE_SCREENSHOT = 318;
+    /** Key code constant: To start dictate to an input field */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_DICTATE = 319;
+    /**
+     * Key code constant: AC New
+     *
+     * e.g. To create a new instance of a window, open a new tab, etc.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_NEW = 320;
+    /**
+     * Key code constant: AC Close
+     *
+     * e.g. To close current instance of the application window, close the current tab, etc.
+     */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_CLOSE = 321;
+    /** Key code constant: To toggle 'Do Not Disturb' mode */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_DO_NOT_DISTURB = 322;
+    /** Key code constant: To Print */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_PRINT = 323;
+    /** Key code constant: To Lock the screen */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_LOCK = 324;
+    /** Key code constant: To toggle fullscreen mode (on the current application) */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_FULLSCREEN = 325;
+    /** Key code constant: F13 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F13 = 326;
+    /** Key code constant: F14 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F14 = 327;
+    /** Key code constant: F15 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F15 = 328;
+    /** Key code constant: F16 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F16 = 329;
+    /** Key code constant: F17 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F17 = 330;
+    /** Key code constant: F18 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F18 = 331;
+    /** Key code constant: F19 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F19 = 332;
+    /** Key code constant: F20 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F20 = 333;
+    /** Key code constant: F21 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F21 = 334;
+    /** Key code constant: F22 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F22 = 335;
+    /** Key code constant: F23 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F23 = 336;
+    /** Key code constant: F24 key. */
+    @FlaggedApi(Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
+    public static final int KEYCODE_F24 = 337;
 
     /**
      * Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent.
      * @hide
      */
     @TestApi
-    public static final int LAST_KEYCODE = KEYCODE_SCREENSHOT;
+    @SuppressWarnings("FlaggedApi")
+    public static final int LAST_KEYCODE = KEYCODE_F24;
 
     /** @hide */
     @IntDef(prefix = {"KEYCODE_"}, value = {
@@ -1275,6 +1339,25 @@
             KEYCODE_MACRO_4,
             KEYCODE_EMOJI_PICKER,
             KEYCODE_SCREENSHOT,
+            KEYCODE_DICTATE,
+            KEYCODE_NEW,
+            KEYCODE_CLOSE,
+            KEYCODE_DO_NOT_DISTURB,
+            KEYCODE_PRINT,
+            KEYCODE_LOCK,
+            KEYCODE_FULLSCREEN,
+            KEYCODE_F13,
+            KEYCODE_F14,
+            KEYCODE_F15,
+            KEYCODE_F16,
+            KEYCODE_F17,
+            KEYCODE_F18,
+            KEYCODE_F19,
+            KEYCODE_F20,
+            KEYCODE_F21,
+            KEYCODE_F22,
+            KEYCODE_F23,
+            KEYCODE_F24,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface KeyCode {}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 618843c..fa06831 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -23882,12 +23882,12 @@
                     } else {
                         draw(canvas);
                     }
-                }
 
-                // For VRR to vote the preferred frame rate
-                if (sToolkitSetFrameRateReadOnlyFlagValue
-                        && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
-                    votePreferredFrameRate();
+                    // For VRR to vote the preferred frame rate
+                    if (sToolkitSetFrameRateReadOnlyFlagValue
+                            && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
+                        votePreferredFrameRate();
+                    }
                 }
             } finally {
                 renderNode.endRecording();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3ce6870..9a2aa0b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -185,7 +185,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.hardware.SyncFence;
-import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.input.InputManagerGlobal;
@@ -1816,9 +1815,9 @@
                 .registerDisplayListener(
                         mDisplayListener,
                         mHandler,
-                        DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
+                        DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
                         mBasePackageName);
 
         if (forceInvertColor()) {
diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java
index a5be58b..16eb437 100644
--- a/core/java/android/window/BackProgressAnimator.java
+++ b/core/java/android/window/BackProgressAnimator.java
@@ -16,8 +16,11 @@
 
 package android.window;
 
+import static android.window.BackEvent.EDGE_NONE;
+
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.window.flags.Flags.predictiveBackTimestampApi;
+import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -60,6 +63,12 @@
     @Nullable
     private Runnable mBackInvokedFinishRunnable;
     private FlingAnimation mBackInvokedFlingAnim;
+    private final SpringForce mGestureSpringForce = new SpringForce()
+            .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+            .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
+    private final SpringForce mButtonSpringForce = new SpringForce()
+            .setStiffness(500)
+            .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY);
     private final DynamicAnimation.OnAnimationEndListener mOnAnimationEndListener =
             (animation, canceled, value, velocity) -> {
                 if (mBackCancelledFinishRunnable != null) invokeBackCancelledRunnable();
@@ -109,9 +118,7 @@
     public BackProgressAnimator() {
         mSpring = new SpringAnimation(this, PROGRESS_PROP);
         mSpring.addUpdateListener(this);
-        mSpring.setSpring(new SpringForce()
-                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
-                .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+        mSpring.setSpring(mGestureSpringForce);
     }
 
     /**
@@ -123,6 +130,11 @@
         if (!mBackAnimationInProgress) {
             return;
         }
+        if (predictiveBackSwipeEdgeNoneApi()) {
+            if (event.getSwipeEdge() == EDGE_NONE) {
+                return;
+            }
+        }
         mLastBackEvent = event;
         if (mSpring == null) {
             return;
@@ -143,7 +155,17 @@
         mBackAnimationInProgress = true;
         updateProgressValue(/* progress */ 0, /* velocity */ 0,
                 /* frameTime */ System.nanoTime() / TimeUtils.NANOS_PER_MS);
-        onBackProgressed(event);
+        if (predictiveBackSwipeEdgeNoneApi()) {
+            if (event.getSwipeEdge() == EDGE_NONE) {
+                mSpring.setSpring(mButtonSpringForce);
+                mSpring.animateToFinalPosition(SCALE_FACTOR);
+            } else {
+                mSpring.setSpring(mGestureSpringForce);
+                onBackProgressed(event);
+            }
+        } else {
+            onBackProgressed(event);
+        }
     }
 
     /**
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 11f6849..68e78fe 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -76,6 +76,14 @@
 }
 
 flag {
+  name: "disable_opt_out_edge_to_edge"
+  namespace: "windowing_frontend"
+  description: "Deprecate and disable windowOptOutEdgeToEdgeEnforcement"
+  bug: "377864165"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "keyguard_going_away_timeout"
   namespace: "windowing_frontend"
   description: "Allow a maximum of 10 seconds with keyguardGoingAway=true before force-resetting"
@@ -388,4 +396,11 @@
     description: "Provide pre-make predictive back API extension"
     is_fixed_read_only: true
     bug: "362938401"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "predictive_back_three_button_nav"
+    namespace: "systemui"
+    description: "Enable Predictive Back Animation for 3-button-nav"
+    bug: "373544911"
+}
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 21fbf9d..a50dbb0 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -600,8 +600,8 @@
             final ContentResolver cr = mContext.getContentResolver();
             cr.registerContentObserver(BRIGHTNESS_URI, false,
                     createBrightnessContentObserver(handler), UserHandle.USER_ALL);
-            mDisplayManager.registerDisplayListener(mListener, handler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+            mDisplayManager.registerDisplayListener(mListener, handler, /* eventFlags */ 0,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS);
             mIsObserving = true;
         }
     }
diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
index ca6c54d..0c2fd4b 100644
--- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java
+++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
@@ -147,8 +147,9 @@
                 @Override
                 public void registerDisplayListener(DisplayManager.DisplayListener listener) {
                     manager.registerDisplayListener(listener, handler,
-                            DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED,
+                            DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                                    | DisplayManagerGlobal
+                                            .INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
                             ActivityThread.currentPackageName());
                 }
 
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index b3480ab..2931bd2 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -56,7 +56,7 @@
  * @hide
  */
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("LongArrayMultiStateCounter_host")
+@RavenwoodRedirectionClass("LongArrayMultiStateCounter_ravenwood")
 public final class LongArrayMultiStateCounter implements Parcelable {
     private static volatile NativeAllocationRegistry sRegistry;
     private final int mStateCount;
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
similarity index 98%
rename from ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
rename to core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
index 90608f6..7030d8e 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter_ravenwood.java
@@ -18,6 +18,7 @@
 
 import android.os.BadParcelableException;
 import android.os.Parcel;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import java.util.Arrays;
 import java.util.HashMap;
@@ -25,7 +26,8 @@
 /**
  * Native implementation substitutions for the LongArrayMultiStateCounter class.
  */
-public class LongArrayMultiStateCounter_host {
+@RavenwoodKeepWholeClass
+class LongArrayMultiStateCounter_ravenwood {
 
     /**
      * A reimplementation of {@link LongArrayMultiStateCounter}, only in
diff --git a/core/java/com/android/internal/os/LongMultiStateCounter.java b/core/java/com/android/internal/os/LongMultiStateCounter.java
index c386a86..ee855d5 100644
--- a/core/java/com/android/internal/os/LongMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongMultiStateCounter.java
@@ -60,7 +60,7 @@
  * @hide
  */
 @RavenwoodKeepWholeClass
-@RavenwoodRedirectionClass("LongMultiStateCounter_host")
+@RavenwoodRedirectionClass("LongMultiStateCounter_ravenwood")
 public final class LongMultiStateCounter implements Parcelable {
 
     private static NativeAllocationRegistry sRegistry;
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java b/core/java/com/android/internal/os/LongMultiStateCounter_ravenwood.java
similarity index 97%
rename from ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java
rename to core/java/com/android/internal/os/LongMultiStateCounter_ravenwood.java
index 1d95aa1..42db37f 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongMultiStateCounter_host.java
+++ b/core/java/com/android/internal/os/LongMultiStateCounter_ravenwood.java
@@ -18,13 +18,15 @@
 
 import android.os.BadParcelableException;
 import android.os.Parcel;
+import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 
 import java.util.HashMap;
 
 /**
  * Native implementation substitutions for the LongMultiStateCounter class.
  */
-public class LongMultiStateCounter_host {
+@RavenwoodKeepWholeClass
+class LongMultiStateCounter_ravenwood {
 
     /**
      * A reimplementation of {@link com.android.internal.os.LongMultiStateCounter}, only in
diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java
index 1938cdb..40161023 100644
--- a/core/java/com/android/internal/statusbar/StatusBarIcon.java
+++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java
@@ -40,9 +40,6 @@
     public enum Type {
         // Notification: the sender avatar for important conversations
         PeopleAvatar,
-        // Notification: the monochrome version of the app icon if available; otherwise fall back to
-        // the small icon
-        MaybeMonochromeAppIcon,
         // Notification: the small icon from the notification
         NotifSmallIcon,
         // The wi-fi, cellular or battery icon.
diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java
index adcc0f6..5fc61b0 100644
--- a/core/java/com/android/internal/widget/NotificationRowIconView.java
+++ b/core/java/com/android/internal/widget/NotificationRowIconView.java
@@ -22,11 +22,7 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
 import android.graphics.Paint;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
@@ -35,8 +31,6 @@
 import android.view.RemotableViewMethod;
 import android.widget.RemoteViews;
 
-import com.android.internal.R;
-
 /**
  * An image view that holds the icon displayed at the start of a notification row.
  * This can generally either display the "small icon" of a notification set via
@@ -48,7 +42,6 @@
     private NotificationIconProvider mIconProvider;
 
     private boolean mApplyCircularCrop = false;
-    private boolean mShouldShowAppIcon = false;
     private Drawable mAppIcon = null;
 
     // Padding, background and colors set on the view prior to being overridden when showing the app
@@ -77,17 +70,6 @@
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
-    @Override
-    protected void onFinishInflate() {
-        // If showing the app icon, we don't need background or padding.
-        if (Flags.notificationsUseAppIcon()) {
-            setPadding(0, 0, 0, 0);
-            setBackground(null);
-        }
-
-        super.onFinishInflate();
-    }
-
     /**
      * Sets the icon provider for this view. This is used to determine whether we should show the
      * app icon instead of the small icon, and to fetch the app icon if needed.
@@ -153,37 +135,12 @@
         return super.setImageIconAsync(icon);
     }
 
-    /** Whether the icon represents the app icon (instead of the small icon). */
-    @RemotableViewMethod
-    public void setShouldShowAppIcon(boolean shouldShowAppIcon) {
-        if (Flags.notificationsUseAppIconInRow()) {
-            if (mShouldShowAppIcon == shouldShowAppIcon) {
-                return; // no change
-            }
-
-            mShouldShowAppIcon = shouldShowAppIcon;
-            if (mShouldShowAppIcon) {
-                adjustViewForAppIcon();
-            } else {
-                // Restore original padding and background if needed
-                restoreViewForSmallIcon();
-            }
-        }
-    }
-
     /**
      * Override padding and background from the view to display the app icon.
      */
     private void adjustViewForAppIcon() {
         removePadding();
-
-        if (Flags.notificationsUseAppIconInRow()) {
-            addWhiteBackground();
-        } else {
-            // No need to set the background for notification redesign, since the icon
-            // factory already does that for us.
-            removeBackground();
-        }
+        removeBackground();
     }
 
     /**
@@ -221,21 +178,6 @@
         setBackground(null);
     }
 
-    private void addWhiteBackground() {
-        if (mOriginalBackground == null) {
-            mOriginalBackground = getBackground();
-        }
-
-        // Make the background white in case the icon itself doesn't have one.
-        ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE,
-                PorterDuff.Mode.SRC_ATOP);
-
-        if (mOriginalBackground == null) {
-            setBackground(getContext().getDrawable(R.drawable.notification_icon_circle));
-        }
-        getBackground().mutate().setColorFilter(colorFilter);
-    }
-
     private void restoreBackground() {
         // NOTE: This will not work if the original background was null, but that's better than
         //  accidentally clearing the background. We expect that there's generally going to be one
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 212df02..0761a24 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -15,12 +15,14 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.operations.ComponentValue;
 import com.android.internal.widget.remotecompose.core.operations.IntegerExpression;
 import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
-import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
@@ -28,7 +30,11 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponent;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchCancelModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchDownModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
@@ -49,12 +55,12 @@
 
     ArrayList<Operation> mOperations;
 
-    RootLayoutComponent mRootLayoutComponent = null;
+    @Nullable RootLayoutComponent mRootLayoutComponent = null;
 
     RemoteComposeState mRemoteComposeState = new RemoteComposeState();
-    TimeVariables mTimeVariables = new TimeVariables();
+    @NonNull TimeVariables mTimeVariables = new TimeVariables();
     // Semantic version of the document
-    Version mVersion = new Version(0, 1, 0);
+    @NonNull Version mVersion = new Version(0, 1, 0);
 
     String mContentDescription; // text description of the document (used for accessibility)
 
@@ -72,6 +78,8 @@
 
     private final HashMap<Long, IntegerExpression> mIntegerExpressions = new HashMap<>();
 
+    private HashSet<Component> mAppliedTouchOperations = new HashSet<>();
+
     private int mLastId = 1; // last component id when inflating the file
 
     public String getContentDescription() {
@@ -272,6 +280,7 @@
      *
      * @return list of click areas in document coordinates
      */
+    @NonNull
     public Set<ClickAreaRepresentation> getClickAreas() {
         return mClickAreas;
     }
@@ -281,6 +290,7 @@
      *
      * @return returns the root component if it exists, null otherwise
      */
+    @Nullable
     public RootLayoutComponent getRootLayoutComponent() {
         return mRootLayoutComponent;
     }
@@ -298,6 +308,7 @@
      * @param id component id
      * @return the component if it exists, null otherwise
      */
+    @Nullable
     public Component getComponent(int id) {
         if (mRootLayoutComponent != null) {
             return mRootLayoutComponent.getComponent(id);
@@ -310,6 +321,7 @@
      *
      * @return a standardized string representation of the component hierarchy
      */
+    @NonNull
     public String displayHierarchy() {
         StringSerializer serializer = new StringSerializer();
         for (Operation op : mOperations) {
@@ -329,7 +341,8 @@
      * @param targetId the id of the value to update with the expression
      * @param context the current context
      */
-    public void evaluateIntExpression(long expressionId, int targetId, RemoteContext context) {
+    public void evaluateIntExpression(
+            long expressionId, int targetId, @NonNull RemoteContext context) {
         IntegerExpression expression = mIntegerExpressions.get(expressionId);
         if (expression != null) {
             int v = expression.evaluate(context);
@@ -337,22 +350,46 @@
         }
     }
 
-    /** Callback interface for host actions */
-    public interface ActionCallback {
-        // TODO: add payload support
-        void onAction(String name);
+    // ============== Haptic support ==================
+    public interface HapticEngine {
+        void haptic(int type);
     }
 
-    HashSet<ActionCallback> mActionListeners = new HashSet<ActionCallback>();
+    HapticEngine mHapticEngine;
+
+    public void setHapticEngine(HapticEngine engine) {
+        mHapticEngine = engine;
+    }
+
+    public void haptic(int type) {
+        if (mHapticEngine != null) {
+            mHapticEngine.haptic(type);
+        }
+    }
+
+    // ============== Haptic support ==================
+
+    public void appliedTouchOperation(Component operation) {
+        mAppliedTouchOperations.add(operation);
+    }
+
+    /** Callback interface for host actions */
+    public interface ActionCallback {
+        void onAction(String name, Object value);
+    }
+
+    @NonNull HashSet<ActionCallback> mActionListeners = new HashSet<ActionCallback>();
 
     /**
      * Warn action listeners for the given named action
      *
      * @param name the action name
+     * @param value a parameter to the action
      */
-    public void runNamedAction(String name) {
+    public void runNamedAction(String name, Object value) {
+        // TODO: we might add an interface to group all valid parameter types
         for (ActionCallback callback : mActionListeners) {
-            callback.onAction(name);
+            callback.onAction(name, value);
         }
     }
 
@@ -374,8 +411,9 @@
         void click(int id, String metadata);
     }
 
-    HashSet<ClickCallbacks> mClickListeners = new HashSet<>();
-    HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>();
+    @NonNull HashSet<ClickCallbacks> mClickListeners = new HashSet<>();
+    @NonNull HashSet<TouchListener> mTouchListeners = new HashSet<>();
+    @NonNull HashSet<ClickAreaRepresentation> mClickAreas = new HashSet<>();
 
     static class Version {
         public final int major;
@@ -456,7 +494,7 @@
     }
 
     /** Load operations from the given buffer */
-    public void initFromBuffer(RemoteComposeBuffer buffer) {
+    public void initFromBuffer(@NonNull RemoteComposeBuffer buffer) {
         mOperations = new ArrayList<Operation>();
         buffer.inflateFromBuffer(mOperations);
         for (Operation op : mOperations) {
@@ -484,12 +522,16 @@
      * @param operations flat list of operations
      * @return nested list of operations / components
      */
-    private ArrayList<Operation> inflateComponents(ArrayList<Operation> operations) {
+    @NonNull
+    private ArrayList<Operation> inflateComponents(@NonNull ArrayList<Operation> operations) {
         Component currentComponent = null;
         ArrayList<Component> components = new ArrayList<>();
         ArrayList<Operation> finalOperationsList = new ArrayList<>();
         ArrayList<Operation> ops = finalOperationsList;
         ClickModifierOperation currentClickModifier = null;
+        TouchDownModifierOperation currentTouchDownModifier = null;
+        TouchUpModifierOperation currentTouchUpModifier = null;
+        TouchCancelModifierOperation currentTouchCancelModifier = null;
         LoopOperation currentLoop = null;
 
         mLastId = -1;
@@ -519,10 +561,30 @@
                 // TODO: refactor to add container <- component...
                 currentClickModifier = (ClickModifierOperation) o;
                 ops = currentClickModifier.getList();
-            } else if (o instanceof ClickModifierEnd) {
+            } else if (o instanceof TouchDownModifierOperation) {
+                currentTouchDownModifier = (TouchDownModifierOperation) o;
+                ops = currentTouchDownModifier.getList();
+            } else if (o instanceof TouchUpModifierOperation) {
+                currentTouchUpModifier = (TouchUpModifierOperation) o;
+                ops = currentTouchUpModifier.getList();
+            } else if (o instanceof TouchCancelModifierOperation) {
+                currentTouchCancelModifier = (TouchCancelModifierOperation) o;
+                ops = currentTouchCancelModifier.getList();
+            } else if (o instanceof OperationsListEnd) {
                 ops = currentComponent.getList();
-                ops.add(currentClickModifier);
-                currentClickModifier = null;
+                if (currentClickModifier != null) {
+                    ops.add(currentClickModifier);
+                    currentClickModifier = null;
+                } else if (currentTouchDownModifier != null) {
+                    ops.add(currentTouchDownModifier);
+                    currentTouchDownModifier = null;
+                } else if (currentTouchUpModifier != null) {
+                    ops.add(currentTouchUpModifier);
+                    currentTouchUpModifier = null;
+                } else if (currentTouchCancelModifier != null) {
+                    ops.add(currentTouchCancelModifier);
+                    currentTouchCancelModifier = null;
+                }
             } else if (o instanceof LoopOperation) {
                 currentLoop = (LoopOperation) o;
                 ops = currentLoop.getList();
@@ -541,9 +603,9 @@
         return ops;
     }
 
-    private HashMap<Integer, Component> mComponentMap = new HashMap<Integer, Component>();
+    @NonNull private HashMap<Integer, Component> mComponentMap = new HashMap<Integer, Component>();
 
-    private void registerVariables(RemoteContext context, ArrayList<Operation> list) {
+    private void registerVariables(RemoteContext context, @NonNull ArrayList<Operation> list) {
         for (Operation op : list) {
             if (op instanceof VariableSupport) {
                 ((VariableSupport) op).updateVariables(context);
@@ -578,7 +640,7 @@
      * Called when an initialization is needed, allowing the document to eg load resources / cache
      * them.
      */
-    public void initializeContext(RemoteContext context) {
+    public void initializeContext(@NonNull RemoteContext context) {
         mRemoteComposeState.reset();
         mRemoteComposeState.setContext(context);
         mClickAreas.clear();
@@ -651,6 +713,15 @@
     }
 
     /**
+     * Called by commands to listen to touch events
+     *
+     * @param listener
+     */
+    public void addTouchListener(TouchListener listener) {
+        mTouchListeners.add(listener);
+    }
+
+    /**
      * Add a click listener. This will get called when a click is detected on the document
      *
      * @param callback called when a click area has been hit, passing the click are id and metadata.
@@ -664,6 +735,7 @@
      *
      * @return set of click listeners
      */
+    @NonNull
     public HashSet<CoreDocument.ClickCallbacks> getClickListeners() {
         return mClickListeners;
     }
@@ -700,12 +772,98 @@
     }
 
     /** Warn click listeners when a click area is activated */
-    private void warnClickListeners(ClickAreaRepresentation clickArea) {
+    private void warnClickListeners(@NonNull ClickAreaRepresentation clickArea) {
         for (ClickCallbacks listener : mClickListeners) {
             listener.click(clickArea.mId, clickArea.mMetadata);
         }
     }
 
+    /**
+     * Returns true if the document has touch listeners
+     *
+     * @return true if the document needs to react to touch events
+     */
+    public boolean hasTouchListener() {
+        boolean hasComponentsTouchListeners =
+                mRootLayoutComponent != null && mRootLayoutComponent.hasTouchListeners();
+        return hasComponentsTouchListeners || !mTouchListeners.isEmpty();
+    }
+
+    // TODO support velocity estimate support, support regions
+    /**
+     * Support touch drag events on commands supporting touch
+     *
+     * @param x position of touch
+     * @param y position of touch
+     */
+    public boolean touchDrag(RemoteContext context, float x, float y) {
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_X, x);
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_Y, y);
+        for (TouchListener clickArea : mTouchListeners) {
+            clickArea.touchDrag(context, x, y);
+        }
+        if (!mTouchListeners.isEmpty()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Support touch down events on commands supporting touch
+     *
+     * @param x position of touch
+     * @param y position of touch
+     */
+    public void touchDown(RemoteContext context, float x, float y) {
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_X, x);
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_Y, y);
+        for (TouchListener clickArea : mTouchListeners) {
+            clickArea.touchDown(context, x, y);
+        }
+        if (mRootLayoutComponent != null) {
+            mRootLayoutComponent.onTouchDown(context, this, x, y);
+        }
+        mRepaintNext = 1;
+    }
+
+    /**
+     * Support touch up events on commands supporting touch
+     *
+     * @param x position of touch
+     * @param y position of touch
+     */
+    public void touchUp(RemoteContext context, float x, float y, float dx, float dy) {
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_X, x);
+        context.loadFloat(RemoteContext.ID_TOUCH_POS_Y, y);
+        for (TouchListener clickArea : mTouchListeners) {
+            clickArea.touchUp(context, x, y, dx, dy);
+        }
+        if (mRootLayoutComponent != null) {
+            for (Component component : mAppliedTouchOperations) {
+                component.onTouchUp(context, this, x, y, true);
+            }
+            mAppliedTouchOperations.clear();
+        }
+        mRepaintNext = 1;
+    }
+
+    /**
+     * Support touch cancel events on commands supporting touch
+     *
+     * @param x position of touch
+     * @param y position of touch
+     */
+    public void touchCancel(RemoteContext context, float x, float y, float dx, float dy) {
+        if (mRootLayoutComponent != null) {
+            for (Component component : mAppliedTouchOperations) {
+                component.onTouchCancel(context, this, x, y, true);
+            }
+            mAppliedTouchOperations.clear();
+        }
+        mRepaintNext = 1;
+    }
+
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
@@ -721,12 +879,22 @@
      *
      * @return array of named colors or null
      */
+    @Nullable
     public String[] getNamedColors() {
+        return getNamedVariables(NamedVariable.COLOR_TYPE);
+    }
+
+    /**
+     * Gets the names of all named Variables.
+     *
+     * @return array of named variables or null
+     */
+    public String[] getNamedVariables(int type) {
         int count = 0;
         for (Operation op : mOperations) {
             if (op instanceof NamedVariable) {
                 NamedVariable n = (NamedVariable) op;
-                if (n.mVarType == NamedVariable.COLOR_TYPE) {
+                if (n.mVarType == type) {
                     count++;
                 }
             }
@@ -739,7 +907,7 @@
         for (Operation op : mOperations) {
             if (op instanceof NamedVariable) {
                 NamedVariable n = (NamedVariable) op;
-                if (n.mVarType == NamedVariable.COLOR_TYPE) {
+                if (n.mVarType == type) {
                     ret[i++] = n.mVarName;
                 }
             }
@@ -770,10 +938,9 @@
      * @param context the provided PaintContext
      * @param theme the theme we want to use for this document.
      */
-    public void paint(RemoteContext context, int theme) {
+    public void paint(@NonNull RemoteContext context, int theme) {
         context.getPaintContext().clearNeedsRepaint();
         context.mMode = RemoteContext.ContextMode.UNSET;
-
         // current theme starts as UNSPECIFIED, until a Theme setter
         // operation gets executed and modify it.
         context.setTheme(Theme.UNSPECIFIED);
@@ -807,6 +974,7 @@
             }
             // TODO -- this should be specifically about applying animation, not paint
             mRootLayoutComponent.paint(context.getPaintContext());
+            context.mPaintContext.reset();
             // TODO -- should be able to remove this
             mRootLayoutComponent.updateVariables(context);
             if (DEBUG) {
@@ -843,6 +1011,7 @@
         }
     }
 
+    @NonNull
     public String[] getStats() {
         ArrayList<String> ret = new ArrayList<>();
         WireBuffer buffer = new WireBuffer();
@@ -875,7 +1044,7 @@
         return ret.toArray(new String[0]);
     }
 
-    private int sizeOfComponent(Operation com, WireBuffer tmp) {
+    private int sizeOfComponent(@NonNull Operation com, @NonNull WireBuffer tmp) {
         tmp.reset(100);
         com.write(tmp);
         int size = tmp.getSize();
@@ -883,7 +1052,8 @@
         return size;
     }
 
-    private int addChildren(Component base, HashMap<String, int[]> map, WireBuffer tmp) {
+    private int addChildren(
+            @NonNull Component base, @NonNull HashMap<String, int[]> map, @NonNull WireBuffer tmp) {
         int count = base.mList.size();
         for (Operation mOperation : base.mList) {
             Class<? extends Operation> c = mOperation.getClass();
@@ -903,6 +1073,7 @@
         return count;
     }
 
+    @NonNull
     public String toNestedString() {
         StringBuilder ret = new StringBuilder();
         for (Operation mOperation : mOperations) {
@@ -915,7 +1086,8 @@
         return ret.toString();
     }
 
-    private void toNestedString(Component base, StringBuilder ret, String indent) {
+    private void toNestedString(
+            @NonNull Component base, @NonNull StringBuilder ret, String indent) {
         for (Operation mOperation : base.mList) {
             ret.append(mOperation.toString());
             ret.append("\n");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operation.java b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
index 9f565a2..f1885f9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.Nullable;
+
 /** Base interface for RemoteCompose operations */
 public interface Operation {
 
@@ -29,5 +31,6 @@
     void apply(RemoteContext context);
 
     /** Debug utility to display an operation + indentation */
+    @Nullable
     String deepToString(String indent);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index acebe07..53c45fa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.operations.BitmapData;
 import com.android.internal.widget.remotecompose.core.operations.ClickArea;
 import com.android.internal.widget.remotecompose.core.operations.ClipPath;
@@ -65,15 +67,19 @@
 import com.android.internal.widget.remotecompose.core.operations.TextMeasure;
 import com.android.internal.widget.remotecompose.core.operations.TextMerge;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
 import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent;
-import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentStart;
 import com.android.internal.widget.remotecompose.core.operations.layout.LayoutComponentContent;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.LoopOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.OperationsListEnd;
 import com.android.internal.widget.remotecompose.core.operations.layout.RootLayoutComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchCancelModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchDownModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchUpModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.animation.AnimationSpec;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.BoxLayout;
 import com.android.internal.widget.remotecompose.core.operations.layout.managers.CanvasLayout;
@@ -85,15 +91,19 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HostNamedActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueFloatChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueIntegerExpressionChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ValueStringChangeActionOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.utilities.IntMap;
 import com.android.internal.widget.remotecompose.core.types.BooleanConstant;
 import com.android.internal.widget.remotecompose.core.types.IntegerConstant;
@@ -165,6 +175,7 @@
     public static final int DATA_MAP_LOOKUP = 154;
     public static final int TEXT_MEASURE = 155;
     public static final int TEXT_LENGTH = 156;
+    public static final int TOUCH_EXPRESSION = 157;
 
     ///////////////////////////////////////// ======================
 
@@ -194,8 +205,16 @@
     public static final int MODIFIER_ROUNDED_CLIP_RECT = 54;
 
     public static final int MODIFIER_CLICK = 59;
+    public static final int MODIFIER_TOUCH_DOWN = 219;
+    public static final int MODIFIER_TOUCH_UP = 220;
+    public static final int MODIFIER_TOUCH_CANCEL = 225;
 
-    public static final int MODIFIER_CLICK_END = 214;
+    public static final int OPERATIONS_LIST_END = 214;
+
+    public static final int MODIFIER_OFFSET = 221;
+    public static final int MODIFIER_ZINDEX = 223;
+    public static final int MODIFIER_GRAPHICS_LAYER = 224;
+
     public static final int LOOP_START = 215;
     public static final int LOOP_END = 216;
 
@@ -206,12 +225,13 @@
     public static final int VALUE_INTEGER_CHANGE_ACTION = 212;
     public static final int VALUE_STRING_CHANGE_ACTION = 213;
     public static final int VALUE_INTEGER_EXPRESSION_CHANGE_ACTION = 218;
+    public static final int VALUE_FLOAT_CHANGE_ACTION = 222;
 
     public static final int ANIMATION_SPEC = 14;
 
     public static final int COMPONENT_VALUE = 150;
 
-    public static UniqueIntMap<CompanionOperation> map = new UniqueIntMap<>();
+    @NonNull public static UniqueIntMap<CompanionOperation> map = new UniqueIntMap<>();
 
     static class UniqueIntMap<T> extends IntMap<T> {
         @Override
@@ -289,8 +309,16 @@
         map.put(MODIFIER_ROUNDED_CLIP_RECT, RoundedClipRectModifierOperation::read);
         map.put(MODIFIER_CLIP_RECT, ClipRectModifierOperation::read);
         map.put(MODIFIER_CLICK, ClickModifierOperation::read);
-        map.put(MODIFIER_CLICK_END, ClickModifierEnd::read);
+        map.put(MODIFIER_TOUCH_DOWN, TouchDownModifierOperation::read);
+        map.put(MODIFIER_TOUCH_UP, TouchUpModifierOperation::read);
+        map.put(MODIFIER_TOUCH_CANCEL, TouchCancelModifierOperation::read);
         map.put(MODIFIER_VISIBILITY, ComponentVisibilityOperation::read);
+        map.put(MODIFIER_OFFSET, OffsetModifierOperation::read);
+        map.put(MODIFIER_ZINDEX, ZIndexModifierOperation::read);
+        map.put(MODIFIER_GRAPHICS_LAYER, GraphicsLayerModifierOperation::read);
+
+        map.put(OPERATIONS_LIST_END, OperationsListEnd::read);
+
         map.put(HOST_ACTION, HostActionOperation::read);
         map.put(HOST_NAMED_ACTION, HostNamedActionOperation::read);
         map.put(VALUE_INTEGER_CHANGE_ACTION, ValueIntegerChangeActionOperation::read);
@@ -298,6 +326,7 @@
                 VALUE_INTEGER_EXPRESSION_CHANGE_ACTION,
                 ValueIntegerExpressionChangeActionOperation::read);
         map.put(VALUE_STRING_CHANGE_ACTION, ValueStringChangeActionOperation::read);
+        map.put(VALUE_FLOAT_CHANGE_ACTION, ValueFloatChangeActionOperation::read);
 
         map.put(LAYOUT_ROOT, RootLayoutComponent::read);
         map.put(LAYOUT_CONTENT, LayoutComponentContent::read);
@@ -315,5 +344,6 @@
         map.put(DATA_MAP_LOOKUP, DataMapLookup::read);
         map.put(TEXT_MEASURE, TextMeasure::read);
         map.put(TEXT_LENGTH, TextLength::read);
+        map.put(TOUCH_EXPRESSION, TouchExpression::read);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
index 13d6f78..1a71afe 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintContext.java
@@ -271,4 +271,24 @@
     public void needsRepaint() {
         mNeedsRepaint = true;
     }
+
+    public abstract void startGraphicsLayer(int w, int h);
+
+    public abstract void setGraphicsLayer(
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            int renderEffectId);
+
+    public abstract void endGraphicsLayer();
+
+    public boolean isVisualDebug() {
+        return mContext.isVisualDebug();
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
index 9b7b50f..049e477 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/PaintOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 /**
  * PaintOperation interface, used for operations aimed at painting (while any operation _can_ paint,
  * this make it a little more explicit)
@@ -22,7 +24,7 @@
 public abstract class PaintOperation implements Operation {
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         if (context.getMode() == RemoteContext.ContextMode.PAINT) {
             PaintContext paintContext = context.getPaintContext();
             if (paintContext != null) {
@@ -31,6 +33,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Platform.java b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
index 6725e7e..7fbcfae 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Platform.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Platform.java
@@ -15,16 +15,30 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.Nullable;
+
 /** Services that are needed to be provided by the platform during encoding. */
 public interface Platform {
+    @Nullable
     byte[] imageToByteArray(Object image);
 
     int getImageWidth(Object image);
 
     int getImageHeight(Object image);
 
+    @Nullable
     float[] pathToFloatArray(Object path);
 
+    enum LogCategory {
+        DEBUG,
+        INFO,
+        WARN,
+        ERROR,
+        TODO,
+    }
+
+    void log(LogCategory category, String message);
+
     Platform None =
             new Platform() {
                 @Override
@@ -46,5 +60,8 @@
                 public float[] pathToFloatArray(Object path) {
                     throw new UnsupportedOperationException();
                 }
+
+                @Override
+                public void log(LogCategory category, String message) {}
             };
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index 5b5adc2..7d9439d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.operations.BitmapData;
 import com.android.internal.widget.remotecompose.core.operations.ClickArea;
 import com.android.internal.widget.remotecompose.core.operations.ClipPath;
@@ -64,6 +67,7 @@
 import com.android.internal.widget.remotecompose.core.operations.TextMeasure;
 import com.android.internal.widget.remotecompose.core.operations.TextMerge;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
+import com.android.internal.widget.remotecompose.core.operations.TouchExpression;
 import com.android.internal.widget.remotecompose.core.operations.Utils;
 import com.android.internal.widget.remotecompose.core.operations.layout.CanvasContent;
 import com.android.internal.widget.remotecompose.core.operations.layout.ComponentEnd;
@@ -81,8 +85,11 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BackgroundModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.BorderModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.OffsetModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.RoundedClipRectModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
 import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
@@ -111,7 +118,7 @@
     public static final int EASING_EASE_OUT_BOUNCE = FloatAnimation.EASE_OUT_BOUNCE;
     public static final int EASING_EASE_OUT_ELASTIC = FloatAnimation.EASE_OUT_ELASTIC;
     WireBuffer mBuffer = new WireBuffer();
-    Platform mPlatform = null;
+    @Nullable Platform mPlatform = null;
     RemoteComposeState mRemoteComposeState;
     private static final boolean DEBUG = false;
 
@@ -143,6 +150,7 @@
         return mLastComponentId;
     }
 
+    @Nullable
     public Platform getPlatform() {
         return mPlatform;
     }
@@ -172,7 +180,11 @@
      * @param capabilities bitmask indicating needed capabilities (unused for now)
      */
     public void header(
-            int width, int height, String contentDescription, float density, long capabilities) {
+            int width,
+            int height,
+            @Nullable String contentDescription,
+            float density,
+            long capabilities) {
         Header.apply(mBuffer, width, height, density, capabilities);
         int contentDescriptionId = 0;
         if (contentDescription != null) {
@@ -219,7 +231,7 @@
             int dstTop,
             int dstRight,
             int dstBottom,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
@@ -267,7 +279,7 @@
      *
      * @param text the string to inject in the buffer
      */
-    public int addText(String text) {
+    public int addText(@NonNull String text) {
         int id = mRemoteComposeState.dataGetId(text);
         if (id == -1) {
             id = mRemoteComposeState.cacheData(text);
@@ -289,12 +301,12 @@
      */
     public void addClickArea(
             int id,
-            String contentDescription,
+            @Nullable String contentDescription,
             float left,
             float top,
             float right,
             float bottom,
-            String metadata) {
+            @Nullable String metadata) {
         int contentDescriptionId = 0;
         if (contentDescription != null) {
             contentDescriptionId = addText(contentDescription);
@@ -380,7 +392,7 @@
             float top,
             float right,
             float bottom,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
@@ -411,7 +423,7 @@
             float top,
             float right,
             float bottom,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int contentDescriptionId = 0;
         if (contentDescription != null) {
             contentDescriptionId = addText(contentDescription);
@@ -445,7 +457,7 @@
             float dstBottom,
             int scaleType,
             float scaleFactor,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
@@ -500,7 +512,7 @@
      * @param image drawScaledBitmap
      * @return id of the image useful with
      */
-    public int addBitmap(Object image, String name) {
+    public int addBitmap(Object image, @NonNull String name) {
         int imageId = mRemoteComposeState.dataGetId(image);
         if (imageId == -1) {
             imageId = mRemoteComposeState.cacheData(image);
@@ -521,7 +533,7 @@
      * @param id of the Bitmap
      * @param name Name of the color
      */
-    public void setBitmapName(int id, String name) {
+    public void setBitmapName(int id, @NonNull String name) {
         NamedVariable.apply(mBuffer, id, NamedVariable.IMAGE_TYPE, name);
     }
 
@@ -551,7 +563,7 @@
             float dstBottom,
             int scaleType,
             float scaleFactor,
-            String contentDescription) {
+            @Nullable String contentDescription) {
         int contentDescriptionId = 0;
         if (contentDescription != null) {
             contentDescriptionId = addText(contentDescription);
@@ -669,7 +681,7 @@
      * @param hOffset The distance along the path to add to the text's starting position
      * @param vOffset The distance above(-) or below(+) the path to position the text
      */
-    public void addDrawTextOnPath(String text, Object path, float hOffset, float vOffset) {
+    public void addDrawTextOnPath(@NonNull String text, Object path, float hOffset, float vOffset) {
         int pathId = mRemoteComposeState.dataGetId(path);
         if (pathId == -1) { // never been seen before
             pathId = addPathData(path);
@@ -692,7 +704,7 @@
      * @param rtl Draw RTTL
      */
     public void addDrawTextRun(
-            String text,
+            @NonNull String text,
             int start,
             int end,
             int contextStart,
@@ -749,7 +761,8 @@
      * @param panY position text -1.0=above, 0.0=center, 1.0=below, Nan=baseline
      * @param flags 1 = RTL
      */
-    public void drawTextAnchored(String text, float x, float y, float panX, float panY, int flags) {
+    public void drawTextAnchored(
+            @NonNull String text, float x, float y, float panX, float panY, int flags) {
         int textId = addText(text);
         DrawTextAnchored.apply(mBuffer, textId, x, y, panX, panY, flags);
     }
@@ -760,7 +773,7 @@
      * @param text
      * @return
      */
-    public int createTextId(String text) {
+    public int createTextId(@NonNull String text) {
         return addText(text);
     }
 
@@ -891,7 +904,7 @@
      *
      * @param paint
      */
-    public void addPaint(PaintBundle paint) {
+    public void addPaint(@NonNull PaintBundle paint) {
         PaintData.apply(mBuffer, paint);
     }
 
@@ -912,7 +925,8 @@
         }
     }
 
-    public static void readNextOperation(WireBuffer buffer, ArrayList<Operation> operations) {
+    public static void readNextOperation(
+            @NonNull WireBuffer buffer, ArrayList<Operation> operations) {
         int opId = buffer.readByte();
         if (DEBUG) {
             Utils.log(">> " + opId);
@@ -924,6 +938,7 @@
         operation.read(buffer, operations);
     }
 
+    @NonNull
     RemoteComposeBuffer copy() {
         ArrayList<Operation> operations = new ArrayList<>();
         inflateFromBuffer(operations);
@@ -935,33 +950,38 @@
         Theme.apply(mBuffer, theme);
     }
 
+    @NonNull
     static String version() {
         return "v1.0";
     }
 
-    public static RemoteComposeBuffer fromFile(String path, RemoteComposeState remoteComposeState)
-            throws IOException {
+    @NonNull
+    public static RemoteComposeBuffer fromFile(
+            @NonNull String path, RemoteComposeState remoteComposeState) throws IOException {
         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
         read(new File(path), buffer);
         return buffer;
     }
 
-    public RemoteComposeBuffer fromFile(File file, RemoteComposeState remoteComposeState)
+    @NonNull
+    public RemoteComposeBuffer fromFile(@NonNull File file, RemoteComposeState remoteComposeState)
             throws IOException {
         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
         read(file, buffer);
         return buffer;
     }
 
+    @NonNull
     public static RemoteComposeBuffer fromInputStream(
-            InputStream inputStream, RemoteComposeState remoteComposeState) {
+            @NonNull InputStream inputStream, RemoteComposeState remoteComposeState) {
         RemoteComposeBuffer buffer = new RemoteComposeBuffer(remoteComposeState);
         read(inputStream, buffer);
         return buffer;
     }
 
+    @NonNull
     RemoteComposeBuffer copyFromOperations(
-            ArrayList<Operation> operations, RemoteComposeBuffer buffer) {
+            @NonNull ArrayList<Operation> operations, @NonNull RemoteComposeBuffer buffer) {
 
         for (Operation operation : operations) {
             operation.write(buffer.mBuffer);
@@ -975,7 +995,7 @@
      * @param buffer a RemoteComposeBuffer
      * @param file a target file
      */
-    public void write(RemoteComposeBuffer buffer, File file) {
+    public void write(@NonNull RemoteComposeBuffer buffer, @NonNull File file) {
         try {
             FileOutputStream fd = new FileOutputStream(file);
             fd.write(buffer.mBuffer.getBuffer(), 0, buffer.mBuffer.getSize());
@@ -986,12 +1006,12 @@
         }
     }
 
-    static void read(File file, RemoteComposeBuffer buffer) throws IOException {
+    static void read(@NonNull File file, @NonNull RemoteComposeBuffer buffer) throws IOException {
         FileInputStream fd = new FileInputStream(file);
         read(fd, buffer);
     }
 
-    public static void read(InputStream fd, RemoteComposeBuffer buffer) {
+    public static void read(@NonNull InputStream fd, @NonNull RemoteComposeBuffer buffer) {
         try {
             byte[] bytes = readAllBytes(fd);
             buffer.reset(bytes.length);
@@ -1002,7 +1022,7 @@
         }
     }
 
-    private static byte[] readAllBytes(InputStream is) throws IOException {
+    private static byte[] readAllBytes(@NonNull InputStream is) throws IOException {
         byte[] buff = new byte[32 * 1024]; // moderate size buff to start
         int red = 0;
         while (true) {
@@ -1176,20 +1196,59 @@
      * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7
      * @return NaN id of the result of the calculation
      */
-    public float addAnimatedFloat(float... value) {
+    public float addAnimatedFloat(@NonNull float... value) {
         int id = mRemoteComposeState.cacheData(value);
         FloatExpression.apply(mBuffer, id, value, null);
         return Utils.asNan(id);
     }
 
     /**
+     * Add a touch handle system
+     *
+     * @param value the default value
+     * @param min the minimum value
+     * @param max the maximum value
+     * @param velocityId the id for the velocity TODO support in v2
+     * @param exp The Float Expression
+     * @param touchMode the touch up handling behaviour
+     * @param touchSpec the touch up handling parameters
+     * @param easingSpec the easing parameter TODO support in v2
+     * @return id of the variable to be used controlled by touch handling
+     */
+    public float addTouchExpression(
+            float value,
+            float min,
+            float max,
+            float velocityId,
+            int touchEffects,
+            float[] exp,
+            int touchMode,
+            float[] touchSpec,
+            float[] easingSpec) {
+        int id = mRemoteComposeState.nextId();
+        TouchExpression.apply(
+                mBuffer,
+                id,
+                value,
+                min,
+                max,
+                velocityId,
+                touchEffects,
+                exp,
+                touchMode,
+                touchSpec,
+                easingSpec);
+        return Utils.asNan(id);
+    }
+
+    /**
      * Add a float that is a computation based on variables. see packAnimation
      *
      * @param value A RPN style float operation i.e. "4, 3, ADD" outputs 7
      * @param animation Array of floats that represents animation
      * @return NaN id of the result of the calculation
      */
-    public float addAnimatedFloat(float[] value, float[] animation) {
+    public float addAnimatedFloat(@NonNull float[] value, float[] animation) {
         int id = mRemoteComposeState.cacheData(value);
         FloatExpression.apply(mBuffer, id, value, animation);
         return Utils.asNan(id);
@@ -1228,7 +1287,7 @@
      * @param values
      * @return the id of the array, encoded as a float NaN
      */
-    public float addFloatArray(float[] values) {
+    public float addFloatArray(@NonNull float[] values) {
         int id = mRemoteComposeState.cacheData(values, NanMap.TYPE_ARRAY);
         DataListFloat.apply(mBuffer, id, values);
         return Utils.asNan(id);
@@ -1240,7 +1299,7 @@
      * @param values array of floats to be individually stored
      * @return id of the list
      */
-    public float addFloatList(float[] values) {
+    public float addFloatList(@NonNull float[] values) {
         int[] listId = new int[values.length];
         for (int i = 0; i < listId.length; i++) {
             listId[i] = mRemoteComposeState.cacheFloat(values[i]);
@@ -1255,7 +1314,7 @@
      * @param listId array id to be stored
      * @return id of the list
      */
-    public float addList(int[] listId) {
+    public float addList(@NonNull int[] listId) {
         int id = mRemoteComposeState.cacheData(listId, NanMap.TYPE_ARRAY);
         DataListIds.apply(mBuffer, id, listId);
         return Utils.asNan(id);
@@ -1268,7 +1327,7 @@
      * @param values
      * @return the id of the map, encoded as a float NaN
      */
-    public float addFloatMap(String[] keys, float[] values) {
+    public float addFloatMap(@NonNull String[] keys, @NonNull float[] values) {
         int[] listId = new int[values.length];
         byte[] type = new byte[values.length];
         for (int i = 0; i < listId.length; i++) {
@@ -1286,7 +1345,7 @@
      * @param listId
      * @return the id of the map, encoded as a float NaN
      */
-    public int addMap(String[] keys, byte[] types, int[] listId) {
+    public int addMap(@NonNull String[] keys, byte[] types, int[] listId) {
         int id = mRemoteComposeState.cacheData(listId, NanMap.TYPE_ARRAY);
         DataMapIds.apply(mBuffer, id, keys, types, listId);
         return id;
@@ -1331,7 +1390,7 @@
      * @param value array of values to calculate maximum 32
      * @return the id as an integer
      */
-    public int addIntegerExpression(int mask, int[] value) {
+    public int addIntegerExpression(int mask, @NonNull int[] value) {
         int id = mRemoteComposeState.cacheData(value);
         IntegerExpression.apply(mBuffer, id, mask, value);
         return id;
@@ -1474,7 +1533,7 @@
      * @param id of the color
      * @param name Name of the color
      */
-    public void setColorName(int id, String name) {
+    public void setColorName(int id, @NonNull String name) {
         NamedVariable.apply(mBuffer, id, NamedVariable.COLOR_TYPE, name);
     }
 
@@ -1484,7 +1543,7 @@
      * @param id of the string
      * @param name name of the string
      */
-    public void setStringName(int id, String name) {
+    public void setStringName(int id, @NonNull String name) {
         NamedVariable.apply(mBuffer, id, NamedVariable.STRING_TYPE, name);
     }
 
@@ -1576,6 +1635,72 @@
     }
 
     /**
+     * Add an offset modifier
+     *
+     * @param x x offset
+     * @param y y offset
+     */
+    public void addModifierOffset(float x, float y) {
+        OffsetModifierOperation.apply(mBuffer, x, y);
+    }
+
+    /**
+     * Add a zIndex modifier
+     *
+     * @param value z-Index value
+     */
+    public void addModifierZIndex(float value) {
+        ZIndexModifierOperation.apply(mBuffer, value);
+    }
+
+    /**
+     * Add a graphics layer
+     *
+     * @param scaleX
+     * @param scaleY
+     * @param rotationX
+     * @param rotationY
+     * @param rotationZ
+     * @param shadowElevation
+     * @param transformOriginX
+     * @param transformOriginY
+     */
+    public void addModifierGraphicsLayer(
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            float cameraDistance,
+            int blendMode,
+            int spotShadowColorId,
+            int ambientShadowColorId,
+            int colorFilterId,
+            int renderEffectId) {
+        GraphicsLayerModifierOperation.apply(
+                mBuffer,
+                scaleX,
+                scaleY,
+                rotationX,
+                rotationY,
+                rotationZ,
+                shadowElevation,
+                transformOriginX,
+                transformOriginY,
+                alpha,
+                cameraDistance,
+                blendMode,
+                spotShadowColorId,
+                ambientShadowColorId,
+                colorFilterId,
+                renderEffectId);
+    }
+
+    /**
      * Sets the clip based on rounded clip rect
      *
      * @param topStart
@@ -1721,7 +1846,8 @@
             float fontSize,
             int fontStyle,
             float fontWeight,
-            String fontFamily) {
+            @Nullable String fontFamily,
+            int textAlign) {
         mLastComponentId = getComponentId(componentId);
         int fontFamilyId = -1;
         if (fontFamily != null) {
@@ -1736,6 +1862,11 @@
                 fontSize,
                 fontStyle,
                 fontWeight,
-                fontFamilyId);
+                fontFamilyId,
+                textAlign);
+    }
+
+    public int createID(int type) {
+        return mRemoteComposeState.nextId(type);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 51445f2..3039328 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.operations.utilities.ArrayAccess;
 import com.android.internal.widget.remotecompose.core.operations.utilities.CollectionsAccess;
 import com.android.internal.widget.remotecompose.core.operations.utilities.DataMap;
@@ -50,10 +53,11 @@
 
     private final boolean[] mDataOverride = new boolean[MAX_DATA];
     private final boolean[] mIntegerOverride = new boolean[MAX_DATA];
+    private final boolean[] mFloatOverride = new boolean[MAX_DATA];
 
     private int mNextId = START_ID;
-    private int[] mIdMaps = new int[] {START_ID, NanMap.START_VAR, NanMap.START_ARRAY};
-    private RemoteContext mRemoteContext = null;
+    @NonNull private int[] mIdMaps = new int[] {START_ID, NanMap.START_VAR, NanMap.START_ARRAY};
+    @Nullable private RemoteContext mRemoteContext = null;
 
     /**
      * Get Object based on id. The system will cache things like bitmaps Paths etc. They can be
@@ -62,6 +66,7 @@
      * @param id
      * @return
      */
+    @Nullable
     public Object getFromId(int id) {
         return mIntDataMap.get(id);
     }
@@ -158,10 +163,28 @@
 
     /** Insert an float item in the cache */
     public void updateFloat(int id, float value) {
+        if (!mFloatOverride[id]) {
+            float previous = mFloatMap.get(id);
+            if (previous != value) {
+                mFloatMap.put(id, value);
+                mIntegerMap.put(id, (int) value);
+                updateListeners(id);
+            }
+        }
+    }
+
+    /**
+     * Adds a float Override.
+     *
+     * @param id
+     * @param value the new value
+     */
+    public void overrideFloat(int id, float value) {
         float previous = mFloatMap.get(id);
         if (previous != value) {
             mFloatMap.put(id, value);
             mIntegerMap.put(id, (int) value);
+            mFloatOverride[id] = true;
             updateListeners(id);
         }
     }
@@ -294,6 +317,16 @@
     }
 
     /**
+     * Clear the float override
+     *
+     * @param id the float id to clear
+     */
+    public void clearFloatOverride(int id) {
+        mFloatOverride[id] = false;
+        updateListeners(id);
+    }
+
+    /**
      * Method to determine if a cached value has been written to the documents WireBuffer based on
      * its id.
      */
@@ -322,7 +355,8 @@
     }
 
     /**
-     * Get the next available id
+     * Get the next available id 0 is normal (float,int,String,color) 1 is VARIABLES 2 is
+     * collections
      *
      * @return
      */
@@ -342,8 +376,8 @@
         mNextId = id;
     }
 
-    IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>();
-    ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>();
+    @NonNull IntMap<ArrayList<VariableSupport>> mVarListeners = new IntMap<>();
+    @NonNull ArrayList<VariableSupport> mAllVarListeners = new ArrayList<>();
 
     private void add(int id, VariableSupport variableSupport) {
         ArrayList<VariableSupport> v = mVarListeners.get(id);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 1066e7d..23cc5b8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.ShaderData;
 import com.android.internal.widget.remotecompose.core.operations.Theme;
@@ -40,10 +42,12 @@
     protected CoreDocument mDocument;
     public RemoteComposeState mRemoteComposeState;
     long mStart = System.nanoTime(); // todo This should be set at a hi level
-    protected PaintContext mPaintContext = null;
+    @Nullable protected PaintContext mPaintContext = null;
+    protected float mDensity = 2.75f;
+
     ContextMode mMode = ContextMode.UNSET;
 
-    boolean mDebug = false;
+    int mDebug = 0;
 
     private int mTheme = Theme.UNSPECIFIED;
 
@@ -56,6 +60,14 @@
     public Component lastComponent;
     public long currentTime = 0L;
 
+    public float getDensity() {
+        return mDensity;
+    }
+
+    public void setDensity(float density) {
+        mDensity = density;
+    }
+
     public boolean isAnimationEnabled() {
         return mAnimate;
     }
@@ -173,12 +185,22 @@
 
     public abstract void runAction(int id, String metadata);
 
-    public abstract void runNamedAction(int textId);
+    // TODO: we might add an interface to group all valid parameter types
+    public abstract void runNamedAction(int textId, Object value);
 
     public abstract void putObject(int mId, Object command);
 
     public abstract Object getObject(int mId);
 
+    public void addTouchListener(TouchListener touchExpression) {}
+
+    /**
+     * Vibrate the device
+     *
+     * @param type 0 = none, 1-21 ,see HapticFeedbackConstants
+     */
+    public abstract void hapticEffect(int type);
+
     /**
      * The context can be used in a few different mode, allowing operations to skip being executed:
      * - UNSET : all operations will get executed - DATA : only operations dealing with DATA (eg
@@ -206,6 +228,7 @@
         this.mMode = mode;
     }
 
+    @Nullable
     public PaintContext getPaintContext() {
         return mPaintContext;
     }
@@ -219,10 +242,14 @@
     }
 
     public boolean isDebug() {
-        return mDebug;
+        return mDebug == 1;
     }
 
-    public void setDebug(boolean debug) {
+    public boolean isVisualDebug() {
+        return mDebug == 2;
+    }
+
+    public void setDebug(int debug) {
         this.mDebug = debug;
     }
 
@@ -314,6 +341,14 @@
     public abstract void loadFloat(int id, float value);
 
     /**
+     * Override an existing float value
+     *
+     * @param id
+     * @param value
+     */
+    public abstract void overrideFloat(int id, float value);
+
+    /**
      * Load a integer
      *
      * @param id id of the integer
@@ -321,8 +356,20 @@
      */
     public abstract void loadInteger(int id, int value);
 
+    /**
+     * Override an existing int value
+     *
+     * @param id
+     * @param value
+     */
     public abstract void overrideInteger(int id, int value);
 
+    /**
+     * Override an existing text value
+     *
+     * @param id
+     * @param valueId
+     */
     public abstract void overrideText(int id, int valueId);
 
     /**
@@ -400,6 +447,25 @@
     public static final int ID_OFFSET_TO_UTC = 10;
     public static final int ID_WEEK_DAY = 11;
     public static final int ID_DAY_OF_MONTH = 12;
+    public static final int ID_TOUCH_POS_X = 13;
+    public static final int ID_TOUCH_POS_Y = 14;
+
+    public static final int ID_TOUCH_VEL_X = 15;
+    public static final int ID_TOUCH_VEL_Y = 16;
+
+    public static final int ID_ACCELERATION_X = 17;
+    public static final int ID_ACCELERATION_Y = 18;
+    public static final int ID_ACCELERATION_Z = 19;
+
+    public static final int ID_GYRO_ROT_X = 20;
+    public static final int ID_GYRO_ROT_Y = 21;
+    public static final int ID_GYRO_ROT_Z = 22;
+
+    public static final int ID_MAGNETIC_X = 23;
+    public static final int ID_MAGNETIC_Y = 24;
+    public static final int ID_MAGNETIC_Z = 25;
+
+    public static final int ID_LIGHT = 26;
 
     /** CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600 */
     public static final float FLOAT_CONTINUOUS_SEC = Utils.asNan(ID_CONTINUOUS_SEC);
@@ -426,9 +492,52 @@
     public static final float FLOAT_WINDOW_HEIGHT = Utils.asNan(ID_WINDOW_HEIGHT);
     public static final float FLOAT_COMPONENT_WIDTH = Utils.asNan(ID_COMPONENT_WIDTH);
     public static final float FLOAT_COMPONENT_HEIGHT = Utils.asNan(ID_COMPONENT_HEIGHT);
-    // ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f)
+
+    /** ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f) */
     public static final float FLOAT_OFFSET_TO_UTC = Utils.asNan(ID_OFFSET_TO_UTC);
 
+    /** TOUCH_POS_X is the x position of the touch */
+    public static final float FLOAT_TOUCH_POS_X = Utils.asNan(ID_TOUCH_POS_X);
+
+    /** TOUCH_POS_Y is the y position of the touch */
+    public static final float FLOAT_TOUCH_POS_Y = Utils.asNan(ID_TOUCH_POS_Y);
+
+    /** TOUCH_VEL_X is the x velocity of the touch */
+    public static final float FLOAT_TOUCH_VEL_X = Utils.asNan(ID_TOUCH_VEL_X);
+
+    /** TOUCH_VEL_Y is the x velocity of the touch */
+    public static final float FLOAT_TOUCH_VEL_Y = Utils.asNan(ID_TOUCH_VEL_Y);
+
+    /** X acceleration sensor value in M/s^2 */
+    public static final float FLOAT_ACCELERATION_X = Utils.asNan(ID_ACCELERATION_X);
+
+    /** Y acceleration sensor value in M/s^2 */
+    public static final float FLOAT_ACCELERATION_Y = Utils.asNan(ID_ACCELERATION_Y);
+
+    /** Z acceleration sensor value in M/s^2 */
+    public static final float FLOAT_ACCELERATION_Z = Utils.asNan(ID_ACCELERATION_Z);
+
+    /** X Gyroscope rotation rate sensor value in radians/second */
+    public static final float FLOAT_GYRO_ROT_X = Utils.asNan(ID_GYRO_ROT_X);
+
+    /** Y Gyroscope rotation rate sensor value in radians/second */
+    public static final float FLOAT_GYRO_ROT_Y = Utils.asNan(ID_GYRO_ROT_Y);
+
+    /** Z Gyroscope rotation rate sensor value in radians/second */
+    public static final float FLOAT_GYRO_ROT_Z = Utils.asNan(ID_GYRO_ROT_Z);
+
+    /** Ambient magnetic field in X. sensor value in micro-Tesla (uT) */
+    public static final float FLOAT_MAGNETIC_X = Utils.asNan(ID_MAGNETIC_X);
+
+    /** Ambient magnetic field in Y. sensor value in micro-Tesla (uT) */
+    public static final float FLOAT_MAGNETIC_Y = Utils.asNan(ID_MAGNETIC_Y);
+
+    /** Ambient magnetic field in Z. sensor value in micro-Tesla (uT) */
+    public static final float FLOAT_MAGNETIC_Z = Utils.asNan(ID_MAGNETIC_Z);
+
+    /** Ambient light level in SI lux */
+    public static final float FLOAT_LIGHT = Utils.asNan(ID_LIGHT);
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Click handling
     ///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
index fa0cf3f..14aed2f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 import java.time.LocalDateTime;
 import java.time.OffsetDateTime;
 import java.time.ZoneId;
@@ -27,7 +29,7 @@
      *
      * @param context
      */
-    public void updateTime(RemoteContext context) {
+    public void updateTime(@NonNull RemoteContext context) {
         LocalDateTime dateTime =
                 LocalDateTime.now(ZoneId.systemDefault()); // TODO, pass in a timezone explicitly?
         // This define the time in the format
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
new file mode 100644
index 0000000..3dda678
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/TouchListener.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core;
+
+public interface TouchListener {
+    void touchDown(RemoteContext context, float x, float y);
+
+    void touchUp(RemoteContext context, float x, float y, float dx, float dy);
+
+    void touchDrag(RemoteContext context, float x, float y);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
index c71b490..738e42b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/WireBuffer.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core;
 
+import android.annotation.NonNull;
+
 import java.util.Arrays;
 
 /** The base communication buffer capable of encoding and decoding various types */
@@ -184,11 +186,13 @@
         return b;
     }
 
+    @NonNull
     public String readUTF8() {
         byte[] stringBuffer = readBuffer();
         return new String(stringBuffer);
     }
 
+    @NonNull
     public String readUTF8(int maxSize) {
         byte[] stringBuffer = readBuffer(maxSize);
         return new String(stringBuffer);
@@ -250,7 +254,7 @@
         writeLong(Double.doubleToRawLongBits(value));
     }
 
-    public void writeBuffer(byte[] b) {
+    public void writeBuffer(@NonNull byte[] b) {
         resize(b.length + 4);
         writeInt(b.length);
         for (int i = 0; i < b.length; i++) {
@@ -259,7 +263,7 @@
         mSize += b.length;
     }
 
-    public void writeUTF8(String content) {
+    public void writeUTF8(@NonNull String content) {
         byte[] buffer = content.getBytes();
         writeBuffer(buffer);
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java
index c33ae24..5edecaa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/DocumentedOperation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.documentation;
 
+import android.annotation.NonNull;
+
 import java.util.ArrayList;
 
 public class DocumentedOperation {
@@ -40,12 +42,13 @@
     boolean mWIP;
     String mTextExamples;
 
-    ArrayList<StringPair> mExamples = new ArrayList<>();
-    ArrayList<OperationField> mFields = new ArrayList<>();
-    String mVarSize = "";
+    @NonNull ArrayList<StringPair> mExamples = new ArrayList<>();
+    @NonNull ArrayList<OperationField> mFields = new ArrayList<>();
+    @NonNull String mVarSize = "";
     int mExamplesWidth = 100;
     int mExamplesHeight = 100;
 
+    @NonNull
     public static String getType(int type) {
         switch (type) {
             case INT:
@@ -85,6 +88,7 @@
         this(category, id, name, false);
     }
 
+    @NonNull
     public ArrayList<OperationField> getFields() {
         return mFields;
     }
@@ -105,6 +109,7 @@
         return mWIP;
     }
 
+    @NonNull
     public String getVarSize() {
         return mVarSize;
     }
@@ -129,6 +134,7 @@
         return mTextExamples;
     }
 
+    @NonNull
     public ArrayList<StringPair> getExamples() {
         return mExamples;
     }
@@ -141,16 +147,19 @@
         return mExamplesHeight;
     }
 
+    @NonNull
     public DocumentedOperation field(int type, String name, String description) {
         mFields.add(new OperationField(type, name, description));
         return this;
     }
 
+    @NonNull
     public DocumentedOperation field(int type, String name, String varSize, String description) {
         mFields.add(new OperationField(type, name, varSize, description));
         return this;
     }
 
+    @NonNull
     public DocumentedOperation possibleValues(String name, int value) {
         if (!mFields.isEmpty()) {
             mFields.get(mFields.size() - 1).possibleValue(name, "" + value);
@@ -158,21 +167,25 @@
         return this;
     }
 
+    @NonNull
     public DocumentedOperation description(String description) {
         mDescription = description;
         return this;
     }
 
+    @NonNull
     public DocumentedOperation examples(String examples) {
         mTextExamples = examples;
         return this;
     }
 
+    @NonNull
     public DocumentedOperation exampleImage(String name, String imagePath) {
         mExamples.add(new StringPair(name, imagePath));
         return this;
     }
 
+    @NonNull
     public DocumentedOperation examplesDimension(int width, int height) {
         mExamplesWidth = width;
         mExamplesHeight = height;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
index c770483..cbb5ca9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/documentation/OperationField.java
@@ -15,15 +15,18 @@
  */
 package com.android.internal.widget.remotecompose.core.documentation;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import java.util.ArrayList;
 
 public class OperationField {
     int mType;
     String mName;
     String mDescription;
-    String mVarSize = null;
+    @Nullable String mVarSize = null;
 
-    ArrayList<StringPair> mPossibleValues = new ArrayList<>();
+    @NonNull ArrayList<StringPair> mPossibleValues = new ArrayList<>();
 
     public OperationField(int type, String name, String description) {
         mType = type;
@@ -50,6 +53,7 @@
         return mDescription;
     }
 
+    @NonNull
     public ArrayList<StringPair> getPossibleValues() {
         return mPossibleValues;
     }
@@ -62,6 +66,7 @@
         return !mPossibleValues.isEmpty();
     }
 
+    @Nullable
     public String getVarSize() {
         return mVarSize;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
index 20ba8c31..8da0e18 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/BitmapData.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -58,15 +60,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mImageId, mImageWidth, mImageHeight, mBitmap);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BITMAP DATA " + mImageId;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -75,7 +79,12 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int imageId, int width, int height, byte[] bitmap) {
+    public static void apply(
+            @NonNull WireBuffer buffer,
+            int imageId,
+            int width,
+            int height,
+            @NonNull byte[] bitmap) {
         buffer.start(OP_CODE);
         buffer.writeInt(imageId);
         buffer.writeInt(width);
@@ -83,7 +92,7 @@
         buffer.writeBuffer(bitmap);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int width = buffer.readInt();
         int height = buffer.readInt();
@@ -97,7 +106,7 @@
         operations.add(new BitmapData(imageId, width, height, bitmap));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Bitmap data")
                 .field(DocumentedOperation.INT, "id", "id of bitmap data")
@@ -107,17 +116,18 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadBitmap(mImageId, mImageWidth, mImageHeight, mBitmap);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 CLASS_NAME + " id " + mImageId + " (" + mImageWidth + "x" + mImageHeight + ")");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
index 8b9e5a8..83d0ac7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClickArea.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
@@ -67,10 +69,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "CLICK_AREA <"
@@ -97,18 +100,20 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         if (context.getMode() != RemoteContext.ContextMode.DATA) {
             return;
         }
         context.addClickArea(mId, mContentDescription, mLeft, mTop, mRight, mBottom, mMetadata);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -118,7 +123,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int id,
             int contentDescription,
             float left,
@@ -136,7 +141,7 @@
         buffer.writeInt(metadata);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int contentDescription = buffer.readInt();
         float left = buffer.readFloat();
@@ -149,7 +154,7 @@
         operations.add(clickArea);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Define a region you can click on")
                 .field(DocumentedOperation.FLOAT, "left", "The left side of the region")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
index 96b600a..db93829 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipPath.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -57,16 +59,17 @@
     public static final int UNDEFINED = PATH_CLIP_UNDEFINED;
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ClipPath " + mId + ";";
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int pack = buffer.readInt();
         int id = pack & 0xFFFFF;
         int regionOp = pack >> 24;
@@ -74,6 +77,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -82,19 +86,19 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int id) {
+    public static void apply(@NonNull WireBuffer buffer, int id) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Intersect the current clip with the path")
                 .field(DocumentedOperation.INT, "id", "id of the path");
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.clipPath(mId, mRegionOp);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
index b101bfb..df54fb1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ClipRect.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -29,7 +31,7 @@
     public static final int OP_CODE = Operations.CLIP_RECT;
     public static final String CLASS_NAME = "ClipRect";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = ClipRect::new;
         read(m, buffer, operations);
     }
@@ -38,16 +40,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Intersect the current clip with rectangle")
                 .field(
@@ -74,7 +77,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.clipRect(mX1, mY1, mX2, mY2);
     }
 
@@ -87,7 +90,7 @@
      * @param x2 end x of the DrawOval
      * @param y2 end y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
index 19d80da..929c9a60 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -39,15 +41,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mColorId, mColor);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ColorConstant[" + mColorId + "] = " + Utils.colorInt(mColor) + "";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -63,19 +67,19 @@
      * @param colorId
      * @param color
      */
-    public static void apply(WireBuffer buffer, int colorId, int color) {
+    public static void apply(@NonNull WireBuffer buffer, int colorId, int color) {
         buffer.start(OP_CODE);
         buffer.writeInt(colorId);
         buffer.writeInt(color);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int colorId = buffer.readInt();
         int color = buffer.readInt();
         operations.add(new ColorConstant(colorId, color));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Define a Color")
                 .field(DocumentedOperation.INT, "id", "Id of the color")
@@ -83,10 +87,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadColor(mColorId, mColor);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
index b6041ea..3d840c5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorExpression.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -94,7 +96,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (mMode == 4) {
             if (Float.isNaN(mHue)) {
                 mOutHue = context.getFloat(Utils.idFromNan(mHue));
@@ -118,7 +120,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (mMode == 4) {
             if (Float.isNaN(mHue)) {
                 context.listensTo(Utils.idFromNan(mHue), this);
@@ -143,7 +145,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         if (mMode == 4) {
             context.loadColor(
                     mId, (mAlpha << 24) | (0xFFFFFF & Utils.hsvToRgb(mOutHue, mOutSat, mOutValue)));
@@ -164,11 +166,12 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         int mode = mMode | (mAlpha << 16);
         apply(buffer, mId, mode, mColor1, mColor2, mTween);
     }
 
+    @NonNull
     @Override
     public String toString() {
         if (mMode == 4) {
@@ -196,6 +199,7 @@
                 + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -215,7 +219,7 @@
      * @param tween
      */
     public static void apply(
-            WireBuffer buffer, int id, int mode, int color1, int color2, float tween) {
+            @NonNull WireBuffer buffer, int id, int mode, int color1, int color2, float tween) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(mode);
@@ -224,7 +228,7 @@
         buffer.writeFloat(tween);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int mode = buffer.readInt();
         int color1 = buffer.readInt();
@@ -234,7 +238,7 @@
         operations.add(new ColorExpression(id, mode, color1, color2, tween));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A Color defined by an expression")
                 .field(DocumentedOperation.INT, "id", "Id of the color")
@@ -249,6 +253,7 @@
                 .field(FLOAT, "tween", "32 bit ARGB color");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
index 9929720..142c97b2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ComponentValue.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -43,10 +46,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return CLASS_NAME + "(" + mType + ", " + mComponentID + ", " + mValueId + ")";
@@ -65,7 +70,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mType, mComponentID, mValueId);
     }
 
@@ -74,7 +79,7 @@
         // Nothing
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int type = buffer.readInt();
         int componentId = buffer.readInt();
         int valueId = buffer.readInt();
@@ -82,7 +87,7 @@
         operations.add(op);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a component-related value (eg its width, height etc.)")
                 .field(
@@ -111,20 +116,21 @@
      * @param componentId component id to reference
      * @param valueId remote float used to represent the component value
      */
-    public static void apply(WireBuffer buffer, int type, int componentId, int valueId) {
+    public static void apply(@NonNull WireBuffer buffer, int type, int componentId, int valueId) {
         buffer.start(OP_CODE);
         buffer.writeInt(type);
         buffer.writeInt(componentId);
         buffer.writeInt(valueId);
     }
 
+    @Nullable
     @Override
     public String deepToString(String indent) {
         return null;
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         String type = "WIDTH";
         if (mType == HEIGHT) {
             type = "HEIGHT";
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
index 0075869..ba02b91 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListFloat.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -48,7 +50,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         context.addCollection(mId, this);
         for (float value : mValues) {
             if (Utils.isVariable(value)) {
@@ -58,16 +60,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mValues);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DataListFloat[" + Utils.idString(mId) + "] " + Arrays.toString(mValues);
     }
 
-    public static void apply(WireBuffer buffer, int id, float[] values) {
+    public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] values) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(values.length);
@@ -76,7 +79,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
         if (len > MAX_FLOAT_ARRAY) {
@@ -90,7 +93,7 @@
         operations.add(data);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("a list of Floats")
                 .field(DocumentedOperation.INT, "id", "id the array (2xxxxx)")
@@ -98,13 +101,14 @@
                 .field(FLOAT_ARRAY, "values", "length", "array of floats");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.addCollection(mId, this);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
index c43dab4..b9820f8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataListIds.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -49,16 +51,17 @@
     public void registerListening(RemoteContext context) {}
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mIds);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "map[" + Utils.idString(mId) + "]  \"" + Arrays.toString(mIds) + "\"";
     }
 
-    public static void apply(WireBuffer buffer, int id, int[] ids) {
+    public static void apply(@NonNull WireBuffer buffer, int id, @NonNull int[] ids) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(ids.length);
@@ -67,7 +70,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
         if (len > MAX_LIST) {
@@ -81,7 +84,7 @@
         operations.add(data);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("a list of id's")
                 .field(DocumentedOperation.INT, "id", "id the array")
@@ -89,13 +92,14 @@
                 .field(INT_ARRAY, "ids[n]", "length", "ids of other variables");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.addCollection(mId, this);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
index 75db29d..fb559bb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DataMapIds.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -64,10 +66,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mDataMap.mNames, mDataMap.mTypes, mDataMap.mIds);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder("DataMapIds[" + Utils.idString(mId) + "] ");
@@ -84,7 +87,8 @@
         return builder.toString();
     }
 
-    public static void apply(WireBuffer buffer, int id, String[] names, byte[] type, int[] ids) {
+    public static void apply(
+            @NonNull WireBuffer buffer, int id, @NonNull String[] names, byte[] type, int[] ids) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(names.length);
@@ -95,7 +99,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
         if (len > MAX_MAP) {
@@ -113,7 +117,7 @@
         operations.add(data);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a collection of name id pairs")
                 .field(INT, "id", "id the array")
@@ -122,13 +126,14 @@
                 .field(UTF8, "id[0]", "length", "path encoded as floats");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.putDataMap(mId, mDataMap);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
index e078307..629f786 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawArc.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -28,7 +30,7 @@
     public static final int OP_CODE = Operations.DRAW_ARC;
     private static final String CLASS_NAME = "DrawArc";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawArc::new;
         read(m, buffer, operations);
     }
@@ -49,7 +51,13 @@
      * @param v6 Sweep angle (in degrees) measured clockwise
      */
     public static void apply(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         buffer.start(OP_CODE);
         buffer.writeFloat(v1);
         buffer.writeFloat(v2);
@@ -61,11 +69,17 @@
 
     @Override
     protected void write(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         apply(buffer, v1, v2, v3, v4, v5, v6);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description(
                         "Draw the specified arc"
@@ -90,7 +104,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawArc(mV1, mV2, mV3, mV4, mV5, mV6);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
index c678cc4..984599e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase2.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -27,7 +30,7 @@
 
 /** Base class for commands that take 3 float */
 public abstract class DrawBase2 extends PaintOperation implements VariableSupport {
-    protected String mName = "DrawRectBase";
+    @NonNull protected String mName = "DrawRectBase";
     float mV1;
     float mV2;
     float mValue1;
@@ -41,13 +44,13 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mV1 = Float.isNaN(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
         mV2 = Float.isNaN(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mValue1)) {
             context.listensTo(Utils.idFromNan(mValue1), this);
         }
@@ -67,12 +70,14 @@
         DrawBase2 create(float v1, float v2);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return mName + " " + floatToString(mV1) + " " + floatToString(mV2);
     }
 
-    public static void read(Maker maker, WireBuffer buffer, List<Operation> operations) {
+    public static void read(
+            @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float v1 = buffer.readFloat();
         float v2 = buffer.readFloat();
 
@@ -87,6 +92,7 @@
      * @param y1
      * @return
      */
+    @Nullable
     public Operation construct(float x1, float y1) {
         return null;
     }
@@ -99,7 +105,7 @@
      * @param x1
      * @param y1
      */
-    protected static void write(WireBuffer buffer, int opCode, float x1, float y1) {
+    protected static void write(@NonNull WireBuffer buffer, int opCode, float x1, float y1) {
         buffer.start(opCode);
         buffer.writeFloat(x1);
         buffer.writeFloat(y1);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
index e1108e9..825da52 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase3.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -28,7 +31,7 @@
 /** Base class for commands that take 3 float */
 public abstract class DrawBase3 extends PaintOperation implements VariableSupport {
 
-    protected String mName = "DrawRectBase";
+    @NonNull protected String mName = "DrawRectBase";
     float mV1;
     float mV2;
     float mV3;
@@ -47,14 +50,14 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mV1 = Utils.isVariable(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
         mV2 = Utils.isVariable(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
         mV3 = Utils.isVariable(mValue3) ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3;
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Utils.isVariable(mValue1)) {
             context.listensTo(Utils.idFromNan(mValue1), this);
         }
@@ -77,6 +80,7 @@
         DrawBase3 create(float v1, float v2, float v3);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return mName
@@ -88,7 +92,8 @@
                 + floatToString(mV3);
     }
 
-    public static void read(Maker maker, WireBuffer buffer, List<Operation> operations) {
+    public static void read(
+            @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float v1 = buffer.readFloat();
         float v2 = buffer.readFloat();
         float v3 = buffer.readFloat();
@@ -104,6 +109,7 @@
      * @param x2
      * @return
      */
+    @Nullable
     public Operation construct(float x1, float y1, float x2) {
         return null;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
index 09f0df9..a23bcb9 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase4.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -27,7 +30,7 @@
 
 /** Base class for draw commands that take 4 floats */
 public abstract class DrawBase4 extends PaintOperation implements VariableSupport {
-    protected String mName = "DrawRectBase";
+    @NonNull protected String mName = "DrawRectBase";
     protected float mX1;
     protected float mY1;
     protected float mX2;
@@ -50,7 +53,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mX1 = Float.isNaN(mX1Value) ? context.getFloat(Utils.idFromNan(mX1Value)) : mX1Value;
         mY1 = Float.isNaN(mY1Value) ? context.getFloat(Utils.idFromNan(mY1Value)) : mY1Value;
         mX2 = Float.isNaN(mX2Value) ? context.getFloat(Utils.idFromNan(mX2Value)) : mX2Value;
@@ -58,7 +61,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mX1Value)) {
             context.listensTo(Utils.idFromNan(mX1Value), this);
         }
@@ -84,6 +87,7 @@
         DrawBase4 create(float v1, float v2, float v3, float v4);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return mName
@@ -97,7 +101,8 @@
                 + floatToString(mY2Value, mY2);
     }
 
-    public static void read(Maker maker, WireBuffer buffer, List<Operation> operations) {
+    public static void read(
+            @NonNull Maker maker, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float v1 = buffer.readFloat();
         float v2 = buffer.readFloat();
         float v3 = buffer.readFloat();
@@ -116,6 +121,7 @@
      * @param y2
      * @return
      */
+    @Nullable
     public Operation construct(float x1, float y1, float x2, float y2) {
         return null;
     }
@@ -131,7 +137,7 @@
      * @param y2
      */
     protected static void write(
-            WireBuffer buffer, int opCode, float x1, float y1, float x2, float y2) {
+            @NonNull WireBuffer buffer, int opCode, float x1, float y1, float x2, float y2) {
         buffer.start(opCode);
         buffer.writeFloat(x1);
         buffer.writeFloat(y1);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
index e071d5f..68c9f8c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBase6.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -25,7 +28,7 @@
 
 /** Base class for draw commands the take 6 floats */
 public abstract class DrawBase6 extends PaintOperation implements VariableSupport {
-    protected String mName = "DrawRectBase";
+    @NonNull protected String mName = "DrawRectBase";
     float mV1;
     float mV2;
     float mV3;
@@ -56,7 +59,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mV1 = Float.isNaN(mValue1) ? context.getFloat(Utils.idFromNan(mValue1)) : mValue1;
         mV2 = Float.isNaN(mValue2) ? context.getFloat(Utils.idFromNan(mValue2)) : mValue2;
         mV3 = Float.isNaN(mValue3) ? context.getFloat(Utils.idFromNan(mValue3)) : mValue3;
@@ -66,7 +69,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mValue1)) {
             context.listensTo(Utils.idFromNan(mValue1), this);
         }
@@ -95,6 +98,7 @@
     protected abstract void write(
             WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6);
 
+    @NonNull
     @Override
     public String toString() {
         return mName
@@ -112,7 +116,8 @@
         DrawBase6 create(float v1, float v2, float v3, float v4, float v5, float v6);
     }
 
-    public static void read(Maker build, WireBuffer buffer, List<Operation> operations) {
+    public static void read(
+            @NonNull Maker build, @NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float sv1 = buffer.readFloat();
         float sv2 = buffer.readFloat();
         float sv3 = buffer.readFloat();
@@ -135,10 +140,12 @@
      * @param v6
      * @return
      */
+    @Nullable
     public Operation construct(float v1, float v2, float v3, float v4, float v5, float v6) {
         return null;
     }
 
+    @NonNull
     public static String name() {
         return "DrawBase6";
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
index 0b43fd2..9c23c95 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmap.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -54,7 +56,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutputLeft = Float.isNaN(mLeft) ? context.getFloat(Utils.idFromNan(mLeft)) : mLeft;
         mOutputTop = Float.isNaN(mTop) ? context.getFloat(Utils.idFromNan(mTop)) : mTop;
         mOutputRight = Float.isNaN(mRight) ? context.getFloat(Utils.idFromNan(mRight)) : mRight;
@@ -62,7 +64,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mLeft)) {
             context.listensTo(Utils.idFromNan(mLeft), this);
         }
@@ -78,10 +80,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mLeft, mTop, mRight, mBottom, mDescriptionId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawBitmap (desc="
@@ -97,7 +100,7 @@
                 + ";";
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         float sLeft = buffer.readFloat();
         float srcTop = buffer.readFloat();
@@ -109,6 +112,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -118,7 +122,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int id,
             float left,
             float top,
@@ -134,7 +138,7 @@
         buffer.writeInt(descriptionId);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap")
                 .field(INT, "id", "id of float")
@@ -146,7 +150,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawBitmap(mId, mOutputLeft, mOutputTop, mOutputRight, mOutputBottom);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
index fc74827..da9fe24 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapInt.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -64,7 +66,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mImageId,
@@ -79,6 +81,7 @@
                 mContentDescId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DRAW_BITMAP_INT "
@@ -103,6 +106,7 @@
                 + ";";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -112,7 +116,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int imageId,
             int srcLeft,
             int srcTop,
@@ -136,7 +140,7 @@
         buffer.writeInt(cdId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int sLeft = buffer.readInt();
         int srcTop = buffer.readInt();
@@ -155,7 +159,7 @@
         operations.add(op);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
                 .field(DocumentedOperation.INT, "id", "id of bitmap")
@@ -171,7 +175,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawBitmap(
                 mImageId,
                 mSrcLeft,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
index 22742c6..e20bcd2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawBitmapScaled.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -45,7 +47,7 @@
     float mScaleFactor, mOutScaleFactor;
     int mScaleType;
 
-    ImageScaling mScaling = new ImageScaling();
+    @NonNull ImageScaling mScaling = new ImageScaling();
     public static final int SCALE_NONE = ImageScaling.SCALE_NONE;
     public static final int SCALE_INSIDE = ImageScaling.SCALE_INSIDE;
     public static final int SCALE_FILL_WIDTH = ImageScaling.SCALE_FILL_WIDTH;
@@ -83,7 +85,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutSrcLeft =
                 Float.isNaN(mSrcLeft) ? context.getFloat(Utils.idFromNan(mSrcLeft)) : mSrcLeft;
         mOutSrcTop = Float.isNaN(mSrcTop) ? context.getFloat(Utils.idFromNan(mSrcTop)) : mSrcTop;
@@ -109,7 +111,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         register(context, mSrcLeft);
         register(context, mSrcTop);
         register(context, mSrcRight);
@@ -121,12 +123,13 @@
         register(context, mScaleFactor);
     }
 
-    private void register(RemoteContext context, float value) {
+    private void register(@NonNull RemoteContext context, float value) {
         if (Float.isNaN(value)) {
             context.listensTo(Utils.idFromNan(value), this);
         }
     }
 
+    @NonNull
     static String str(float v) {
         String s = "  " + (int) v;
         return s.substring(s.length() - 3);
@@ -140,7 +143,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mImageId,
@@ -157,6 +160,7 @@
                 mContentDescId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawBitmapScaled "
@@ -185,6 +189,7 @@
                 + Utils.floatToString(mScaleFactor, mOutScaleFactor);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -194,7 +199,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int imageId,
             float srcLeft,
             float srcTop,
@@ -225,7 +230,7 @@
         buffer.writeInt(cdId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
 
         float sLeft = buffer.readFloat();
@@ -258,7 +263,7 @@
         operations.add(op);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
                 .field(DocumentedOperation.INT, "id", "id of bitmap")
@@ -289,7 +294,7 @@
     //    }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         mScaling.setup(
                 mOutSrcLeft,
                 mOutSrcTop,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
index 04f095a..11bd49a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawCircle.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -28,7 +30,7 @@
     private static final int OP_CODE = Operations.DRAW_CIRCLE;
     private static final String CLASS_NAME = "DrawCircle";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawCircle::new;
         read(m, buffer, operations);
     }
@@ -37,11 +39,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a Circle")
                 .field(
@@ -56,7 +59,7 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3) {
         apply(buffer, v1, v2, v3);
     }
 
@@ -66,7 +69,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawCircle(mV1, mV2, mV3);
     }
 
@@ -78,7 +81,7 @@
      * @param y1
      * @param x2
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2) {
         buffer.start(OP_CODE);
         buffer.writeFloat(x1);
         buffer.writeFloat(y1);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
index dacbb03..7310a9d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawLine.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -30,7 +32,7 @@
     private static final int OP_CODE = Operations.DRAW_LINE;
     private static final String CLASS_NAME = "DrawLine";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawLine::new;
         read(m, buffer, operations);
     }
@@ -39,11 +41,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a line segment")
                 .field(
@@ -65,7 +68,7 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
@@ -75,7 +78,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawLine(mX1, mY1, mX2, mY2);
     }
 
@@ -88,12 +91,12 @@
      * @param x2 end x of the line
      * @param y2 end y of the line
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         String x1 = "" + mX1;
         if (Float.isNaN(mX1Value)) {
             x1 = "[" + Utils.idFromNan(mX1Value) + " = " + mX1 + "]";
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
index 5d498e8..aa5116e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawOval.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -28,7 +30,7 @@
     private static final int OP_CODE = Operations.DRAW_OVAL;
     private static final String CLASS_NAME = "DrawOval";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawOval::new;
         read(m, buffer, operations);
     }
@@ -37,11 +39,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified oval")
                 .field(DocumentedOperation.FLOAT, "left", "The left side of the oval")
@@ -51,12 +54,12 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mX1, mY1, mX2, mY2);
     }
 
@@ -66,7 +69,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawOval(mX1, mY1, mX2, mY2);
     }
 
@@ -79,7 +82,7 @@
      * @param x2 end x of the DrawOval
      * @param y2 end y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
index ccbf3d9..d35094b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawPath.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -38,21 +40,23 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawPath " + "[" + mId + "]" + ", " + mStart + ", " + mEnd;
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         DrawPath op = new DrawPath(id);
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -61,19 +65,19 @@
         return Operations.DRAW_PATH;
     }
 
-    public static void apply(WireBuffer buffer, int id) {
+    public static void apply(@NonNull WireBuffer buffer, int id) {
         buffer.start(Operations.DRAW_PATH);
         buffer.writeInt(id);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw a bitmap using integer coordinates")
                 .field(DocumentedOperation.INT, "id", "id of path");
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawPath(mId, mStart, mEnd);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
index 644011b..db7633c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRect.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -29,7 +31,7 @@
     private static final int OP_CODE = Operations.DRAW_RECT;
     private static final String CLASS_NAME = "DrawRect";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawRect::new;
         read(m, buffer, operations);
     }
@@ -38,11 +40,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified rectangle")
                 .field(DocumentedOperation.FLOAT, "left", "The left side of the rectangle")
@@ -52,7 +55,7 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
@@ -62,7 +65,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawRect(mX1, mY1, mX2, mY2);
     }
 
@@ -75,7 +78,7 @@
      * @param x2 right x of the rect
      * @param y2 bottom y of the rect
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
index 64a3b28..c306e2b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawRoundRect.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -29,7 +31,7 @@
     private static final int OP_CODE = Operations.DRAW_ROUND_RECT;
     private static final String CLASS_NAME = "DrawRoundRect";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawRoundRect::new;
         read(m, buffer, operations);
     }
@@ -50,7 +52,13 @@
      * @param v6 The y-radius of the oval used to round the corners
      */
     public static void apply(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         buffer.start(OP_CODE);
         buffer.writeFloat(v1);
         buffer.writeFloat(v2);
@@ -62,11 +70,17 @@
 
     @Override
     protected void write(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         apply(buffer, v1, v2, v3, v4, v5, v6);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified round-rect")
                 .field(DocumentedOperation.FLOAT, "left", "The left side of the rect")
@@ -89,7 +103,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawRoundRect(mV1, mV2, mV3, mV4, mV5, mV6);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
index 3cb1916..3b60df7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawSector.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -28,7 +30,7 @@
     public static final int OP_CODE = Operations.DRAW_SECTOR;
     private static final String CLASS_NAME = "DrawSector";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = DrawSector::new;
         read(m, buffer, operations);
     }
@@ -49,7 +51,13 @@
      * @param v6 Sweep angle (in degrees) measured clockwise
      */
     public static void apply(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         buffer.start(OP_CODE);
         buffer.writeFloat(v1);
         buffer.writeFloat(v2);
@@ -61,11 +69,17 @@
 
     @Override
     protected void write(
-            WireBuffer buffer, float v1, float v2, float v3, float v4, float v5, float v6) {
+            @NonNull WireBuffer buffer,
+            float v1,
+            float v2,
+            float v3,
+            float v4,
+            float v5,
+            float v6) {
         apply(buffer, v1, v2, v3, v4, v5, v6);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description(
                         "Draw the specified sector (pie shape)"
@@ -90,7 +104,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawSector(mV1, mV2, mV3, mV4, mV5, mV6);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
index bcb7852..9c587ab 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawText.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -64,13 +66,13 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutX = Float.isNaN(mX) ? context.getFloat(Utils.idFromNan(mX)) : mX;
         mOutY = Float.isNaN(mY) ? context.getFloat(Utils.idFromNan(mY)) : mY;
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mX)) {
             context.listensTo(Utils.idFromNan(mX), this);
         }
@@ -80,10 +82,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextID, mStart, mEnd, mContextStart, mContextEnd, mX, mY, mRtl);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawTextRun ["
@@ -98,7 +101,7 @@
                 + floatToString(mY, mOutY);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int text = buffer.readInt();
         int start = buffer.readInt();
         int end = buffer.readInt();
@@ -112,6 +115,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -134,7 +138,7 @@
      * @param rtl is it Right to Left text
      */
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int textID,
             int start,
             int end,
@@ -154,7 +158,7 @@
         buffer.writeBoolean(rtl);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", id(), CLASS_NAME)
                 .description("Draw a run of text, all in a single direction")
                 .field(DocumentedOperation.INT, "textId", "id of bitmap")
@@ -177,7 +181,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawTextRun(mTextID, mStart, mEnd, mContextStart, mContextEnd, mOutX, mOutY, mRtl);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
index 95a8766..8b70181 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextAnchored.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -57,7 +59,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutX = Float.isNaN(mX) ? context.getFloat(Utils.idFromNan(mX)) : mX;
         mOutY = Float.isNaN(mY) ? context.getFloat(Utils.idFromNan(mY)) : mY;
         mOutPanX = Float.isNaN(mPanX) ? context.getFloat(Utils.idFromNan(mPanX)) : mPanX;
@@ -65,7 +67,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mX)) {
             context.listensTo(Utils.idFromNan(mX), this);
         }
@@ -81,10 +83,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextID, mX, mY, mPanX, mPanY, mFlags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawTextAnchored ["
@@ -108,7 +111,7 @@
         return Float.toString(v);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textID = buffer.readInt();
         float x = buffer.readFloat();
         float y = buffer.readFloat();
@@ -121,6 +124,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -141,7 +145,13 @@
      * @param flags Change the behaviour
      */
     public static void apply(
-            WireBuffer buffer, int textID, float x, float y, float panX, float panY, int flags) {
+            @NonNull WireBuffer buffer,
+            int textID,
+            float x,
+            float y,
+            float panX,
+            float panY,
+            int flags) {
         buffer.start(OP_CODE);
         buffer.writeInt(textID);
         buffer.writeFloat(x);
@@ -151,7 +161,7 @@
         buffer.writeInt(flags);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text centered about an anchor point")
                 .field(DocumentedOperation.INT, "textId", "id of bitmap")
@@ -168,7 +178,7 @@
                 .field(DocumentedOperation.INT, "flags", "Change the behaviour");
     }
 
-    float[] mBounds = new float[4];
+    @NonNull float[] mBounds = new float[4];
 
     private float getHorizontalOffset() {
         // TODO scale  TextSize / BaseTextSize;
@@ -188,7 +198,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         int flags =
                 ((mFlags & ANCHOR_MONOSPACE_MEASURE) != 0)
                         ? PaintContext.TEXT_MEASURE_MONOSPACE_WIDTH
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
index aefd6f3..e90122b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTextOnPath.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -46,7 +48,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutHOffset =
                 Float.isNaN(mHOffset) ? context.getFloat(Utils.idFromNan(mHOffset)) : mHOffset;
         mOutVOffset =
@@ -54,7 +56,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mHOffset)) {
             context.listensTo(Utils.idFromNan(mHOffset), this);
         }
@@ -64,10 +66,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mPathId, mHOffset, mVOffset);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawTextOnPath ["
@@ -80,7 +83,7 @@
                 + Utils.floatToString(mVOffset, mOutVOffset);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int pathId = buffer.readInt();
         float vOffset = buffer.readFloat();
@@ -89,6 +92,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return "DrawTextOnPath";
     }
@@ -98,7 +102,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) {
+            @NonNull WireBuffer buffer, int textId, int pathId, float hOffset, float vOffset) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeInt(pathId);
@@ -106,7 +110,7 @@
         buffer.writeFloat(hOffset);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text along path object")
                 .field(DocumentedOperation.INT, "textId", "id of the text")
@@ -116,7 +120,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawTextOnPath(mTextId, mPathId, mOutHOffset, mOutVOffset);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
index b6d45d9..0aaaf42 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/DrawTweenPath.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.operations.Utils.floatToString;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -50,14 +52,14 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutTween = Float.isNaN(mTween) ? context.getFloat(Utils.idFromNan(mTween)) : mTween;
         mOutStart = Float.isNaN(mStart) ? context.getFloat(Utils.idFromNan(mStart)) : mStart;
         mOutStop = Float.isNaN(mStop) ? context.getFloat(Utils.idFromNan(mStop)) : mStop;
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mTween)) {
             context.listensTo(Utils.idFromNan(mTween), this);
         }
@@ -70,10 +72,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mPath1Id, mPath2Id, mTween, mStart, mStop);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DrawTweenPath "
@@ -89,7 +92,7 @@
                 + floatToString(mStop, mOutStop);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int path1Id = buffer.readInt();
         int path2Id = buffer.readInt();
         float tween = buffer.readFloat();
@@ -99,6 +102,7 @@
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return "DrawTweenPath";
     }
@@ -108,7 +112,12 @@
     }
 
     public static void apply(
-            WireBuffer buffer, int path1Id, int path2Id, float tween, float start, float stop) {
+            @NonNull WireBuffer buffer,
+            int path1Id,
+            int path2Id,
+            float tween,
+            float start,
+            float stop) {
         buffer.start(OP_CODE);
         buffer.writeInt(path1Id);
         buffer.writeInt(path2Id);
@@ -117,7 +126,7 @@
         buffer.writeFloat(stop);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Draw Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text along path object")
                 .field(DocumentedOperation.INT, "pathId1", "id of path 1")
@@ -128,7 +137,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.drawTweenPath(mPath1Id, mPath2Id, mOutTween, mOutStart, mOutStop);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
index 765e150..89390ac 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatConstant.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -39,15 +41,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mValue);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "FloatConstant[" + mTextId + "] = " + mValue;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -63,20 +67,20 @@
      * @param id the id
      * @param value the value of the float
      */
-    public static void apply(WireBuffer buffer, int id, float value) {
+    public static void apply(@NonNull WireBuffer buffer, int id, float value) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeFloat(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
 
         float value = buffer.readFloat();
         operations.add(new FloatConstant(textId, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A float and its associated id")
                 .field(DocumentedOperation.INT, "id", "id of float")
@@ -84,10 +88,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadFloat(mTextId, mValue);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
index d717933..e1c6c25 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/FloatExpression.java
@@ -20,6 +20,9 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -48,7 +51,7 @@
     public float[] mPreCalcValue;
     private float mLastChange = Float.NaN;
     private float mLastCalculatedValue = Float.NaN;
-    AnimatedFloatExpression mExp = new AnimatedFloatExpression();
+    @NonNull AnimatedFloatExpression mExp = new AnimatedFloatExpression();
     public static final int MAX_EXPRESSION_SIZE = 32;
 
     public FloatExpression(int id, float[] value, float[] animation) {
@@ -61,7 +64,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) {
             mPreCalcValue = new float[mSrcValue.length];
         }
@@ -107,7 +110,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         for (float v : mSrcValue) {
             if (Float.isNaN(v)
                     && !AnimatedFloatExpression.isMathOperator(v)
@@ -118,7 +121,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         updateVariables(context);
         float t = context.getAnimationTime();
         if (Float.isNaN(mLastChange)) {
@@ -135,10 +138,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mSrcValue, mSrcAnimation);
     }
 
+    @NonNull
     @Override
     public String toString() {
         String[] labels = new String[mSrcValue.length];
@@ -161,6 +165,7 @@
                 + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -177,7 +182,11 @@
      * @param value the float expression array
      * @param animation the animation expression array
      */
-    public static void apply(WireBuffer buffer, int id, float[] value, float[] animation) {
+    public static void apply(
+            @NonNull WireBuffer buffer,
+            int id,
+            @NonNull float[] value,
+            @Nullable float[] animation) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
 
@@ -197,7 +206,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int len = buffer.readInt();
         int valueLen = len & 0xFFFF;
@@ -222,7 +231,7 @@
         operations.add(new FloatExpression(id, values, animation));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("A Float expression")
                 .field(DocumentedOperation.INT, "id", "The id of the Color")
@@ -245,6 +254,7 @@
                 .field(FLOAT, "wrapValue", "> [Wrap value] ");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
index 4f8516f..1979bc5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Header.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.LONG;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
@@ -80,10 +82,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mWidth, mHeight, mDensity, mCapabilities);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "HEADER v"
@@ -102,15 +105,17 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.header(mMajorVersion, mMinorVersion, mPatchVersion, mWidth, mHeight, mCapabilities);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -120,7 +125,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer, int width, int height, float density, long capabilities) {
+            @NonNull WireBuffer buffer, int width, int height, float density, long capabilities) {
         buffer.start(OP_CODE);
         buffer.writeInt(MAJOR_VERSION); // major version number of the protocol
         buffer.writeInt(MINOR_VERSION); // minor version number of the protocol
@@ -131,7 +136,7 @@
         buffer.writeLong(capabilities);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int majorVersion = buffer.readInt();
         int minorVersion = buffer.readInt();
         int patchVersion = buffer.readInt();
@@ -152,7 +157,7 @@
         operations.add(header);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description(
                         "Document metadata, containing the version,"
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
index c9a8508..6375f00 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/IntegerExpression.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -45,7 +47,7 @@
     public int[] mPreCalcValue;
     private float mLastChange = Float.NaN;
     public static final int MAX_SIZE = 320;
-    IntegerExpressionEvaluator mExp = new IntegerExpressionEvaluator();
+    @NonNull IntegerExpressionEvaluator mExp = new IntegerExpressionEvaluator();
 
     public IntegerExpression(int id, int mask, int[] value) {
         this.mId = id;
@@ -54,7 +56,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (mPreCalcValue == null || mPreCalcValue.length != mSrcValue.length) {
             mPreCalcValue = new int[mSrcValue.length];
         }
@@ -70,7 +72,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         for (int i = 0; i < mSrcValue.length; i++) {
             if (isId(mMask, i, mSrcValue[i])) {
                 context.listensTo(mSrcValue[i], this);
@@ -79,7 +81,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         updateVariables(context);
         float t = context.getAnimationTime();
         if (Float.isNaN(mLastChange)) {
@@ -95,7 +97,7 @@
      * @param context current context
      * @return the resulting value
      */
-    public int evaluate(RemoteContext context) {
+    public int evaluate(@NonNull RemoteContext context) {
         updateVariables(context);
         float t = context.getAnimationTime();
         if (Float.isNaN(mLastChange)) {
@@ -105,10 +107,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mMask, mSrcValue);
     }
 
+    @NonNull
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
@@ -132,6 +135,7 @@
         return "IntegerExpression[" + mId + "] = (" + s + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -148,7 +152,7 @@
      * @param mask the mask bits of ints & operators or variables
      * @param value array of integers to be evaluated
      */
-    public static void apply(WireBuffer buffer, int id, int mask, int[] value) {
+    public static void apply(@NonNull WireBuffer buffer, int id, int mask, @NonNull int[] value) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeInt(mask);
@@ -158,7 +162,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
         int mask = buffer.readInt();
         int len = buffer.readInt();
@@ -173,7 +177,7 @@
         operations.add(new IntegerExpression(id, mask, values));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Expression that computes an integer")
                 .field(DocumentedOperation.INT, "id", "id of integer")
@@ -182,6 +186,7 @@
                 .field(INT_ARRAY, "values", "length", "Array of ints");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
index 04f8a50..6a620e5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRestore.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -31,20 +33,22 @@
     public MatrixRestore() {}
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         MatrixRestore op = new MatrixRestore();
         operations.add(op);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "MatrixRestore";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -53,17 +57,17 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(OP_CODE);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Restore the matrix and clip");
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixRestore();
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
index df10f32..438a2aa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixRotate.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -28,9 +30,10 @@
     public static final int OP_CODE = Operations.MATRIX_ROTATE;
     private static final String CLASS_NAME = "MatrixRotate";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m =
                 new Maker() {
+                    @NonNull
                     @Override
                     public DrawBase3 create(float v1, float v2, float v3) {
                         return new MatrixRotate(v1, v2, v3);
@@ -43,11 +46,12 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("apply rotation to matrix")
                 .field(DocumentedOperation.FLOAT, "rotate", "Angle to rotate")
@@ -56,7 +60,7 @@
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3) {
         apply(buffer, v1, v2, v3);
     }
 
@@ -66,7 +70,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixRotate(mV1, mV2, mV3);
     }
 
@@ -78,7 +82,7 @@
      * @param y1 X Pivot point
      * @param x2 Y Pivot point
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2) {
         buffer.start(OP_CODE);
         buffer.writeFloat(x1);
         buffer.writeFloat(y1);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
index 67612c7..1880b19 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSave.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -29,20 +31,22 @@
     private static final String CLASS_NAME = "MatrixSave";
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "MatrixSave;";
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         MatrixSave op = new MatrixSave();
         operations.add(op);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -51,17 +55,17 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(Operations.MATRIX_SAVE);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Save the matrix and clip to a stack");
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixSave();
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
index 26c898a..6304584 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixScale.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -28,7 +30,7 @@
     public static final int OP_CODE = Operations.MATRIX_SCALE;
     public static final String CLASS_NAME = "MatrixScale";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = MatrixScale::new;
         read(m, buffer, operations);
     }
@@ -37,16 +39,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified Oval")
                 .field(DocumentedOperation.FLOAT, "scaleX", "The amount to scale in X")
@@ -61,7 +64,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixScale(mX1, mY1, mX2, mY2);
     }
 
@@ -74,7 +77,7 @@
      * @param x2 end x of the DrawOval
      * @param y2 end y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1, float x2, float y2) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1, float x2, float y2) {
         write(buffer, OP_CODE, x1, y1, x2, y2);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
index d641178..675cf0d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixSkew.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -29,7 +31,7 @@
     public static final int OP_CODE = Operations.MATRIX_SKEW;
     public static final String CLASS_NAME = "MatrixSkew";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = MatrixSkew::new;
         read(m, buffer, operations);
     }
@@ -38,16 +40,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
         apply(buffer, v1, v2);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Current matrix with the specified skew.")
                 .field(FLOAT, "skewX", "The amount to skew in X")
@@ -60,7 +63,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixSkew(mV1, mV2);
     }
 
@@ -71,7 +74,7 @@
      * @param x1 start x of DrawOval
      * @param y1 start y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
         write(buffer, OP_CODE, x1, y1);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
index e008292..b0a7d35 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/MatrixTranslate.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -28,7 +30,7 @@
     public static final int OP_CODE = Operations.MATRIX_TRANSLATE;
     public static final String CLASS_NAME = "MatrixTranslate";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = MatrixTranslate::new;
         read(m, buffer, operations);
     }
@@ -37,16 +39,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2) {
         apply(buffer, v1, v2);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, "MatrixTranslate")
                 .description("Preconcat the current matrix with the specified translation")
                 .field(DocumentedOperation.FLOAT, "dx", "The distance to translate in X")
@@ -59,7 +62,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.matrixTranslate(mV1, mV2);
     }
 
@@ -70,7 +73,7 @@
      * @param x1 start x of DrawOval
      * @param y1 start y of the DrawOval
      */
-    public static void apply(WireBuffer buffer, float x1, float y1) {
+    public static void apply(@NonNull WireBuffer buffer, float x1, float y1) {
         write(buffer, OP_CODE, x1, y1);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
index fa6e271..6310521e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -47,10 +49,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mVarId, mVarType, mVarName);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "VariableName["
@@ -61,6 +64,7 @@
                 + mVarType;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -77,21 +81,22 @@
      * @param varType The type of variable
      * @param text String
      */
-    public static void apply(WireBuffer buffer, int varId, int varType, String text) {
+    public static void apply(
+            @NonNull WireBuffer buffer, int varId, int varType, @NonNull String text) {
         buffer.start(Operations.NAMED_VARIABLE);
         buffer.writeInt(varId);
         buffer.writeInt(varType);
         buffer.writeUTF8(text);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int varId = buffer.readInt();
         int varType = buffer.readInt();
         String text = buffer.readUTF8(MAX_STRING_SIZE);
         operations.add(new NamedVariable(varId, varType, text));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Add a string name for an ID")
                 .field(DocumentedOperation.INT, "varId", "id to label")
@@ -100,10 +105,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadVariableName(mVarName, mVarId, mVarType);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
index 095a010..527d5610 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PaintData.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT_ARRAY;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -33,31 +35,33 @@
 public class PaintData extends PaintOperation implements VariableSupport {
     private static final int OP_CODE = Operations.PAINT_VALUES;
     private static final String CLASS_NAME = "PaintData";
-    public PaintBundle mPaintData = new PaintBundle();
+    @NonNull public PaintBundle mPaintData = new PaintBundle();
     public static final int MAX_STRING_SIZE = 4000;
 
     public PaintData() {}
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mPaintData.updateVariables(context);
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         mPaintData.registerVars(context, this);
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mPaintData);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "PaintData " + "\"" + mPaintData + "\"";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -66,31 +70,32 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, PaintBundle paintBundle) {
+    public static void apply(@NonNull WireBuffer buffer, @NonNull PaintBundle paintBundle) {
         buffer.start(Operations.PAINT_VALUES);
         paintBundle.writeBundle(buffer);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         PaintData data = new PaintData();
         data.mPaintData.readBundle(buffer);
         operations.add(data);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a Paint ")
                 .field(INT, "length", "id string")
                 .field(INT_ARRAY, "paint", "length", "path encoded as floats");
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.applyPaint(mPaintData);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
index 13d5a49..06a1fec 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/PathData.java
@@ -18,6 +18,9 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -43,7 +46,7 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         for (int i = 0; i < mFloatPath.length; i++) {
             float v = mFloatPath[i];
             if (Utils.isVariable(v)) {
@@ -55,7 +58,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         for (float v : mFloatPath) {
             if (Float.isNaN(v)) {
                 context.listensTo(Utils.idFromNan(v), this);
@@ -64,15 +67,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mInstanceId, mOutputPath);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return pathString(mFloatPath);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "PathData[" + mInstanceId + "] = " + "\"" + deepToString(" ") + "\"";
@@ -102,6 +107,7 @@
     public static final float CLOSE_NAN = Utils.asNan(CLOSE);
     public static final float DONE_NAN = Utils.asNan(DONE);
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -110,7 +116,7 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int id, float[] data) {
+    public static void apply(@NonNull WireBuffer buffer, int id, @NonNull float[] data) {
         buffer.start(Operations.DATA_PATH);
         buffer.writeInt(id);
         buffer.writeInt(data.length);
@@ -119,7 +125,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int imageId = buffer.readInt();
         int len = buffer.readInt();
         float[] data = new float[len];
@@ -129,7 +135,7 @@
         operations.add(new PathData(imageId, data));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a Path ")
                 .field(DocumentedOperation.INT, "id", "id string")
@@ -137,7 +143,8 @@
                 .field(FLOAT_ARRAY, "pathData", "length", "path encoded as floats");
     }
 
-    public static String pathString(float[] path) {
+    @NonNull
+    public static String pathString(@Nullable float[] path) {
         if (path == null) {
             return "null";
         }
@@ -186,7 +193,7 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadPathData(mInstanceId, mOutputPath);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
index 4a8f532..6ff9ad7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentBehavior.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
@@ -172,10 +174,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mScroll, mAlignment, mSizing, mMode);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ROOT_CONTENT_BEHAVIOR scroll: "
@@ -187,15 +190,17 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.setRootContentBehavior(mScroll, mAlignment, mSizing, mMode);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -204,7 +209,8 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int scroll, int alignment, int sizing, int mode) {
+    public static void apply(
+            @NonNull WireBuffer buffer, int scroll, int alignment, int sizing, int mode) {
         buffer.start(OP_CODE);
         buffer.writeInt(scroll);
         buffer.writeInt(alignment);
@@ -212,7 +218,7 @@
         buffer.writeInt(mode);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int scroll = buffer.readInt();
         int alignment = buffer.readInt();
         int sizing = buffer.readInt();
@@ -222,7 +228,7 @@
         operations.add(rootContentBehavior);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Describes the behaviour of the root")
                 .field(DocumentedOperation.INT, "scroll", "scroll")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
index bff9029..c2d62a7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/RootContentDescription.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
@@ -41,25 +43,28 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mContentDescription);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "RootContentDescription " + mContentDescription;
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.setDocumentContentDescription(mContentDescription);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -68,18 +73,18 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int contentDescription) {
+    public static void apply(@NonNull WireBuffer buffer, int contentDescription) {
         buffer.start(Operations.ROOT_CONTENT_DESCRIPTION);
         buffer.writeInt(contentDescription);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int contentDescription = buffer.readInt();
         RootContentDescription header = new RootContentDescription(contentDescription);
         operations.add(header);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Content description of root")
                 .field(DocumentedOperation.INT, "id", "id of Int");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
index 7ec7879..ae61c3a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ShaderData.java
@@ -22,6 +22,9 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -43,17 +46,17 @@
     private static final String CLASS_NAME = "ShaderData";
     int mShaderTextId; // the actual text of a shader
     int mShaderID; // allows shaders to be referenced by number
-    HashMap<String, float[]> mUniformRawFloatMap = null;
-    HashMap<String, float[]> mUniformFloatMap = null;
-    HashMap<String, int[]> mUniformIntMap = null;
-    HashMap<String, Integer> mUniformBitmapMap = null;
+    @Nullable HashMap<String, float[]> mUniformRawFloatMap = null;
+    @Nullable HashMap<String, float[]> mUniformFloatMap = null;
+    @Nullable HashMap<String, int[]> mUniformIntMap = null;
+    @Nullable HashMap<String, Integer> mUniformBitmapMap = null;
 
     public ShaderData(
             int shaderID,
             int shaderTextId,
-            HashMap<String, float[]> floatMap,
-            HashMap<String, int[]> intMap,
-            HashMap<String, Integer> bitmapMap) {
+            @Nullable HashMap<String, float[]> floatMap,
+            @Nullable HashMap<String, int[]> intMap,
+            @Nullable HashMap<String, Integer> bitmapMap) {
         mShaderID = shaderID;
         mShaderTextId = shaderTextId;
         if (floatMap != null) {
@@ -89,6 +92,7 @@
      *
      * @return Names of all uniform floats or empty array
      */
+    @NonNull
     public String[] getUniformFloatNames() {
         if (mUniformFloatMap == null) return new String[0];
         return mUniformFloatMap.keySet().toArray(new String[0]);
@@ -109,6 +113,7 @@
      *
      * @return Name of all integer uniforms
      */
+    @NonNull
     public String[] getUniformIntegerNames() {
         if (mUniformIntMap == null) return new String[0];
         return mUniformIntMap.keySet().toArray(new String[0]);
@@ -129,6 +134,7 @@
      *
      * @return Name of all bitmap uniforms
      */
+    @NonNull
     public String[] getUniformBitmapNames() {
         if (mUniformBitmapMap == null) return new String[0];
         return mUniformBitmapMap.keySet().toArray(new String[0]);
@@ -145,7 +151,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mShaderID,
@@ -155,13 +161,14 @@
                 mUniformBitmapMap);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "SHADER DATA " + mShaderID;
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         for (String name : mUniformRawFloatMap.keySet()) {
             float[] value = mUniformRawFloatMap.get(name);
             float[] out = null;
@@ -178,7 +185,7 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         for (String name : mUniformRawFloatMap.keySet()) {
             float[] value = mUniformRawFloatMap.get(name);
             for (float v : value) {
@@ -189,6 +196,7 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -208,12 +216,12 @@
      * @param bitmapMap the map of bitmap uniforms
      */
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int shaderID,
             int shaderTextId,
-            HashMap<String, float[]> floatMap,
-            HashMap<String, int[]> intMap,
-            HashMap<String, Integer> bitmapMap) {
+            @Nullable HashMap<String, float[]> floatMap,
+            @Nullable HashMap<String, int[]> intMap,
+            @Nullable HashMap<String, Integer> bitmapMap) {
         buffer.start(OP_CODE);
         buffer.writeInt(shaderID);
 
@@ -256,7 +264,7 @@
         }
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int shaderID = buffer.readInt();
         int shaderTextId = buffer.readInt();
         HashMap<String, float[]> floatMap = null;
@@ -308,7 +316,7 @@
         operations.add(new ShaderData(shaderID, shaderTextId, floatMap, intMap, bitmapMap));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Shader")
                 .field(DocumentedOperation.INT, "shaderID", "id of shader")
@@ -326,10 +334,11 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadShader(mShaderID, this);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
index 6383249..dbaef7e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextData.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.UTF8;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -42,15 +44,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mText);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextData[" + mTextId + "] = \"" + Utils.trimString(mText, 10) + "\"";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -59,20 +63,20 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int textId, String text) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, @NonNull String text) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeUTF8(text);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
 
         String text = buffer.readUTF8(MAX_STRING_SIZE);
         operations.add(new TextData(textId, text));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Encode a string ")
                 .field(DocumentedOperation.INT, "id", "id string")
@@ -80,20 +84,22 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadText(mTextId, mText);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, getSerializedName() + "<" + mTextId + "> = \"" + mText + "\"");
     }
 
+    @NonNull
     private String getSerializedName() {
         return "DATA_TEXT";
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
index 0d966d1..fb5087f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextFromFloat.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -87,10 +89,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mValue, mDigitsBefore, mDigitsAfter, mFlags);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextFromFloat["
@@ -106,19 +109,20 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (Float.isNaN(mValue)) {
             mOutValue = context.getFloat(Utils.idFromNan(mValue));
         }
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mValue)) {
             context.listensTo(Utils.idFromNan(mValue), this);
         }
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -138,7 +142,7 @@
      * @param flags flags that control if and how to fill the empty spots
      */
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int textId,
             float value,
             short digitsBefore,
@@ -151,7 +155,7 @@
         buffer.writeInt(flags);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         float value = buffer.readFloat();
         int tmp = buffer.readInt();
@@ -162,7 +166,7 @@
         operations.add(new TextFromFloat(textId, value, pre, post, flags));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Draw text along path object")
                 .field(DocumentedOperation.INT, "textId", "id of the text generated")
@@ -173,12 +177,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         float v = mOutValue;
         String s = StringUtils.floatToString(v, mDigitsBefore, mDigitsAfter, mPre, mAfter);
         context.loadText(mTextId, s);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
index b04d698..2129edd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookup.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -48,10 +50,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mDataSetId, mIndex);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextLookup["
@@ -63,19 +66,20 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (Float.isNaN(mIndex)) {
             mOutIndex = context.getFloat(Utils.idFromNan(mIndex));
         }
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (Float.isNaN(mIndex)) {
             context.listensTo(Utils.idFromNan(mIndex), this);
         }
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -92,21 +96,21 @@
      * @param dataSet float pointer to the array/list to turn int a string
      * @param index index of element to return
      */
-    public static void apply(WireBuffer buffer, int textId, int dataSet, float index) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int dataSet, float index) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeInt(dataSet);
         buffer.writeFloat(index);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int dataSetId = buffer.readInt();
         float index = buffer.readFloat();
         operations.add(new TextLookup(textId, dataSetId, index));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Look an array and turn into a text object")
                 .field(INT, "textId", "id of the text generated")
@@ -115,11 +119,12 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         int id = context.getCollectionsAccess().getId(mDataSetId, (int) mOutIndex);
         context.loadText(mTextId, context.getText(id));
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
index 171bea2..ea550cb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextLookupInt.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -45,10 +47,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mDataSetId, mIndex);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextLookupInt["
@@ -60,15 +63,16 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mOutIndex = context.getInteger(mIndex);
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         context.listensTo(mIndex, this);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -85,21 +89,21 @@
      * @param dataSet float pointer to the array/list to turn int a string
      * @param indexId index of element to return
      */
-    public static void apply(WireBuffer buffer, int textId, int dataSet, int indexId) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int dataSet, int indexId) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeInt(dataSet);
         buffer.writeInt(indexId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int dataSetId = buffer.readInt();
         int indexId = buffer.readInt();
         operations.add(new TextLookupInt(textId, dataSetId, indexId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
                 .description("Look up an array and turn into a text object")
                 .field(DocumentedOperation.INT, "textId", "id of the text generated")
@@ -108,11 +112,12 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         int id = context.getCollectionsAccess().getId(mDataSetId, (int) mOutIndex);
         context.loadText(mTextId, context.getText(id));
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
index 78cc674..fa18b4d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TextMerge.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -41,15 +43,17 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTextId, mSrcId1, mSrcId2);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TextMerge[" + mTextId + "] = [" + mSrcId1 + " ] + [ " + mSrcId2 + "]";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -66,14 +70,14 @@
      * @param srcId1 source text 1
      * @param srcId2 source text 2
      */
-    public static void apply(WireBuffer buffer, int textId, int srcId1, int srcId2) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int srcId1, int srcId2) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
         buffer.writeInt(srcId1);
         buffer.writeInt(srcId2);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
         int srcId1 = buffer.readInt();
         int srcId2 = buffer.readInt();
@@ -81,7 +85,7 @@
         operations.add(new TextMerge(textId, srcId1, srcId2));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Data Operations", OP_CODE, CLASS_NAME)
                 .description("Merge two string into one")
                 .field(DocumentedOperation.INT, "textId", "id of the text")
@@ -90,12 +94,13 @@
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         String str1 = context.getText(mSrcId1);
         String str2 = context.getText(mSrcId2);
         context.loadText(mTextId, str1 + str2);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
index 845f25d..1e90ab1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Theme.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteComposeOperation;
@@ -49,25 +51,28 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mTheme);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "SET_THEME " + mTheme;
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.setTheme(mTheme);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return indent + toString();
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -76,17 +81,17 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int theme) {
+    public static void apply(@NonNull WireBuffer buffer, int theme) {
         buffer.start(OP_CODE);
         buffer.writeInt(theme);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int theme = buffer.readInt();
         operations.add(new Theme(theme));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Protocol Operations", OP_CODE, CLASS_NAME)
                 .description("Set a theme")
                 .field(INT, "THEME", "theme id")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
new file mode 100644
index 0000000..b25a7f6
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/TouchExpression.java
@@ -0,0 +1,599 @@
+/*
+ * 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.internal.widget.remotecompose.core.operations;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT_ARRAY;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.SHORT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.TouchListener;
+import com.android.internal.widget.remotecompose.core.VariableSupport;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.utilities.AnimatedFloatExpression;
+import com.android.internal.widget.remotecompose.core.operations.utilities.NanMap;
+import com.android.internal.widget.remotecompose.core.operations.utilities.touch.VelocityEasing;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Operation to deal with Touch handling (typically on canvas) This support handling of many typical
+ * touch behaviours. Including animating to Notched, positions. and tweaking the dynamics of the
+ * animation.
+ */
+public class TouchExpression implements Operation, VariableSupport, TouchListener {
+    private static final int OP_CODE = Operations.TOUCH_EXPRESSION;
+    private static final String CLASS_NAME = "TouchExpression";
+    private float mDefValue;
+    private float mOutDefValue;
+    public int mId;
+    public float[] mSrcExp;
+    int mMode = 1; // 0 = delta, 1 = absolute
+    float mMax = 1;
+    float mMin = 1;
+    float mOutMax = 1;
+    float mOutMin = 1;
+    float mValue = 0;
+    boolean mUnmodified = true;
+    public float[] mPreCalcValue;
+    private float mLastChange = Float.NaN;
+    private float mLastCalculatedValue = Float.NaN;
+    AnimatedFloatExpression mExp = new AnimatedFloatExpression();
+    public static final int MAX_EXPRESSION_SIZE = 32;
+    private VelocityEasing mEasyTouch = new VelocityEasing();
+    private boolean mEasingToStop = false;
+    private float mTouchUpTime = 0;
+    private float mCurrentValue = Float.NaN;
+    private boolean mTouchDown = false;
+    float mMaxTime = 1;
+    float mMaxAcceleration = 5;
+    float mMaxVelocity = 7;
+    int mStopMode = 0;
+    boolean mWrapMode = false;
+    float[] mNotches;
+    float[] mStopSpec;
+    int mTouchEffects;
+    float mVelocityId;
+
+    public static final int STOP_GENTLY = 0;
+    public static final int STOP_ENDS = 2;
+    public static final int STOP_INSTANTLY = 1;
+    public static final int STOP_NOTCHES_EVEN = 3;
+    public static final int STOP_NOTCHES_PERCENTS = 4;
+    public static final int STOP_NOTCHES_ABSOLUTE = 5;
+    public static final int STOP_ABSOLUTE_POS = 6;
+
+    public TouchExpression(
+            int id,
+            float[] exp,
+            float defValue,
+            float min,
+            float max,
+            int touchEffects,
+            float velocityId,
+            int stopMode,
+            float[] stopSpec,
+            float[] easingSpec) {
+        this.mId = id;
+        this.mSrcExp = exp;
+        mOutDefValue = mDefValue = defValue;
+        mMode = STOP_ABSOLUTE_POS == stopMode ? 1 : 0;
+        mOutMax = mMax = max;
+        mTouchEffects = touchEffects;
+        mVelocityId = velocityId;
+        if (Float.isNaN(min) && Utils.idFromNan(min) == 0) {
+            mWrapMode = true;
+        } else {
+            mOutMin = mMin = min;
+        }
+        mStopMode = stopMode;
+        mStopSpec = stopSpec;
+        if (easingSpec != null) {
+            Utils.log("easingSpec  " + Arrays.toString(easingSpec));
+            if (easingSpec.length >= 4) {
+                if (Float.floatToRawIntBits(easingSpec[0]) == 0) {
+                    Utils.log("easingSpec[2]  " + easingSpec[2]);
+                    mMaxTime = easingSpec[1];
+                    mMaxAcceleration = easingSpec[2];
+                    mMaxVelocity = easingSpec[3];
+                }
+            }
+        }
+    }
+
+    @Override
+    public void updateVariables(RemoteContext context) {
+
+        if (mPreCalcValue == null || mPreCalcValue.length != mSrcExp.length) {
+            mPreCalcValue = new float[mSrcExp.length];
+        }
+        if (Float.isNaN(mMax)) {
+            mOutMax = context.getFloat(Utils.idFromNan(mMax));
+        }
+        if (Float.isNaN(mMin)) {
+            mOutMin = context.getFloat(Utils.idFromNan(mMin));
+        }
+        if (Float.isNaN(mDefValue)) {
+            mOutDefValue = context.getFloat(Utils.idFromNan(mDefValue));
+        }
+
+        boolean value_changed = false;
+        for (int i = 0; i < mSrcExp.length; i++) {
+            float v = mSrcExp[i];
+            if (Float.isNaN(v)
+                    && !AnimatedFloatExpression.isMathOperator(v)
+                    && !NanMap.isDataVariable(v)) {
+                float newValue = context.getFloat(Utils.idFromNan(v));
+
+                mPreCalcValue[i] = newValue;
+
+            } else {
+                mPreCalcValue[i] = mSrcExp[i];
+            }
+        }
+        float v = mLastCalculatedValue;
+        if (value_changed) { // inputs changed check if output changed
+            v = mExp.eval(mPreCalcValue, mPreCalcValue.length);
+            if (v != mLastCalculatedValue) {
+                mLastChange = context.getAnimationTime();
+                mLastCalculatedValue = v;
+            } else {
+                value_changed = false;
+            }
+        }
+    }
+
+    @Override
+    public void registerListening(RemoteContext context) {
+        if (Float.isNaN(mMax)) {
+            context.listensTo(Utils.idFromNan(mMax), this);
+        }
+        if (Float.isNaN(mMin)) {
+            context.listensTo(Utils.idFromNan(mMin), this);
+        }
+        if (Float.isNaN(mDefValue)) {
+            context.listensTo(Utils.idFromNan(mDefValue), this);
+        }
+        context.addTouchListener(this);
+        for (float v : mSrcExp) {
+            if (Float.isNaN(v)
+                    && !AnimatedFloatExpression.isMathOperator(v)
+                    && !NanMap.isDataVariable(v)) {
+                context.listensTo(Utils.idFromNan(v), this);
+            }
+        }
+    }
+
+    private float wrap(float pos) {
+        if (!mWrapMode) {
+            return pos;
+        }
+        pos = pos % mOutMax;
+        if (pos < 0) {
+            pos += mOutMax;
+        }
+        return pos;
+    }
+
+    private float getStopPosition(float pos, float slope) {
+        float target = pos + slope / mMaxAcceleration;
+        if (mWrapMode) {
+            pos = wrap(pos);
+            target = pos += +slope / mMaxAcceleration;
+        } else {
+            target = Math.max(Math.min(target, mOutMax), mOutMin);
+        }
+        float[] positions = new float[mStopSpec.length];
+        float min = (mWrapMode) ? 0 : mOutMin;
+
+        switch (mStopMode) {
+            case STOP_ENDS:
+                return ((pos + slope) > (mOutMax + min) / 2) ? mOutMax : min;
+            case STOP_INSTANTLY:
+                return pos;
+            case STOP_NOTCHES_EVEN:
+                int evenSpacing = (int) mStopSpec[0];
+                float step = (mOutMax - min) / evenSpacing;
+
+                float notch = min + step * (int) (0.5f + (target - mOutMin) / step);
+
+                notch = Math.max(Math.min(notch, mOutMax), min);
+                return notch;
+            case STOP_NOTCHES_PERCENTS:
+                positions = new float[mStopSpec.length];
+                float minPos = min;
+                float minPosDist = Math.abs(mOutMin - target);
+                for (int i = 0; i < positions.length; i++) {
+                    float p = mOutMin + mStopSpec[i] * (mOutMax - mOutMin);
+                    float dist = Math.abs(p - target);
+                    if (minPosDist > dist) {
+                        minPosDist = dist;
+                        minPos = p;
+                    }
+                }
+                return minPos;
+            case STOP_NOTCHES_ABSOLUTE:
+                positions = mStopSpec;
+                minPos = mOutMin;
+                minPosDist = Math.abs(mOutMin - target);
+                for (int i = 0; i < positions.length; i++) {
+                    float dist = Math.abs(positions[i] - target);
+                    if (minPosDist > dist) {
+                        minPosDist = dist;
+                        minPos = positions[i];
+                    }
+                }
+                return minPos;
+            case STOP_GENTLY:
+            default:
+                return target;
+        }
+    }
+
+    void haptic(RemoteContext context) {
+        int touch = ((mTouchEffects) & 0xFF);
+        if ((mTouchEffects & (1 << 15)) != 0) {
+            touch = context.getInteger(mTouchEffects & 0x7FFF);
+        }
+
+        context.hapticEffect(touch);
+    }
+
+    float mLastValue = 0;
+
+    void crossNotchCheck(RemoteContext context) {
+        float prev = mLastValue;
+        float next = mCurrentValue;
+        mLastValue = next;
+
+        //        System.out.println(mStopMode + "    " + prev + "  -> " + next);
+        float min = (mWrapMode) ? 0 : mOutMin;
+        float max = mOutMax;
+
+        switch (mStopMode) {
+            case STOP_ENDS:
+                if (((min - prev) * (max - prev) < 0) ^ ((min - next) * (max - next)) < 0) {
+                    haptic(context);
+                }
+                break;
+            case STOP_INSTANTLY:
+                haptic(context);
+                break;
+            case STOP_NOTCHES_EVEN:
+                int evenSpacing = (int) mStopSpec[0];
+                float step = (max - min) / evenSpacing;
+                if ((int) ((prev - min) / step) != (int) ((next - min) / step)) {
+                    haptic(context);
+                }
+                break;
+            case STOP_NOTCHES_PERCENTS:
+                for (int i = 0; i < mStopSpec.length; i++) {
+                    float p = mOutMin + mStopSpec[i] * (mOutMax - mOutMin);
+                    if ((prev - p) * (next - p) < 0) {
+                        haptic(context);
+                    }
+                }
+                break;
+            case STOP_NOTCHES_ABSOLUTE:
+                for (int i = 0; i < mStopSpec.length; i++) {
+                    float p = mStopSpec[i];
+                    if ((prev - p) * (next - p) < 0) {
+                        haptic(context);
+                    }
+                }
+                break;
+            case STOP_GENTLY:
+        }
+    }
+
+    float mScrLeft, mScrRight, mScrTop, mScrBottom;
+
+    @Override
+    public void apply(RemoteContext context) {
+        Component comp = context.lastComponent;
+        if (comp != null) {
+            float x = comp.getX();
+            float y = comp.getY();
+            float w = comp.getWidth();
+            float h = comp.getHeight();
+            comp = comp.getParent();
+            while (comp != null) {
+                x += comp.getX();
+                y += comp.getY();
+                comp = comp.getParent();
+            }
+            mScrLeft = x;
+            mScrTop = y;
+            mScrRight = w + x;
+            mScrBottom = h + y;
+        }
+        updateVariables(context);
+        if (mUnmodified) {
+            mCurrentValue = mOutDefValue;
+
+            context.loadFloat(mId, wrap(mCurrentValue));
+            return;
+        }
+        if (mEasingToStop) {
+            float time = context.getAnimationTime() - mTouchUpTime;
+            float value = mEasyTouch.getPos(time);
+            mCurrentValue = value;
+            value = wrap(value);
+            context.loadFloat(mId, value);
+            if (mEasyTouch.getDuration() < time) {
+                mEasingToStop = false;
+            }
+            crossNotchCheck(context);
+            return;
+        }
+        if (mTouchDown) {
+            float value =
+                    mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+            if (mMode == 0) {
+                value = mValueAtDown + (value - mDownTouchValue);
+            }
+            if (mWrapMode) {
+                value = wrap(value);
+            } else {
+                value = Math.min(Math.max(value, mOutMin), mOutMax);
+            }
+            mCurrentValue = value;
+        }
+        crossNotchCheck(context);
+        context.loadFloat(mId, wrap(mCurrentValue));
+    }
+
+    float mValueAtDown; // The currently "displayed" value at down
+    float mDownTouchValue; // The calculated value at down
+
+    @Override
+    public void touchDown(RemoteContext context, float x, float y) {
+
+        if (!(x >= mScrLeft && x <= mScrRight && y >= mScrTop && y <= mScrBottom)) {
+            Utils.log("NOT IN WINDOW " + x + ", " + y + " " + mScrLeft + ", " + mScrTop);
+            return;
+        }
+        mTouchDown = true;
+        mUnmodified = false;
+        if (mMode == 0) {
+            mValueAtDown = context.getFloat(mId);
+            mDownTouchValue =
+                    mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+        }
+    }
+
+    @Override
+    public void touchUp(RemoteContext context, float x, float y, float dx, float dy) {
+        // calculate the slope (using small changes)
+        if (!mTouchDown) {
+            return;
+        }
+        mTouchDown = false;
+        float dt = 0.0001f;
+        if (mStopMode == STOP_INSTANTLY) {
+            return;
+        }
+        float v = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+        for (int i = 0; i < mSrcExp.length; i++) {
+            if (Float.isNaN(mSrcExp[i])) {
+                int id = Utils.idFromNan(mSrcExp[i]);
+                if (id == RemoteContext.ID_TOUCH_POS_X) {
+                    mPreCalcValue[i] = x + dx * dt;
+                } else if (id == RemoteContext.ID_TOUCH_POS_Y) {
+                    mPreCalcValue[i] = y + dy * dt;
+                }
+            }
+        }
+        float vdt = mExp.eval(context.getCollectionsAccess(), mPreCalcValue, mPreCalcValue.length);
+        float slope = (vdt - v) / dt; // the rate of change with respect to the dx,dy movement
+        float value = context.getFloat(mId);
+
+        mTouchUpTime = context.getAnimationTime();
+
+        float dest = getStopPosition(value, slope);
+        mEasyTouch.config(value, dest, slope, mMaxTime, mMaxAcceleration, mMaxVelocity, null);
+        mEasingToStop = true;
+    }
+
+    @Override
+    public void touchDrag(RemoteContext context, float x, float y) {
+        if (!mTouchDown) {
+            return;
+        }
+        apply(context);
+        context.getDocument().getRootLayoutComponent().needsRepaint();
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(
+                buffer,
+                mId,
+                mValue,
+                mMin,
+                mMax,
+                mVelocityId,
+                mTouchEffects,
+                mSrcExp,
+                mStopMode,
+                mNotches,
+                null);
+    }
+
+    @Override
+    public String toString() {
+        String[] labels = new String[mSrcExp.length];
+        for (int i = 0; i < mSrcExp.length; i++) {
+            if (Float.isNaN(mSrcExp[i])) {
+                labels[i] = "[" + Utils.idStringFromNan(mSrcExp[i]) + "]";
+            }
+        }
+        if (mPreCalcValue == null) {
+            return CLASS_NAME
+                    + "["
+                    + mId
+                    + "] = ("
+                    + AnimatedFloatExpression.toString(mSrcExp, labels)
+                    + ")";
+        }
+        return CLASS_NAME
+                + "["
+                + mId
+                + "] = ("
+                + AnimatedFloatExpression.toString(mPreCalcValue, labels)
+                + ")";
+    }
+
+    // ===================== static ======================
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    /**
+     * Writes out the operation to the buffer
+     *
+     * @param buffer The buffer to write to
+     * @param id the id of the resulting float
+     * @param value the float expression array
+     */
+    public static void apply(
+            WireBuffer buffer,
+            int id,
+            float value,
+            float min,
+            float max,
+            float velocityId,
+            int touchEffects,
+            float[] exp,
+            int touchMode,
+            float[] touchSpec,
+            float[] easingSpec) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(id);
+        buffer.writeFloat(value);
+        buffer.writeFloat(min);
+        buffer.writeFloat(max);
+        buffer.writeFloat(velocityId);
+        buffer.writeInt(touchEffects);
+        buffer.writeInt(exp.length);
+        for (float v : exp) {
+            buffer.writeFloat(v);
+        }
+        int len = 0;
+        if (touchSpec != null) {
+            len = touchSpec.length;
+        }
+        buffer.writeInt((touchMode << 16) | len);
+        for (int i = 0; i < len; i++) {
+            buffer.writeFloat(touchSpec[i]);
+        }
+
+        if (easingSpec != null) {
+            len = easingSpec.length;
+        } else {
+            len = 0;
+        }
+        buffer.writeInt(len);
+        for (int i = 0; i < len; i++) {
+            buffer.writeFloat(easingSpec[i]);
+        }
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        int id = buffer.readInt();
+        float startValue = buffer.readFloat();
+        float min = buffer.readFloat();
+        float max = buffer.readFloat();
+        float velocityId = buffer.readFloat(); // TODO future support
+        int touchEffects = buffer.readInt();
+        int len = buffer.readInt();
+        int valueLen = len & 0xFFFF;
+        if (valueLen > MAX_EXPRESSION_SIZE) {
+            throw new RuntimeException("Float expression to long");
+        }
+        float[] exp = new float[valueLen];
+        for (int i = 0; i < exp.length; i++) {
+            exp[i] = buffer.readFloat();
+        }
+        int stopLogic = buffer.readInt();
+        int stopLen = stopLogic & 0xFFFF;
+        int stopMode = stopLogic >> 16;
+
+        Utils.log("stopMode " + stopMode + " stopLen " + stopLen);
+        float[] stopsData = new float[stopLen];
+        for (int i = 0; i < stopsData.length; i++) {
+            stopsData[i] = buffer.readFloat();
+        }
+        int easingLen = buffer.readInt();
+
+        float[] easingData = new float[easingLen];
+        for (int i = 0; i < easingData.length; i++) {
+            easingData[i] = buffer.readFloat();
+        }
+
+        operations.add(
+                new TouchExpression(
+                        id,
+                        exp,
+                        startValue,
+                        min,
+                        max,
+                        touchEffects,
+                        velocityId,
+                        stopMode,
+                        stopsData,
+                        easingData));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Expressions Operations", OP_CODE, CLASS_NAME)
+                .description("A Float expression")
+                .field(INT, "id", "The id of the Color")
+                .field(SHORT, "expression_length", "expression length")
+                .field(SHORT, "animation_length", "animation description length")
+                .field(
+                        FLOAT_ARRAY,
+                        "expression",
+                        "expression_length",
+                        "Sequence of Floats representing and expression")
+                .field(
+                        FLOAT_ARRAY,
+                        "AnimationSpec",
+                        "animation_length",
+                        "Sequence of Floats representing animation curve")
+                .field(FLOAT, "duration", "> time in sec")
+                .field(INT, "bits", "> WRAP|INITALVALUE | TYPE ")
+                .field(FLOAT_ARRAY, "spec", "> [SPEC PARAMETERS] ")
+                .field(FLOAT, "initialValue", "> [Initial value] ")
+                .field(FLOAT, "wrapValue", "> [Wrap value] ");
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return indent + toString();
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
index 8ebb40c..03f7e05 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations;
 
+import android.annotation.NonNull;
+
 /** Utilities to be used across all core operations */
 public class Utils {
     public static float asNan(int v) {
@@ -30,11 +32,13 @@
         return v - 0x100000000L;
     }
 
+    @NonNull
     public static String idStringFromNan(float value) {
         int b = Float.floatToRawIntBits(value) & 0x3FFFFF;
         return idString(b);
     }
 
+    @NonNull
     public static String idString(int b) {
         return (b > 0xFFFFF) ? "A_" + (b & 0xFFFFF) : "" + b;
     }
@@ -50,7 +54,8 @@
      * @param n
      * @return
      */
-    public static String trimString(String str, int n) {
+    @NonNull
+    public static String trimString(@NonNull String str, int n) {
         if (str.length() > n) {
             str = str.substring(0, n - 3) + "...";
         }
@@ -145,6 +150,7 @@
      * @param color
      * @return
      */
+    @NonNull
     public static String colorInt(int color) {
         String str = "000000000000" + Integer.toHexString(color);
         return "0x" + str.substring(str.length() - 8);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
new file mode 100644
index 0000000..e789710
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/AnimatableValue.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.FloatAnimation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.easing.GeneralEasing;
+
+public class AnimatableValue {
+    boolean mIsVariable = false;
+    int mId = 0;
+    float mValue = 0f;
+
+    boolean mAnimate = false;
+    long mAnimateTargetTime = 0;
+    float mAnimateDuration = 300f;
+    float mTargetRotationX;
+    float mStartRotationX;
+
+    int mMotionEasingType = GeneralEasing.CUBIC_STANDARD;
+    FloatAnimation mMotionEasing;
+
+    public AnimatableValue(float value) {
+        if (Utils.isVariable(value)) {
+            mId = Utils.idFromNan(value);
+            mIsVariable = true;
+        } else {
+            mValue = value;
+        }
+    }
+
+    public float getValue() {
+        return mValue;
+    }
+
+    public float evaluate(PaintContext context) {
+        if (!mIsVariable) {
+            return mValue;
+        }
+        float value = context.getContext().mRemoteComposeState.getFloat(mId);
+
+        if (value != mValue && !mAnimate) {
+            // animate
+            mStartRotationX = mValue;
+            mTargetRotationX = value;
+            mAnimate = true;
+            mAnimateTargetTime = System.currentTimeMillis();
+            mMotionEasing =
+                    new FloatAnimation(
+                            mMotionEasingType, mAnimateDuration / 1000f, null, 0f, Float.NaN);
+            mMotionEasing.setTargetValue(1f);
+        }
+        if (mAnimate) {
+            float elapsed = System.currentTimeMillis() - mAnimateTargetTime;
+            float p = mMotionEasing.get(elapsed / mAnimateDuration);
+            mValue = (1 - p) * mStartRotationX + p * mTargetRotationX;
+            if (p >= 1f) {
+                mAnimate = false;
+            }
+        } else {
+            mValue = mTargetRotationX;
+        }
+
+        return mValue;
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
index 9d80d3c..9886518 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/CanvasContent.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
@@ -38,6 +40,7 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
+    @NonNull
     public static String name() {
         return "CanvasContent";
     }
@@ -46,29 +49,30 @@
         return Operations.LAYOUT_CANVAS_CONTENT;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "CANVAS_CONTENT";
     }
 
-    public static void apply(WireBuffer buffer, int componentId) {
+    public static void apply(@NonNull WireBuffer buffer, int componentId) {
         buffer.start(Operations.LAYOUT_CANVAS_CONTENT);
         buffer.writeInt(componentId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         operations.add(new CanvasContent(componentId, 0, 0, 0, 0, null, -1));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
                 .description("Container for canvas commands.");
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickHandler.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickHandler.java
new file mode 100644
index 0000000..0ca72fa
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/** Interface to represent operations that can handle click events */
+public interface ClickHandler {
+
+    /**
+     * callback for a click event
+     *
+     * @param context the current context
+     * @param document the current document
+     * @param component the component on which the click has been received
+     * @param x the x position of the click in document coordinates
+     * @param y the y position of the click in document coordinates
+     */
+    void onClick(
+            RemoteContext context, CoreDocument document, Component component, float x, float y);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
index d5ff07d..b567538 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierOperation.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -37,7 +40,7 @@
 
 /** Represents a click modifier + actions */
 public class ClickModifierOperation extends PaintOperation
-        implements ModifierOperation, DecoratorComponent {
+        implements ModifierOperation, DecoratorComponent, ClickHandler {
     private static final int OP_CODE = Operations.MODIFIER_CLICK;
 
     long mAnimateRippleStart = 0;
@@ -48,9 +51,9 @@
     float mWidth = 0;
     float mHeight = 0;
 
-    public float[] locationInWindow = new float[2];
+    @NonNull public float[] locationInWindow = new float[2];
 
-    PaintBundle mPaint = new PaintBundle();
+    @NonNull PaintBundle mPaint = new PaintBundle();
 
     public void animateRipple(float x, float y) {
         mAnimateRippleStart = System.currentTimeMillis();
@@ -58,17 +61,19 @@
         mAnimateRippleY = y;
     }
 
-    public ArrayList<Operation> mList = new ArrayList<>();
+    @NonNull public ArrayList<Operation> mList = new ArrayList<>();
 
+    @NonNull
     public ArrayList<Operation> getList() {
         return mList;
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ClickModifier";
@@ -83,13 +88,14 @@
         }
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         if (mAnimateRippleStart == 0) {
             return;
         }
@@ -137,7 +143,7 @@
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, "CLICK_MODIFIER");
         for (Operation o : mList) {
             if (o instanceof ActionOperation) {
@@ -148,7 +154,11 @@
 
     @Override
     public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            RemoteContext context,
+            CoreDocument document,
+            @NonNull Component component,
+            float x,
+            float y) {
         if (!component.isVisible()) {
             return;
         }
@@ -163,19 +173,20 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "ClickModifier";
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(OP_CODE);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ClickModifierOperation());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, name())
                 .description(
                         "Click modifier. This operation contains"
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
index 96dffca..f4f4ee2 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/Component.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -31,7 +34,6 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.Measurable;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.MeasurePass;
-import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
 import com.android.internal.widget.remotecompose.core.operations.paint.PaintBundle;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
@@ -52,16 +54,23 @@
     protected int mAnimationId = -1;
     public Visibility mVisibility = Visibility.VISIBLE;
     public Visibility mScheduledVisibility = Visibility.VISIBLE;
-    public ArrayList<Operation> mList = new ArrayList<>();
+    @NonNull public ArrayList<Operation> mList = new ArrayList<>();
     public PaintOperation mPreTranslate;
     public boolean mNeedsMeasure = true;
     public boolean mNeedsRepaint = false;
-    public AnimateMeasure mAnimateMeasure;
-    public AnimationSpec mAnimationSpec = new AnimationSpec();
+    @Nullable public AnimateMeasure mAnimateMeasure;
+    @NonNull public AnimationSpec mAnimationSpec = new AnimationSpec();
     public boolean mFirstLayout = true;
-    PaintBundle mPaint = new PaintBundle();
-    protected HashSet<ComponentValue> mComponentValues = new HashSet<>();
+    @NonNull PaintBundle mPaint = new PaintBundle();
+    @NonNull protected HashSet<ComponentValue> mComponentValues = new HashSet<>();
 
+    protected float mZIndex = 0f;
+
+    public float getZIndex() {
+        return mZIndex;
+    }
+
+    @NonNull
     public ArrayList<Operation> getList() {
         return mList;
     }
@@ -115,7 +124,7 @@
      *
      * @param context the current context
      */
-    private void updateComponentValues(RemoteContext context) {
+    private void updateComponentValues(@NonNull RemoteContext context) {
         if (DEBUG) {
             System.out.println(
                     "UPDATE COMPONENT VALUES ("
@@ -172,7 +181,7 @@
         this(parent, componentId, -1, x, y, width, height);
     }
 
-    public Component(Component component) {
+    public Component(@NonNull Component component) {
         this(
                 component.mParent,
                 component.mComponentId,
@@ -212,7 +221,10 @@
      *
      * @param context the current context
      */
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
+        Component prev = context.lastComponent;
+        context.lastComponent = this;
+
         if (!mComponentValues.isEmpty()) {
             updateComponentValues(context);
         }
@@ -224,6 +236,7 @@
                 o.apply(context);
             }
         }
+        context.lastComponent = prev;
     }
 
     public void addComponentValue(ComponentValue v) {
@@ -283,14 +296,14 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         ComponentMeasure m = measure.get(this);
         m.setW(mWidth);
         m.setH(mHeight);
     }
 
     @Override
-    public void layout(RemoteContext context, MeasurePass measure) {
+    public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) {
         ComponentMeasure m = measure.get(this);
         if (!mFirstLayout
                 && context.isAnimationEnabled()
@@ -332,7 +345,7 @@
         mFirstLayout = false;
     }
 
-    public float[] locationInWindow = new float[2];
+    @NonNull public float[] locationInWindow = new float[2];
 
     public boolean contains(float x, float y) {
         locationInWindow[0] = 0f;
@@ -353,13 +366,57 @@
             if (op instanceof Component) {
                 ((Component) op).onClick(context, document, x, y);
             }
-            if (op instanceof ComponentModifiers) {
-                ((ComponentModifiers) op).onClick(context, document, this, x, y);
+            if (op instanceof ClickHandler) {
+                ((ClickHandler) op).onClick(context, document, this, x, y);
             }
         }
     }
 
-    public void getLocationInWindow(float[] value) {
+    public void onTouchDown(RemoteContext context, CoreDocument document, float x, float y) {
+        if (!contains(x, y)) {
+            return;
+        }
+        for (Operation op : mList) {
+            if (op instanceof Component) {
+                ((Component) op).onTouchDown(context, document, x, y);
+            }
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchDown(context, document, this, x, y);
+            }
+        }
+    }
+
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, float x, float y, boolean force) {
+        if (!force && !contains(x, y)) {
+            return;
+        }
+        for (Operation op : mList) {
+            if (op instanceof Component) {
+                ((Component) op).onTouchUp(context, document, x, y, force);
+            }
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchUp(context, document, this, x, y);
+            }
+        }
+    }
+
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, float x, float y, boolean force) {
+        if (!force && !contains(x, y)) {
+            return;
+        }
+        for (Operation op : mList) {
+            if (op instanceof Component) {
+                ((Component) op).onTouchCancel(context, document, x, y, force);
+            }
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchCancel(context, document, this, x, y);
+            }
+        }
+    }
+
+    public void getLocationInWindow(@NonNull float[] value) {
         value[0] += mX;
         value[1] += mY;
         if (mParent != null) {
@@ -372,6 +429,7 @@
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "COMPONENT(<"
@@ -393,14 +451,14 @@
                 + ") ";
     }
 
+    @NonNull
     protected String getSerializedName() {
         return "COMPONENT";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
-        serializer.append(
-                indent,
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        String content =
                 getSerializedName()
                         + " ["
                         + mComponentId
@@ -416,9 +474,9 @@
                         + ", "
                         + mHeight
                         + "] "
-                        + mVisibility
-                //        + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]"
-        );
+                        + mVisibility;
+        //        + " [" + mNeedsMeasure + ", " + mNeedsRepaint + "]"
+        serializer.append(indent, content);
     }
 
     @Override
@@ -427,6 +485,7 @@
     }
 
     /** Returns the top-level RootLayoutComponent */
+    @NonNull
     public RootLayoutComponent getRoot() throws Exception {
         if (this instanceof RootLayoutComponent) {
             return (RootLayoutComponent) this;
@@ -441,6 +500,7 @@
         return (RootLayoutComponent) p;
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         StringBuilder builder = new StringBuilder();
@@ -477,6 +537,7 @@
         }
     }
 
+    @NonNull
     public String content() {
         StringBuilder builder = new StringBuilder();
         for (Operation op : mList) {
@@ -487,6 +548,7 @@
         return builder.toString();
     }
 
+    @NonNull
     public String textContent() {
         StringBuilder builder = new StringBuilder();
         for (Operation ignored : mList) {
@@ -499,7 +561,7 @@
         return builder.toString();
     }
 
-    public void debugBox(Component component, PaintContext context) {
+    public void debugBox(@NonNull Component component, @NonNull PaintContext context) {
         float width = component.mWidth;
         float height = component.mHeight;
 
@@ -536,13 +598,15 @@
         return 0f;
     }
 
-    public void paintingComponent(PaintContext context) {
+    public void paintingComponent(@NonNull PaintContext context) {
         if (mPreTranslate != null) {
             mPreTranslate.paint(context);
         }
+        Component prev = context.getContext().lastComponent;
+        context.getContext().lastComponent = this;
         context.save();
         context.translate(mX, mY);
-        if (context.isDebug()) {
+        if (context.isVisualDebug()) {
             debugBox(this, context);
         }
         for (Operation op : mList) {
@@ -554,9 +618,10 @@
             }
         }
         context.restore();
+        context.getContext().lastComponent = prev;
     }
 
-    public boolean applyAnimationAsNeeded(PaintContext context) {
+    public boolean applyAnimationAsNeeded(@NonNull PaintContext context) {
         if (context.isAnimationEnabled() && mAnimateMeasure != null) {
             mAnimateMeasure.apply(context);
             needsRepaint();
@@ -566,8 +631,8 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
-        if (context.isDebug()) {
+    public void paint(@NonNull PaintContext context) {
+        if (context.isVisualDebug()) {
             context.save();
             context.translate(mX, mY);
             context.savePaint();
@@ -594,7 +659,7 @@
         paintingComponent(context);
     }
 
-    public void getComponents(ArrayList<Component> components) {
+    public void getComponents(@NonNull ArrayList<Component> components) {
         for (Operation op : mList) {
             if (op instanceof Component) {
                 components.add((Component) op);
@@ -602,7 +667,7 @@
         }
     }
 
-    public void getData(ArrayList<TextData> data) {
+    public void getData(@NonNull ArrayList<TextData> data) {
         for (Operation op : mList) {
             if (op instanceof TextData) {
                 data.add((TextData) op);
@@ -631,6 +696,7 @@
         return mNeedsRepaint;
     }
 
+    @Nullable
     public Component getComponent(int cid) {
         if (mComponentId == cid || mAnimationId == cid) {
             return this;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
index c83ee487..f370e20 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentEnd.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -26,10 +29,11 @@
 public class ComponentEnd implements Operation {
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "COMPONENT_END";
@@ -40,11 +44,13 @@
         // nothing
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     public static String name() {
         return "ComponentEnd";
     }
@@ -53,7 +59,7 @@
         return Operations.COMPONENT_END;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(Operations.COMPONENT_END);
     }
 
@@ -61,11 +67,11 @@
         return 1 + 4 + 4 + 4;
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ComponentEnd());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "End tag for components / layouts. This operation marks the end"
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
index 72cc9b6..f250d9a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ComponentStart.java
@@ -18,6 +18,9 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -69,10 +72,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mType, mComponentId, mWidth, mHeight);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "COMPONENT_START (type "
@@ -90,8 +94,9 @@
                 + ")";
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -119,6 +124,7 @@
     public static final int LAYOUT_ROW = 15;
     public static final int LAYOUT_COLUMN = 16;
 
+    @NonNull
     public static String typeDescription(int type) {
         switch (type) {
             case DEFAULT:
@@ -152,6 +158,7 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "ComponentStart";
     }
@@ -161,7 +168,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer, int type, int componentId, float width, float height) {
+            @NonNull WireBuffer buffer, int type, int componentId, float width, float height) {
         buffer.start(Operations.COMPONENT_START);
         buffer.writeInt(type);
         buffer.writeInt(componentId);
@@ -173,7 +180,7 @@
         return 1 + 4 + 4 + 4;
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int type = buffer.readInt();
         int componentId = buffer.readInt();
         float width = buffer.readFloat();
@@ -181,7 +188,7 @@
         operations.add(new ComponentStart(type, componentId, width, height));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "Basic component encapsulating draw commands." + "This is not resizable.")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
index 314650f..bb43119 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/DecoratorComponent.java
@@ -15,7 +15,6 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 
 /**
@@ -24,7 +23,4 @@
  */
 public interface DecoratorComponent {
     void layout(RemoteContext context, float width, float height);
-
-    void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y);
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
index 8172502..e0923dfb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponent.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.operations.BitmapData;
@@ -25,18 +28,22 @@
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentModifiers;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ComponentVisibilityOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.DimensionModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.GraphicsLayerModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.HeightModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.PaddingModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.WidthModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ZIndexModifierOperation;
 
 import java.util.ArrayList;
 
 /** Component with modifiers and children */
 public class LayoutComponent extends Component {
 
-    protected WidthModifierOperation mWidthModifier = null;
-    protected HeightModifierOperation mHeightModifier = null;
+    @Nullable protected WidthModifierOperation mWidthModifier = null;
+    @Nullable protected HeightModifierOperation mHeightModifier = null;
+    @Nullable protected ZIndexModifierOperation mZIndexModifier = null;
+    @Nullable protected GraphicsLayerModifierOperation mGraphicsLayerModifier = null;
 
     // Margins
     protected float mMarginLeft = 0f;
@@ -49,8 +56,10 @@
     protected float mPaddingTop = 0f;
     protected float mPaddingBottom = 0f;
 
-    protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
-    protected ArrayList<Component> mChildrenComponents = new ArrayList<>();
+    @NonNull protected ComponentModifiers mComponentModifiers = new ComponentModifiers();
+    @NonNull protected ArrayList<Component> mChildrenComponents = new ArrayList<>();
+
+    protected boolean mChildrenHaveZIndex = false;
 
     public LayoutComponent(
             Component parent,
@@ -95,15 +104,25 @@
         return mPaddingBottom;
     }
 
+    @Nullable
     public WidthModifierOperation getWidthModifier() {
         return mWidthModifier;
     }
 
+    @Nullable
     public HeightModifierOperation getHeightModifier() {
         return mHeightModifier;
     }
 
-    protected LayoutComponentContent mContent = null;
+    @Override
+    public float getZIndex() {
+        if (mZIndexModifier != null) {
+            return mZIndexModifier.getValue();
+        }
+        return mZIndex;
+    }
+
+    @Nullable protected LayoutComponentContent mContent = null;
 
     // Should be removed after ImageLayout is in
     private static final boolean USE_IMAGE_TEMP_FIX = true;
@@ -164,6 +183,9 @@
         for (Component c : mChildrenComponents) {
             c.mParent = this;
             mList.add(c);
+            if (c instanceof LayoutComponent && ((LayoutComponent) c).mZIndexModifier != null) {
+                mChildrenHaveZIndex = true;
+            }
         }
 
         mX = 0f;
@@ -209,6 +231,12 @@
                 mHeightModifier = (HeightModifierOperation) op;
                 applyVerticalMargin = false;
             }
+            if (op instanceof ZIndexModifierOperation) {
+                mZIndexModifier = (ZIndexModifierOperation) op;
+            }
+            if (op instanceof GraphicsLayerModifierOperation) {
+                mGraphicsLayerModifier = (GraphicsLayerModifierOperation) op;
+            }
         }
         if (mWidthModifier == null) {
             mWidthModifier = new WidthModifierOperation(DimensionModifierOperation.Type.WRAP);
@@ -220,24 +248,64 @@
         setHeight(computeModifierDefinedHeight());
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "UNKNOWN LAYOUT_COMPONENT";
     }
 
     @Override
-    public void paintingComponent(PaintContext context) {
+    public void paintingComponent(@NonNull PaintContext context) {
+        Component prev = context.getContext().lastComponent;
+        context.getContext().lastComponent = this;
         context.save();
         context.translate(mX, mY);
+        if (mGraphicsLayerModifier != null) {
+            context.startGraphicsLayer((int) getWidth(), (int) getHeight());
+            float scaleX = mGraphicsLayerModifier.getScaleX();
+            float scaleY = mGraphicsLayerModifier.getScaleY();
+            float rotationX = mGraphicsLayerModifier.getRotationX();
+            float rotationY = mGraphicsLayerModifier.getRotationY();
+            float rotationZ = mGraphicsLayerModifier.getRotationZ();
+            float shadowElevation = mGraphicsLayerModifier.getShadowElevation();
+            float transformOriginX = mGraphicsLayerModifier.getTransformOriginX();
+            float transformOriginY = mGraphicsLayerModifier.getTransformOriginY();
+            float alpha = mGraphicsLayerModifier.getAlpha();
+            int renderEffectId = mGraphicsLayerModifier.getRenderEffectId();
+            context.setGraphicsLayer(
+                    scaleX,
+                    scaleY,
+                    rotationX,
+                    rotationY,
+                    rotationZ,
+                    shadowElevation,
+                    transformOriginX,
+                    transformOriginY,
+                    alpha,
+                    renderEffectId);
+        }
         mComponentModifiers.paint(context);
         float tx = mPaddingLeft;
         float ty = mPaddingTop;
         context.translate(tx, ty);
-        for (Component child : mChildrenComponents) {
-            child.paint(context);
+        if (mChildrenHaveZIndex) {
+            // TODO -- should only sort when something has changed
+            ArrayList<Component> sorted = new ArrayList<Component>(mChildrenComponents);
+            sorted.sort((a, b) -> (int) (a.getZIndex() - b.getZIndex()));
+            for (Component child : sorted) {
+                child.paint(context);
+            }
+        } else {
+            for (Component child : mChildrenComponents) {
+                child.paint(context);
+            }
+        }
+        if (mGraphicsLayerModifier != null) {
+            context.endGraphicsLayer();
         }
         context.translate(-tx, -ty);
         context.restore();
+        context.getContext().lastComponent = prev;
     }
 
     /** Traverse the modifiers to compute indicated dimension */
@@ -248,7 +316,8 @@
         for (Operation c : mComponentModifiers.getList()) {
             if (c instanceof WidthModifierOperation) {
                 WidthModifierOperation o = (WidthModifierOperation) c;
-                if (o.getType() == DimensionModifierOperation.Type.EXACT) {
+                if (o.getType() == DimensionModifierOperation.Type.EXACT
+                        || o.getType() == DimensionModifierOperation.Type.EXACT_DP) {
                     w = o.getValue();
                 }
                 break;
@@ -291,7 +360,8 @@
         for (Operation c : mComponentModifiers.getList()) {
             if (c instanceof HeightModifierOperation) {
                 HeightModifierOperation o = (HeightModifierOperation) c;
-                if (o.getType() == DimensionModifierOperation.Type.EXACT) {
+                if (o.getType() == DimensionModifierOperation.Type.EXACT
+                        || o.getType() == DimensionModifierOperation.Type.EXACT_DP) {
                     h = o.getValue();
                 }
                 break;
@@ -326,6 +396,7 @@
         return t + b;
     }
 
+    @NonNull
     public ArrayList<Component> getChildrenComponents() {
         return mChildrenComponents;
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
index 66fd053..0a085b4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LayoutComponentContent.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
@@ -38,6 +40,7 @@
         super(parent, componentId, animationId, x, y, width, height);
     }
 
+    @NonNull
     public static String name() {
         return "LayoutContent";
     }
@@ -46,22 +49,23 @@
         return Operations.LAYOUT_CONTENT;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "CONTENT";
     }
 
-    public static void apply(WireBuffer buffer, int componentId) {
+    public static void apply(@NonNull WireBuffer buffer, int componentId) {
         buffer.start(Operations.LAYOUT_CONTENT);
         buffer.writeInt(componentId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         operations.add(new LayoutComponentContent(componentId, 0, 0, 0, 0, null, -1));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
                 .description(
@@ -71,7 +75,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
new file mode 100644
index 0000000..c4df075
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ListActionsOperation.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.PaintOperation;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.operations.TextData;
+import com.android.internal.widget.remotecompose.core.operations.layout.modifiers.ModifierOperation;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.ArrayList;
+
+public abstract class ListActionsOperation extends PaintOperation
+        implements ModifierOperation, DecoratorComponent {
+
+    String mOperationName;
+    float mWidth = 0;
+    float mHeight = 0;
+
+    private final float[] mLocationInWindow = new float[2];
+
+    public ListActionsOperation(String operationName) {
+        mOperationName = operationName;
+    }
+
+    public ArrayList<Operation> mList = new ArrayList<>();
+
+    public ArrayList<Operation> getList() {
+        return mList;
+    }
+
+    @Override
+    public String toString() {
+        return mOperationName;
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        for (Operation op : mList) {
+            if (op instanceof TextData) {
+                op.apply(context);
+            }
+        }
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {}
+
+    @Override
+    public void layout(RemoteContext context, float width, float height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, mOperationName);
+        for (Operation o : mList) {
+            if (o instanceof ActionOperation) {
+                ((ActionOperation) o).serializeToString(indent + 1, serializer);
+            }
+        }
+    }
+
+    public boolean applyActions(
+            RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y,
+            boolean force) {
+        if (!force && !component.isVisible()) {
+            return false;
+        }
+        if (!force && !component.contains(x, y)) {
+            return false;
+        }
+        mLocationInWindow[0] = 0f;
+        mLocationInWindow[1] = 0f;
+        component.getLocationInWindow(mLocationInWindow);
+        for (Operation o : mList) {
+            if (o instanceof ActionOperation) {
+                ((ActionOperation) o).runAction(context, document, component, x, y);
+            }
+        }
+        return true;
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
index 3086d6a..c90077b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopEnd.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -26,10 +29,11 @@
 public class LoopEnd implements Operation {
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "LOOP_END";
@@ -40,11 +44,13 @@
         // nothing
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     public static String name() {
         return "LoopEnd";
     }
@@ -53,15 +59,15 @@
         return Operations.LOOP_END;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(id());
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new LoopEnd());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Operations", id(), name()).description("End tag for loops");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
index 6910008..eeaeafd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/LoopOperation.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -30,7 +33,7 @@
 public class LoopOperation extends PaintOperation {
     private static final int OP_CODE = Operations.LOOP_START;
 
-    public ArrayList<Operation> mList = new ArrayList<>();
+    @NonNull public ArrayList<Operation> mList = new ArrayList<>();
 
     int mIndexVariableId;
     float mUntil = 12;
@@ -49,27 +52,30 @@
         mIndexVariableId = indexId;
     }
 
+    @NonNull
     public ArrayList<Operation> getList() {
         return mList;
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mUntil, mFrom, mStep, mIndexVariableId);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "LoopOperation";
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         if (mIndexVariableId == 0) {
             for (float i = mFrom; i < mUntil; i += mStep) {
                 for (Operation op : mList) {
@@ -89,11 +95,13 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "Loop";
     }
 
-    public static void apply(WireBuffer buffer, float count, float from, float step, int indexId) {
+    public static void apply(
+            @NonNull WireBuffer buffer, float count, float from, float step, int indexId) {
         buffer.start(OP_CODE);
         buffer.writeFloat(count);
         buffer.writeFloat(from);
@@ -101,7 +109,7 @@
         buffer.writeInt(indexId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float count = buffer.readFloat();
         float from = buffer.readFloat();
         float step = buffer.readFloat();
@@ -109,7 +117,7 @@
         operations.add(new LoopOperation(count, from, step, indexId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Operations", OP_CODE, name())
                 .description("Loop. This operation execute" + " a list of action in a loop");
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierEnd.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
similarity index 68%
rename from core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierEnd.java
rename to core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
index fe726ac..bd8d1f0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/ClickModifierEnd.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/OperationsListEnd.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -23,16 +26,17 @@
 
 import java.util.List;
 
-public class ClickModifierEnd implements Operation {
+public class OperationsListEnd implements Operation {
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     @Override
     public String toString() {
-        return "CLICK_END";
+        return "LIST_END";
     }
 
     @Override
@@ -40,31 +44,31 @@
         // nothing
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     public static String name() {
-        return "ClickModifierEnd";
+        return "ListEnd";
     }
 
     public static int id() {
-        return Operations.MODIFIER_CLICK_END;
+        return Operations.OPERATIONS_LIST_END;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(id());
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
-        operations.add(new ClickModifierEnd());
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
+        operations.add(new OperationsListEnd());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
-                .description(
-                        "End tag for click modifiers. This operation marks the end"
-                                + "of a click modifier");
+                .description("End tag for list of operations.");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
index 680bb0b..524ae59 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/RootLayoutComponent.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -34,7 +36,8 @@
 
 /** Represents the root layout component. Entry point to the component tree layout/paint. */
 public class RootLayoutComponent extends Component implements ComponentStartOperation {
-    int mCurrentId = -1;
+    private int mCurrentId = -1;
+    private boolean mHasTouchListeners = false;
 
     public RootLayoutComponent(
             int componentId,
@@ -52,6 +55,7 @@
         super(parent, componentId, -1, x, y, width, height);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ROOT "
@@ -69,7 +73,7 @@
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 "ROOT ["
@@ -89,6 +93,15 @@
     }
 
     /**
+     * Set the flag to traverse the tree when touch events happen
+     *
+     * @param value true to indicate that the tree has touch listeners
+     */
+    public void setHasTouchListeners(boolean value) {
+        mHasTouchListeners = value;
+    }
+
+    /**
      * Traverse the hierarchy and assign generated ids to component without ids. Most components
      * would already have ids assigned during the document creation, but this allow us to take care
      * of any components added during the inflation.
@@ -100,7 +113,7 @@
         assignId(this);
     }
 
-    private void assignId(Component component) {
+    private void assignId(@NonNull Component component) {
         if (component.mComponentId == -1) {
             mCurrentId--;
             component.mComponentId = mCurrentId;
@@ -113,7 +126,7 @@
     }
 
     /** This will measure then layout the tree of components */
-    public void layout(RemoteContext context) {
+    public void layout(@NonNull RemoteContext context) {
         if (!mNeedsMeasure) {
             return;
         }
@@ -134,7 +147,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         mNeedsRepaint = false;
         context.getContext().lastComponent = this;
         context.save();
@@ -152,13 +165,15 @@
         context.restore();
     }
 
+    @NonNull
     public String displayHierarchy() {
         StringSerializer serializer = new StringSerializer();
         displayHierarchy(this, 0, serializer);
         return serializer.toString();
     }
 
-    public void displayHierarchy(Component component, int indent, StringSerializer serializer) {
+    public void displayHierarchy(
+            @NonNull Component component, int indent, @NonNull StringSerializer serializer) {
         component.serializeToString(indent, serializer);
         for (Operation c : component.mList) {
             if (c instanceof ComponentModifiers) {
@@ -171,6 +186,7 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "RootLayout";
     }
@@ -179,17 +195,17 @@
         return Operations.LAYOUT_ROOT;
     }
 
-    public static void apply(WireBuffer buffer, int componentId) {
+    public static void apply(@NonNull WireBuffer buffer, int componentId) {
         buffer.start(Operations.LAYOUT_ROOT);
         buffer.writeInt(componentId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         operations.add(new RootLayoutComponent(componentId, 0, 0, 0, 0, null, -1));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .field(INT, "COMPONENT_ID", "unique id for this component")
                 .description(
@@ -199,7 +215,11 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId);
     }
+
+    public boolean hasTouchListeners() {
+        return mHasTouchListeners;
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
new file mode 100644
index 0000000..486efbd
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchCancelModifierOperation.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+
+import java.util.List;
+
+/** Represents a touch cancel modifier + actions */
+public class TouchCancelModifierOperation extends ListActionsOperation implements TouchHandler {
+
+    private static final int OP_CODE = Operations.MODIFIER_TOUCH_CANCEL;
+
+    public TouchCancelModifierOperation() {
+        super("TOUCH_CANCEL_MODIFIER");
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer);
+    }
+
+    @Override
+    public String toString() {
+        return "TouchCancelModifier";
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
+        super.apply(context);
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        applyActions(context, document, component, x, y, true);
+    }
+
+    public static String name() {
+        return "TouchCancelModifier";
+    }
+
+    public static void apply(WireBuffer buffer) {
+        buffer.start(OP_CODE);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        operations.add(new TouchCancelModifierOperation());
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, name())
+                .description(
+                        "Touch cancel modifier. This operation contains"
+                                + " a list of action executed on Touch cancel");
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
new file mode 100644
index 0000000..5d379fe
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchDownModifierOperation.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+
+import java.util.List;
+
+/** Represents a touch down modifier + actions */
+public class TouchDownModifierOperation extends ListActionsOperation implements TouchHandler {
+
+    private static final int OP_CODE = Operations.MODIFIER_TOUCH_DOWN;
+
+    public TouchDownModifierOperation() {
+        super("TOUCH_DOWN_MODIFIER");
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer);
+    }
+
+    @Override
+    public String toString() {
+        return "TouchDownModifier";
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
+        super.apply(context);
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        if (applyActions(context, document, component, x, y, false)) {
+            document.appliedTouchOperation(component);
+        }
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    public static String name() {
+        return "TouchModifier";
+    }
+
+    public static void apply(WireBuffer buffer) {
+        buffer.start(OP_CODE);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        operations.add(new TouchDownModifierOperation());
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, name())
+                .description(
+                        "Touch down modifier. This operation contains"
+                                + " a list of action executed on Touch down");
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
new file mode 100644
index 0000000..5adfc33
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchHandler.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+
+/** Interface to represent operations that can handle touch events */
+public interface TouchHandler {
+
+    /**
+     * callback for a touch down event
+     *
+     * @param context the current context
+     * @param document the current document
+     * @param component the component on which the touch has been received
+     * @param x the x position of the click in document coordinates
+     * @param y the y position of the click in document coordinates
+     */
+    void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y);
+
+    /**
+     * callback for a touch up event
+     *
+     * @param context the current context
+     * @param document the current document
+     * @param component the component on which the touch has been received
+     * @param x the x position of the click in document coordinates
+     * @param y the y position of the click in document coordinates
+     */
+    void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y);
+
+    /**
+     * callback for a touch cancel event
+     *
+     * @param context the current context
+     * @param document the current document
+     * @param component the component on which the touch has been received
+     * @param x the x position of the click in document coordinates
+     * @param y the y position of the click in document coordinates
+     */
+    void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y);
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
new file mode 100644
index 0000000..263cc43
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/TouchUpModifierOperation.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+
+import java.util.List;
+
+/** Represents a touch up modifier + actions */
+public class TouchUpModifierOperation extends ListActionsOperation implements TouchHandler {
+
+    private static final int OP_CODE = Operations.MODIFIER_TOUCH_UP;
+
+    public TouchUpModifierOperation() {
+        super("TOUCH_UP_MODIFIER");
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer);
+    }
+
+    @Override
+    public String toString() {
+        return "TouchUpModifier";
+    }
+
+    @Override
+    public void apply(RemoteContext context) {
+        RootLayoutComponent root = context.getDocument().getRootLayoutComponent();
+        if (root != null) {
+            root.setHasTouchListeners(true);
+        }
+        super.apply(context);
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        applyActions(context, document, component, x, y, true);
+    }
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        // nothing
+    }
+
+    public static String name() {
+        return "TouchUpModifier";
+    }
+
+    public static void apply(WireBuffer buffer) {
+        buffer.start(OP_CODE);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        operations.add(new TouchUpModifierOperation());
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, name())
+                .description(
+                        "Touch up modifier. This operation contains"
+                                + " a list of action executed on Touch up");
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
index e450585..6036b74 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimateMeasure.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.animation;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
@@ -44,18 +46,23 @@
 
     float mP = 0f;
     float mVp = 0f;
+
+    @NonNull
     FloatAnimation mMotionEasing =
             new FloatAnimation(mMotionEasingType, mDuration / 1000f, null, 0f, Float.NaN);
+
+    @NonNull
     FloatAnimation mVisibilityEasing =
             new FloatAnimation(
                     mVisibilityEasingType, mDurationVisibilityChange / 1000f, null, 0f, Float.NaN);
+
     ParticleAnimation mParticleAnimation;
 
     public AnimateMeasure(
             long startTime,
-            Component component,
+            @NonNull Component component,
             ComponentMeasure original,
-            ComponentMeasure target,
+            @NonNull ComponentMeasure target,
             int duration,
             int durationVisibilityChange,
             AnimationSpec.ANIMATION enterAnimation,
@@ -94,9 +101,9 @@
         mVp = mVisibilityEasing.get(visibilityProgress);
     }
 
-    public PaintBundle paint = new PaintBundle();
+    @NonNull public PaintBundle paint = new PaintBundle();
 
-    public void apply(PaintContext context) {
+    public void apply(@NonNull PaintContext context) {
         update(context.getContext().currentTime);
 
         mComponent.setX(getX());
@@ -338,7 +345,7 @@
         }
     }
 
-    public void updateTarget(ComponentMeasure measure, long currentTime) {
+    public void updateTarget(@NonNull ComponentMeasure measure, long currentTime) {
         mOriginal.setX(getX());
         mOriginal.setY(getY());
         mOriginal.setW(getWidth());
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
index 35533cb..47abade 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/AnimationSpec.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -92,6 +95,7 @@
         return mExitAnimation;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ANIMATION_SPEC (" + mMotionDuration + " ms)";
@@ -109,7 +113,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mAnimationId,
@@ -126,11 +130,13 @@
         // nothing here
     }
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     public static String name() {
         return "AnimationSpec";
     }
@@ -139,10 +145,11 @@
         return Operations.ANIMATION_SPEC;
     }
 
-    public static int animationToInt(ANIMATION animation) {
+    public static int animationToInt(@NonNull ANIMATION animation) {
         return animation.ordinal();
     }
 
+    @NonNull
     public static ANIMATION intToAnimation(int value) {
         switch (value) {
             case 0:
@@ -167,14 +174,14 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int animationId,
             int motionDuration,
             int motionEasingType,
             int visibilityDuration,
             int visibilityEasingType,
-            ANIMATION enterAnimation,
-            ANIMATION exitAnimation) {
+            @NonNull ANIMATION enterAnimation,
+            @NonNull ANIMATION exitAnimation) {
         buffer.start(Operations.ANIMATION_SPEC);
         buffer.writeInt(animationId);
         buffer.writeInt(motionDuration);
@@ -185,7 +192,7 @@
         buffer.writeInt(animationToInt(exitAnimation));
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int animationId = buffer.readInt();
         int motionDuration = buffer.readInt();
         int motionEasingType = buffer.readInt();
@@ -205,7 +212,7 @@
         operations.add(op);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("define the animation")
                 .field(INT, "animationId", "")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
index 686643f..37d2078 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/animation/ParticleAnimation.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.animation;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.measure.ComponentMeasure;
@@ -24,14 +26,14 @@
 import java.util.HashMap;
 
 public class ParticleAnimation {
-    HashMap<Integer, ArrayList<Particle>> mAllParticles = new HashMap<>();
+    @NonNull HashMap<Integer, ArrayList<Particle>> mAllParticles = new HashMap<>();
 
-    PaintBundle mPaint = new PaintBundle();
+    @NonNull PaintBundle mPaint = new PaintBundle();
 
     public void animate(
-            PaintContext context,
-            Component component,
-            ComponentMeasure start,
+            @NonNull PaintContext context,
+            @NonNull Component component,
+            @NonNull ComponentMeasure start,
             ComponentMeasure end,
             float progress) {
         ArrayList<Particle> particles = mAllParticles.get(component.getComponentId());
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
index 047a968..f3e5509 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/BoxLayout.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -75,6 +77,7 @@
                 verticalPositioning);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BOX ["
@@ -93,6 +96,7 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "BOX";
@@ -100,7 +104,11 @@
 
     @Override
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
         for (Component c : mChildrenComponents) {
             c.measure(context, 0f, maxWidth, 0f, maxHeight, measure);
             ComponentMeasure m = measure.get(c);
@@ -119,14 +127,14 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         for (Component child : mChildrenComponents) {
             child.measure(context, minWidth, maxWidth, minHeight, maxHeight, measure);
         }
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, MeasurePass measure) {
+    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
         float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
@@ -161,6 +169,7 @@
         }
     }
 
+    @NonNull
     public static String name() {
         return "BoxLayout";
     }
@@ -170,7 +179,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -182,7 +191,7 @@
         buffer.writeInt(verticalPositioning);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         int horizontalPositioning = buffer.readInt();
@@ -196,7 +205,7 @@
                         verticalPositioning));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "Box layout implementation.\n\n"
@@ -224,7 +233,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId, mAnimationId, mHorizontalPositioning, mVerticalPositioning);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
index f799767..12ff969 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/CanvasLayout.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -44,6 +46,7 @@
         this(parent, componentId, animationId, 0, 0, 0, 0);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "CANVAS ["
@@ -62,11 +65,13 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "CANVAS";
     }
 
+    @NonNull
     public static String name() {
         return "CanvasLayout";
     }
@@ -75,19 +80,19 @@
         return Operations.LAYOUT_CANVAS;
     }
 
-    public static void apply(WireBuffer buffer, int componentId, int animationId) {
+    public static void apply(@NonNull WireBuffer buffer, int componentId, int animationId) {
         buffer.start(Operations.LAYOUT_CANVAS);
         buffer.writeInt(componentId);
         buffer.writeInt(animationId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         operations.add(new CanvasLayout(null, componentId, animationId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("Canvas implementation. Encapsulate draw operations.\n\n")
                 .field(INT, "COMPONENT_ID", "unique id for this component")
@@ -98,7 +103,7 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, MeasurePass measure) {
+    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         float selfWidth = selfMeasure.getW() - mPaddingLeft - mPaddingRight;
         float selfHeight = selfMeasure.getH() - mPaddingTop - mPaddingBottom;
@@ -112,7 +117,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mComponentId, mAnimationId);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
index 402b784..52bf4c5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/ColumnLayout.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -87,6 +89,7 @@
                 spacedBy);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "COLUMN ["
@@ -105,14 +108,24 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "COLUMN";
     }
 
     @Override
+    public boolean isInVerticalFill() {
+        return super.isInVerticalFill() || childrenHaveVerticalWeights();
+    }
+
+    @Override
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
         DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
         int visibleChildrens = 0;
         for (Component c : mChildrenComponents) {
@@ -137,7 +150,7 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")");
         float mh = maxHeight;
         for (Component child : mChildrenComponents) {
@@ -151,7 +164,7 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, MeasurePass measure) {
+    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         DebugLog.s(
                 () ->
@@ -302,6 +315,7 @@
         DebugLog.e();
     }
 
+    @NonNull
     public static String name() {
         return "ColumnLayout";
     }
@@ -311,7 +325,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -325,7 +339,7 @@
         buffer.writeFloat(spacedBy);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         int horizontalPositioning = buffer.readInt();
@@ -341,7 +355,7 @@
                         spacedBy));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "Column layout implementation, positioning components one"
@@ -374,7 +388,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mComponentId,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
index 308ed64..0c4d24a 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/LayoutManager.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.managers;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
@@ -27,7 +29,7 @@
 /** Base class for layout managers -- resizable components. */
 public abstract class LayoutManager extends LayoutComponent implements Measurable {
 
-    Size mCachedWrapSize = new Size(0f, 0f);
+    @NonNull Size mCachedWrapSize = new Size(0f, 0f);
 
     public LayoutManager(
             Component parent,
@@ -62,6 +64,38 @@
         // nothing here
     }
 
+    protected boolean childrenHaveHorizontalWeights() {
+        for (Component c : mChildrenComponents) {
+            if (c instanceof LayoutManager) {
+                LayoutManager m = (LayoutManager) c;
+                if (m.getWidthModifier() != null && m.getWidthModifier().hasWeight()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    protected boolean childrenHaveVerticalWeights() {
+        for (Component c : mChildrenComponents) {
+            if (c instanceof LayoutManager) {
+                LayoutManager m = (LayoutManager) c;
+                if (m.getHeightModifier() != null && m.getHeightModifier().hasWeight()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean isInHorizontalFill() {
+        return mWidthModifier.isFill();
+    }
+
+    public boolean isInVerticalFill() {
+        return mHeightModifier.isFill();
+    }
+
     /** Base implementation of the measure resolution */
     @Override
     public void measure(
@@ -70,7 +104,7 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         boolean hasWrap = true;
         float measuredWidth =
                 Math.min(maxWidth, computeModifierDefinedWidth() - mMarginLeft - mMarginRight);
@@ -87,7 +121,7 @@
         } else {
             hasWrap = false;
         }
-        if (mWidthModifier.isFill()) {
+        if (isInHorizontalFill()) {
             measuredWidth = insetMaxWidth;
         } else if (mWidthModifier.hasWeight()) {
             measuredWidth = Math.max(measuredWidth, computeModifierDefinedWidth());
@@ -95,7 +129,7 @@
             measuredWidth = Math.max(measuredWidth, minWidth);
             measuredWidth = Math.min(measuredWidth, insetMaxWidth);
         }
-        if (mHeightModifier.isFill()) {
+        if (isInVerticalFill()) {
             measuredHeight = insetMaxHeight;
         } else if (mHeightModifier.hasWeight()) {
             measuredHeight = Math.max(measuredHeight, computeModifierDefinedHeight());
@@ -136,7 +170,7 @@
 
     /** basic layout of internal components */
     @Override
-    public void layout(RemoteContext context, MeasurePass measure) {
+    public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) {
         super.layout(context, measure);
         ComponentMeasure self = measure.get(this);
 
@@ -153,7 +187,7 @@
      * @param context
      * @param measure
      */
-    public void selfLayout(RemoteContext context, MeasurePass measure) {
+    public void selfLayout(@NonNull RemoteContext context, @NonNull MeasurePass measure) {
         super.layout(context, measure);
         ComponentMeasure self = measure.get(this);
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
index b29a05c..a366dc8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/RowLayout.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -85,6 +87,7 @@
                 spacedBy);
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ROW ["
@@ -103,14 +106,24 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "ROW";
     }
 
     @Override
+    public boolean isInHorizontalFill() {
+        return super.isInHorizontalFill() || childrenHaveHorizontalWeights();
+    }
+
+    @Override
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            @NonNull MeasurePass measure,
+            @NonNull Size size) {
         DebugLog.s(() -> "COMPUTE WRAP SIZE in " + this + " (" + mComponentId + ")");
         //        int visibleChildrens = 0;
         for (Component c : mChildrenComponents) {
@@ -135,7 +148,7 @@
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         DebugLog.s(() -> "COMPUTE SIZE in " + this + " (" + mComponentId + ")");
         float mw = maxWidth;
         for (Component child : mChildrenComponents) {
@@ -149,7 +162,7 @@
     }
 
     @Override
-    public void internalLayoutMeasure(PaintContext context, MeasurePass measure) {
+    public void internalLayoutMeasure(PaintContext context, @NonNull MeasurePass measure) {
         ComponentMeasure selfMeasure = measure.get(this);
         DebugLog.s(
                 () ->
@@ -305,6 +318,7 @@
         DebugLog.e();
     }
 
+    @NonNull
     public static String name() {
         return "RowLayout";
     }
@@ -314,7 +328,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -328,7 +342,7 @@
         buffer.writeFloat(spacedBy);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         int horizontalPositioning = buffer.readInt();
@@ -344,7 +358,7 @@
                         spacedBy));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description(
                         "Row layout implementation, positioning components one"
@@ -377,7 +391,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mComponentId,
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
index b5c7281..e47ffde 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/StateLayout.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.managers;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -50,10 +52,10 @@
     // This keep track of all the components associated with a given Id,
     // (the key being the id), and the set of components corresponds to the set of states
     // TODO: we should be able to optimize this
-    public Map<Integer, Component[]> statePaintedComponents = new HashMap<>();
+    @NonNull public Map<Integer, Component[]> statePaintedComponents = new HashMap<>();
 
     public int MAX_CACHE_ELEMENTS = 16;
-    public int[] cacheListElementsId = new int[MAX_CACHE_ELEMENTS];
+    @NonNull public int[] cacheListElementsId = new int[MAX_CACHE_ELEMENTS];
 
     public boolean inTransition = false;
 
@@ -168,7 +170,7 @@
     }
 
     @Override
-    public void layout(RemoteContext context, MeasurePass measure) {
+    public void layout(@NonNull RemoteContext context, @NonNull MeasurePass measure) {
         ComponentMeasure self = measure.get(this);
         super.selfLayout(context, measure);
 
@@ -207,12 +209,12 @@
 
     @Override
     public void measure(
-            PaintContext context,
+            @NonNull PaintContext context,
             float minWidth,
             float maxWidth,
             float minHeight,
             float maxHeight,
-            MeasurePass measure) {
+            @NonNull MeasurePass measure) {
         // The general approach for this widget is to do most of the work/setup in measure.
         // layout and paint then simply use what's been setup in the measure phase.
 
@@ -364,19 +366,20 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         if (mIndexId != 0) {
             int newValue = context.getContext().mRemoteComposeState.getInteger(mIndexId);
             if (newValue != currentLayoutIndex) {
                 previousLayoutIndex = currentLayoutIndex;
                 currentLayoutIndex = newValue;
                 inTransition = true;
-                System.out.println("currentLayout index is $currentLayoutIndex");
+                // System.out.println("currentLayout index is $currentLayoutIndex");
                 // executeValueSetActions(getLayout(currentLayoutIndex));
                 invalidateMeasure();
             }
         }
-        System.out.println("PAINTING LAYOUT STATELAYOUT, CURRENT INDEX " + currentLayoutIndex);
+        //        System.out.println("PAINTING LAYOUT STATELAYOUT, CURRENT INDEX " +
+        // currentLayoutIndex);
         // Make sure to mark any components that are not in either the current or previous layout
         // as being GONE.
         int index = 0;
@@ -529,6 +532,7 @@
     //        }
     //    }
 
+    @NonNull
     @Override
     public String toString() {
         return "STATE_LAYOUT";
@@ -539,7 +543,7 @@
     //    }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int horizontalPositioning,
@@ -553,7 +557,7 @@
         buffer.writeInt(indexId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         buffer.readInt(); // horizontalPositioning
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
index c1cabcd..8aa7712 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/managers/TextLayout.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -44,22 +46,24 @@
     private int mFontStyle = 0;
     private float mFontWeight = 400f;
     private int mFontFamilyId = -1;
+    private int mTextAlign = -1;
 
     private int mType = -1;
     private float mTextX;
     private float mTextY;
+    private float mTextW;
 
     private String mCachedString = "";
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (mTextId != -1) {
             context.listensTo(mTextId, this);
         }
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         mCachedString = context.getText(mTextId);
         if (mType == -1) {
             if (mFontFamilyId != -1) {
@@ -97,7 +101,8 @@
             float fontSize,
             int fontStyle,
             float fontWeight,
-            int fontFamilyId) {
+            int fontFamilyId,
+            int textAlign) {
         super(parent, componentId, animationId, x, y, width, height);
         mTextId = textId;
         mColor = color;
@@ -105,6 +110,7 @@
         mFontStyle = fontStyle;
         mFontWeight = fontWeight;
         mFontFamilyId = fontFamilyId;
+        mTextAlign = textAlign;
     }
 
     public TextLayout(
@@ -116,7 +122,8 @@
             float fontSize,
             int fontStyle,
             float fontWeight,
-            int fontFamilyId) {
+            int fontFamilyId,
+            int textAlign) {
         this(
                 parent,
                 componentId,
@@ -130,13 +137,14 @@
                 fontSize,
                 fontStyle,
                 fontWeight,
-                fontFamilyId);
+                fontFamilyId,
+                textAlign);
     }
 
-    public PaintBundle mPaint = new PaintBundle();
+    @NonNull public PaintBundle mPaint = new PaintBundle();
 
     @Override
-    public void paintingComponent(PaintContext context) {
+    public void paintingComponent(@NonNull PaintContext context) {
         context.save();
         context.translate(mX, mY);
         mComponentModifiers.paint(context);
@@ -176,6 +184,7 @@
         context.restore();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "TEXT_LAYOUT ["
@@ -194,13 +203,14 @@
                 + mVisibility;
     }
 
+    @NonNull
     @Override
     protected String getSerializedName() {
         return "TEXT_LAYOUT";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 getSerializedName()
@@ -228,7 +238,11 @@
 
     @Override
     public void computeWrapSize(
-            PaintContext context, float maxWidth, float maxHeight, MeasurePass measure, Size size) {
+            @NonNull PaintContext context,
+            float maxWidth,
+            float maxHeight,
+            MeasurePass measure,
+            @NonNull Size size) {
         context.savePaint();
         mPaint.reset();
         mPaint.setTextSize(mFontSize);
@@ -244,8 +258,10 @@
         mTextX = -bounds[0];
         size.setHeight(h);
         mTextY = -bounds[1];
+        mTextW = w;
     }
 
+    @NonNull
     public static String name() {
         return "TextLayout";
     }
@@ -255,7 +271,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             int componentId,
             int animationId,
             int textId,
@@ -263,7 +279,8 @@
             float fontSize,
             int fontStyle,
             float fontWeight,
-            int fontFamilyId) {
+            int fontFamilyId,
+            int textAlign) {
         buffer.start(id());
         buffer.writeInt(componentId);
         buffer.writeInt(animationId);
@@ -273,9 +290,10 @@
         buffer.writeInt(fontStyle);
         buffer.writeFloat(fontWeight);
         buffer.writeInt(fontFamilyId);
+        buffer.writeInt(textAlign);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int componentId = buffer.readInt();
         int animationId = buffer.readInt();
         int textId = buffer.readInt();
@@ -284,6 +302,7 @@
         int fontStyle = buffer.readInt();
         float fontWeight = buffer.readFloat();
         int fontFamilyId = buffer.readInt();
+        int textAlign = buffer.readInt();
         operations.add(
                 new TextLayout(
                         null,
@@ -294,10 +313,11 @@
                         fontSize,
                         fontStyle,
                         fontWeight,
-                        fontFamilyId));
+                        fontFamilyId,
+                        textAlign));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", id(), name())
                 .description("Text layout implementation.\n\n")
                 .field(INT, "COMPONENT_ID", "unique id for this component")
@@ -313,7 +333,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mComponentId,
@@ -323,6 +343,7 @@
                 mFontSize,
                 mFontStyle,
                 mFontWeight,
-                mFontFamilyId);
+                mFontFamilyId,
+                mTextAlign);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
index 285425f..426e023 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/ComponentMeasure.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.measure;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 
 /** Encapsulate the result of a measure pass for a component */
@@ -80,7 +82,7 @@
         this(id, x, y, w, h, Component.Visibility.VISIBLE);
     }
 
-    public ComponentMeasure(Component component) {
+    public ComponentMeasure(@NonNull Component component) {
         this(
                 component.getComponentId(),
                 component.getX(),
@@ -90,7 +92,7 @@
                 component.mVisibility);
     }
 
-    public void copyFrom(ComponentMeasure m) {
+    public void copyFrom(@NonNull ComponentMeasure m) {
         mX = m.mX;
         mY = m.mY;
         mW = m.mW;
@@ -98,7 +100,7 @@
         mVisibility = m.mVisibility;
     }
 
-    public boolean same(ComponentMeasure m) {
+    public boolean same(@NonNull ComponentMeasure m) {
         return mX == m.mX && mY == m.mY && mW == m.mW && mH == m.mH && mVisibility == m.mVisibility;
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
index 8d01fea..112ab1b 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/measure/MeasurePass.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.measure;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 
 import java.util.HashMap;
@@ -24,13 +26,13 @@
  * array vs the current hashmap
  */
 public class MeasurePass {
-    HashMap<Integer, ComponentMeasure> mList = new HashMap<>();
+    @NonNull HashMap<Integer, ComponentMeasure> mList = new HashMap<>();
 
     public void clear() {
         mList.clear();
     }
 
-    public void add(ComponentMeasure measure) throws Exception {
+    public void add(@NonNull ComponentMeasure measure) throws Exception {
         if (measure.mId == -1) {
             throw new Exception("Component has no id!");
         }
@@ -41,7 +43,7 @@
         return mList.containsKey(id);
     }
 
-    public ComponentMeasure get(Component c) {
+    public ComponentMeasure get(@NonNull Component c) {
         if (!mList.containsKey(c.getComponentId())) {
             ComponentMeasure measure =
                     new ComponentMeasure(
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
index 64e40f7..76a97ca 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BackgroundModifierOperation.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -42,7 +44,7 @@
     float mA;
     int mShapeType = ShapeType.RECTANGLE;
 
-    public PaintBundle mPaint = new PaintBundle();
+    @NonNull public PaintBundle mPaint = new PaintBundle();
 
     public BackgroundModifierOperation(
             float x,
@@ -66,12 +68,12 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mX, mY, mWidth, mHeight, mR, mG, mB, mA, mShapeType);
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 "BACKGROUND = ["
@@ -101,11 +103,13 @@
         this.mHeight = height;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BackgroundModifierOperation(" + mWidth + " x " + mHeight + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -115,7 +119,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             float x,
             float y,
             float width,
@@ -138,7 +142,7 @@
         buffer.writeInt(shapeType);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float x = buffer.readFloat();
         float y = buffer.readFloat();
         float width = buffer.readFloat();
@@ -153,7 +157,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.savePaint();
         mPaint.reset();
         mPaint.setStyle(PaintBundle.STYLE_FILL);
@@ -167,7 +171,7 @@
         context.restorePaint();
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Background Modifier")
                 .field(FLOAT, "x", "")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
index 92c0a73..d48a9c7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/BorderModifierOperation.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -45,7 +47,7 @@
     float mA;
     int mShapeType = ShapeType.RECTANGLE;
 
-    public PaintBundle paint = new PaintBundle();
+    @NonNull public PaintBundle paint = new PaintBundle();
 
     public BorderModifierOperation(
             float x,
@@ -73,7 +75,7 @@
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 "BORDER = ["
@@ -105,7 +107,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(
                 buffer,
                 mX,
@@ -127,6 +129,7 @@
         this.mHeight = height;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BorderModifierOperation("
@@ -152,6 +155,7 @@
                 + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -161,7 +165,7 @@
     }
 
     public static void apply(
-            WireBuffer buffer,
+            @NonNull WireBuffer buffer,
             float x,
             float y,
             float width,
@@ -188,7 +192,7 @@
         buffer.writeInt(shapeType);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float x = buffer.readFloat();
         float y = buffer.readFloat();
         float width = buffer.readFloat();
@@ -206,7 +210,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.savePaint();
         paint.reset();
         paint.setColor(mR, mG, mB, mA);
@@ -225,7 +229,7 @@
         context.restorePaint();
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Border Modifier")
                 .field(FLOAT, "x", "")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
index 0d8aeaa..78b51c3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ClipRectModifierOperation.java
@@ -15,14 +15,14 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
-import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.List;
@@ -35,7 +35,7 @@
     float mHeight;
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.clipRect(0f, 0f, mWidth, mHeight);
     }
 
@@ -46,21 +46,16 @@
     }
 
     @Override
-    public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        // nothing
-    }
-
-    @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, "CLIP_RECT = [" + mWidth + ", " + mHeight + "]");
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer);
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -69,15 +64,15 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer) {
+    public static void apply(@NonNull WireBuffer buffer) {
         buffer.start(OP_CODE);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(WireBuffer buffer, @NonNull List<Operation> operations) {
         operations.add(new ClipRectModifierOperation());
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Canvas Operations", OP_CODE, CLASS_NAME)
                 .description("Draw the specified round-rect");
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
index 95786a8..011d7ed 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentModifiers.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.PaintContext;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
@@ -22,29 +24,34 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.operations.MatrixRestore;
 import com.android.internal.widget.remotecompose.core.operations.MatrixSave;
+import com.android.internal.widget.remotecompose.core.operations.layout.ClickHandler;
 import com.android.internal.widget.remotecompose.core.operations.layout.ClickModifierOperation;
 import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
+import com.android.internal.widget.remotecompose.core.operations.layout.TouchHandler;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
 import java.util.ArrayList;
 
 /** Maintain a list of modifiers */
-public class ComponentModifiers extends PaintOperation implements DecoratorComponent {
-    ArrayList<ModifierOperation> mList = new ArrayList<>();
+public class ComponentModifiers extends PaintOperation
+        implements DecoratorComponent, ClickHandler, TouchHandler {
+    @NonNull ArrayList<ModifierOperation> mList = new ArrayList<>();
 
+    @NonNull
     public ArrayList<ModifierOperation> getList() {
         return mList;
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         super.apply(context);
         for (ModifierOperation op : mList) {
             op.apply(context);
         }
     }
 
+    @NonNull
     @Override
     public String toString() {
         String str = "ComponentModifiers \n";
@@ -59,7 +66,7 @@
         // nothing
     }
 
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, "MODIFIERS");
         for (ModifierOperation m : mList) {
             m.serializeToString(indent + 1, serializer);
@@ -75,7 +82,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         float tx = 0f;
         float ty = 0f;
         for (ModifierOperation op : mList) {
@@ -127,8 +134,38 @@
     public void onClick(
             RemoteContext context, CoreDocument document, Component component, float x, float y) {
         for (ModifierOperation op : mList) {
-            if (op instanceof DecoratorComponent) {
-                ((DecoratorComponent) op).onClick(context, document, component, x, y);
+            if (op instanceof ClickHandler) {
+                ((ClickHandler) op).onClick(context, document, component, x, y);
+            }
+        }
+    }
+
+    @Override
+    public void onTouchDown(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        for (ModifierOperation op : mList) {
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchDown(context, document, component, x, y);
+            }
+        }
+    }
+
+    @Override
+    public void onTouchUp(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        for (ModifierOperation op : mList) {
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchUp(context, document, component, x, y);
+            }
+        }
+    }
+
+    @Override
+    public void onTouchCancel(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        for (ModifierOperation op : mList) {
+            if (op instanceof TouchHandler) {
+                ((TouchHandler) op).onTouchCancel(context, document, component, x, y);
             }
         }
     }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
index 312d016..26e737b3 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ComponentVisibilityOperation.java
@@ -17,7 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -37,49 +39,52 @@
     private static final int OP_CODE = Operations.MODIFIER_VISIBILITY;
 
     int mVisibilityId;
-    Component.Visibility mVisibility = Component.Visibility.VISIBLE;
+    @NonNull Component.Visibility mVisibility = Component.Visibility.VISIBLE;
     private LayoutComponent mParent;
 
     public ComponentVisibilityOperation(int id) {
         mVisibilityId = id;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ComponentVisibilityOperation(" + mVisibilityId + ")";
     }
 
+    @NonNull
     public String serializedName() {
         return "COMPONENT_VISIBILITY";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, serializedName() + " = " + mVisibilityId);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
     @Override
     public void write(WireBuffer buffer) {}
 
-    public static void apply(WireBuffer buffer, int valueId) {
+    public static void apply(@NonNull WireBuffer buffer, int valueId) {
         buffer.start(OP_CODE);
         buffer.writeInt(valueId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         operations.add(new ComponentVisibilityOperation(valueId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ComponentVisibility")
                 .description(
                         "This operation allows setting a component"
@@ -88,12 +93,12 @@
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         context.listensTo(mVisibilityId, this);
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         int visibility = context.getInteger(mVisibilityId);
         if (visibility == Component.Visibility.VISIBLE.ordinal()) {
             mVisibility = Component.Visibility.VISIBLE;
@@ -115,8 +120,4 @@
 
     @Override
     public void layout(RemoteContext context, float width, float height) {}
-
-    @Override
-    public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {}
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
index 41e18cb..b4c4108 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DecoratorModifierOperation.java
@@ -15,10 +15,7 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.PaintOperation;
-import com.android.internal.widget.remotecompose.core.RemoteContext;
-import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
 
 /**
@@ -26,11 +23,4 @@
  * output (background, border...)
  */
 public abstract class DecoratorModifierOperation extends PaintOperation
-        implements ModifierOperation, DecoratorComponent {
-
-    @Override
-    public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        // nothing
-    }
-}
+        implements ModifierOperation, DecoratorComponent {}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
index 408bebc..3c2d85c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/DimensionModifierOperation.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.RemoteContext;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.operations.Utils;
@@ -29,8 +32,10 @@
         WRAP,
         WEIGHT,
         INTRINSIC_MIN,
-        INTRINSIC_MAX;
+        INTRINSIC_MAX,
+        EXACT_DP;
 
+        @NonNull
         static Type fromInt(int value) {
             switch (value) {
                 case 0:
@@ -45,6 +50,8 @@
                     return INTRINSIC_MIN;
                 case 5:
                     return INTRINSIC_MAX;
+                case 6:
+                    return EXACT_DP;
             }
             return EXACT;
         }
@@ -68,19 +75,32 @@
     }
 
     @Override
-    public void updateVariables(RemoteContext context) {
+    public void updateVariables(@NonNull RemoteContext context) {
         if (mType == Type.EXACT) {
             mOutValue = Float.isNaN(mValue) ? context.getFloat(Utils.idFromNan(mValue)) : mValue;
         }
+        if (mType == Type.EXACT_DP) {
+            float pre = mOutValue;
+            mOutValue = Float.isNaN(mValue) ? context.getFloat(Utils.idFromNan(mValue)) : mValue;
+            mOutValue *= context.getDensity();
+            if (pre != mOutValue) {
+                context.getDocument().getRootLayoutComponent().invalidateMeasure();
+            }
+        }
     }
 
     @Override
-    public void registerListening(RemoteContext context) {
+    public void registerListening(@NonNull RemoteContext context) {
         if (mType == Type.EXACT) {
             if (Float.isNaN(mValue)) {
                 context.listensTo(Utils.idFromNan(mValue), this);
             }
         }
+        if (mType == Type.EXACT_DP) {
+            if (Float.isNaN(mValue)) {
+                context.listensTo(Utils.idFromNan(mValue), this);
+            }
+        }
     }
 
     public boolean hasWeight() {
@@ -107,25 +127,31 @@
         mOutValue = mValue = value;
     }
 
+    @NonNull
     public String serializedName() {
         return "DIMENSION";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         if (mType == Type.EXACT) {
             serializer.append(indent, serializedName() + " = " + mValue);
         }
+        if (mType == Type.EXACT_DP) {
+            serializer.append(indent, serializedName() + " = " + mValue + " dp");
+        }
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "DimensionModifierOperation(" + mValue + ")";
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
new file mode 100644
index 0000000..2b30382
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/GraphicsLayerModifierOperation.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.AnimatableValue;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/**
+ * Represents a padding modifier. Padding modifiers can be chained and will impact following
+ * modifiers.
+ */
+public class GraphicsLayerModifierOperation extends DecoratorModifierOperation {
+    private static final int OP_CODE = Operations.MODIFIER_GRAPHICS_LAYER;
+    public static final String CLASS_NAME = "GraphicsLayerModifierOperation";
+
+    AnimatableValue mScaleX;
+    AnimatableValue mScaleY;
+    AnimatableValue mRotationX;
+    AnimatableValue mRotationY;
+    AnimatableValue mRotationZ;
+    AnimatableValue mTransformOriginX;
+    AnimatableValue mTransformOriginY;
+    AnimatableValue mShadowElevation;
+    AnimatableValue mAlpha;
+    AnimatableValue mCameraDistance;
+    int mBlendMode;
+    int mSpotShadowColorId;
+    int mAmbientShadowColorId;
+    int mColorFilterId;
+    int mRenderEffectId;
+
+    public GraphicsLayerModifierOperation(
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            float cameraDistance,
+            int blendMode,
+            int spotShadowColorId,
+            int ambientShadowColorId,
+            int colorFilterId,
+            int renderEffectId) {
+        mScaleX = new AnimatableValue(scaleX);
+        mScaleY = new AnimatableValue(scaleY);
+        mRotationX = new AnimatableValue(rotationX);
+        mRotationY = new AnimatableValue(rotationY);
+        mRotationZ = new AnimatableValue(rotationZ);
+        mShadowElevation = new AnimatableValue(shadowElevation);
+        mTransformOriginX = new AnimatableValue(transformOriginX);
+        mTransformOriginY = new AnimatableValue(transformOriginY);
+        mAlpha = new AnimatableValue(alpha);
+        mCameraDistance = new AnimatableValue(cameraDistance);
+        mBlendMode = blendMode;
+        mSpotShadowColorId = spotShadowColorId;
+        mAmbientShadowColorId = ambientShadowColorId;
+        mColorFilterId = colorFilterId;
+        mRenderEffectId = renderEffectId;
+    }
+
+    public float getScaleX() {
+        return mScaleX.getValue();
+    }
+
+    public float getScaleY() {
+        return mScaleY.getValue();
+    }
+
+    public float getRotationX() {
+        return mRotationX.getValue();
+    }
+
+    public float getRotationY() {
+        return mRotationY.getValue();
+    }
+
+    public float getRotationZ() {
+        return mRotationZ.getValue();
+    }
+
+    public float getShadowElevation() {
+        return mShadowElevation.getValue();
+    }
+
+    public float getTransformOriginX() {
+        return mTransformOriginX.getValue();
+    }
+
+    public float getTransformOriginY() {
+        return mTransformOriginY.getValue();
+    }
+
+    public float getAlpha() {
+        return mAlpha.getValue();
+    }
+
+    public float getCameraDistance() {
+        return mCameraDistance.getValue();
+    }
+
+    // TODO: add implementation for blendmode
+    public int getBlendModeId() {
+        return mBlendMode;
+    }
+
+    // TODO: add implementation for shadow
+    public int getSpotShadowColorId() {
+        return mSpotShadowColorId;
+    }
+
+    public int getAmbientShadowColorId() {
+        return mAmbientShadowColorId;
+    }
+
+    // TODO: add implementation for color filters
+    public int getColorFilterId() {
+        return mColorFilterId;
+    }
+
+    public int getRenderEffectId() {
+        return mRenderEffectId;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(
+                buffer,
+                mScaleX.getValue(),
+                mScaleY.getValue(),
+                mRotationX.getValue(),
+                mRotationY.getValue(),
+                mRotationZ.getValue(),
+                mShadowElevation.getValue(),
+                mTransformOriginX.getValue(),
+                mTransformOriginY.getValue(),
+                mAlpha.getValue(),
+                mCameraDistance.getValue(),
+                mBlendMode,
+                mSpotShadowColorId,
+                mAmbientShadowColorId,
+                mColorFilterId,
+                mRenderEffectId);
+    }
+
+    @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, "GRAPHICS_LAYER = [" + mScaleX + ", " + mScaleY + "]");
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        mScaleX.evaluate(context);
+        mScaleY.evaluate(context);
+        mRotationX.evaluate(context);
+        mRotationY.evaluate(context);
+        mRotationZ.evaluate(context);
+        mTransformOriginX.evaluate(context);
+        mTransformOriginY.evaluate(context);
+        mShadowElevation.evaluate(context);
+        mAlpha.evaluate(context);
+        mCameraDistance.evaluate(context);
+    }
+
+    @Override
+    public String toString() {
+        return "GraphicsLayerModifierOperation(" + mScaleX + ", " + mScaleY + ")";
+    }
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(
+            WireBuffer buffer,
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            float cameraDistance,
+            int blendMode,
+            int spotShadowColorId,
+            int ambientShadowColorId,
+            int colorFilterId,
+            int renderEffectId) {
+        buffer.start(OP_CODE);
+        buffer.writeFloat(scaleX);
+        buffer.writeFloat(scaleY);
+        buffer.writeFloat(rotationX);
+        buffer.writeFloat(rotationY);
+        buffer.writeFloat(rotationZ);
+        buffer.writeFloat(shadowElevation);
+        buffer.writeFloat(transformOriginX);
+        buffer.writeFloat(transformOriginY);
+        buffer.writeFloat(alpha);
+        buffer.writeFloat(cameraDistance);
+        buffer.writeInt(blendMode);
+        buffer.writeInt(spotShadowColorId);
+        buffer.writeInt(ambientShadowColorId);
+        buffer.writeInt(colorFilterId);
+        buffer.writeInt(renderEffectId);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        float scaleX = buffer.readFloat();
+        float scaleY = buffer.readFloat();
+        float rotationX = buffer.readFloat();
+        float rotationY = buffer.readFloat();
+        float rotationZ = buffer.readFloat();
+        float shadowElevation = buffer.readFloat();
+        float transformOriginX = buffer.readFloat();
+        float transformOriginY = buffer.readFloat();
+        float alpha = buffer.readFloat();
+        float cameraDistance = buffer.readFloat();
+        int blendMode = buffer.readInt();
+        int spotShadowColorId = buffer.readInt();
+        int ambientShadowColorId = buffer.readInt();
+        int colorFilterId = buffer.readInt();
+        int renderEffectId = buffer.readInt();
+        operations.add(
+                new GraphicsLayerModifierOperation(
+                        scaleX,
+                        scaleY,
+                        rotationX,
+                        rotationY,
+                        rotationZ,
+                        shadowElevation,
+                        transformOriginX,
+                        transformOriginY,
+                        alpha,
+                        cameraDistance,
+                        blendMode,
+                        spotShadowColorId,
+                        ambientShadowColorId,
+                        colorFilterId,
+                        renderEffectId));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+                .description("define the GraphicsLayer Modifier")
+                .field(FLOAT, "scaleX", "")
+                .field(FLOAT, "scaleY", "")
+                .field(FLOAT, "rotationX", "")
+                .field(FLOAT, "rotationY", "")
+                .field(FLOAT, "rotationZ", "")
+                .field(FLOAT, "shadowElevation", "")
+                .field(FLOAT, "transformOriginX", "")
+                .field(FLOAT, "transformOriginY", "")
+                .field(FLOAT, "alpha", "")
+                .field(FLOAT, "cameraDistance", "")
+                .field(INT, "blendMode", "")
+                .field(INT, "spotShadowColorId", "")
+                .field(INT, "ambientShadowColorId", "")
+                .field(INT, "colorFilterId", "")
+                .field(INT, "renderEffectId", "");
+    }
+
+    @Override
+    public void layout(RemoteContext context, float width, float height) {}
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
index d3613f8..97c76c0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HeightModifierOperation.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
@@ -30,6 +32,7 @@
     private static final int OP_CODE = Operations.MODIFIER_HEIGHT;
     public static final String CLASS_NAME = "HeightModifierOperation";
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -38,13 +41,13 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int type, float value) {
+    public static void apply(@NonNull WireBuffer buffer, int type, float value) {
         buffer.start(OP_CODE);
         buffer.writeInt(type);
         buffer.writeFloat(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Type type = Type.fromInt(buffer.readInt());
         float value = buffer.readFloat();
         Operation op = new HeightModifierOperation(type, value);
@@ -52,7 +55,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mType.ordinal(), mValue);
     }
 
@@ -68,17 +71,19 @@
         super(value);
     }
 
+    @NonNull
     @Override
     public String toString() {
-        return "Height(" + mValue + ")";
+        return "Height(" + mType + ", " + mValue + ")";
     }
 
+    @NonNull
     @Override
     public String serializedName() {
         return "HEIGHT";
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the animation")
                 .field(INT, "type", "")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
index ac42470a..836321f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostActionOperation.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -39,6 +42,7 @@
         mActionId = id;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "HostActionOperation(" + mActionId + ")";
@@ -48,20 +52,22 @@
         return mActionId;
     }
 
+    @NonNull
     public String serializedName() {
         return "HOST_ACTION";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, serializedName() + " = " + mActionId);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -70,21 +76,25 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            @NonNull RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y) {
         context.runAction(mActionId, "");
     }
 
-    public static void apply(WireBuffer buffer, int actionId) {
+    public static void apply(@NonNull WireBuffer buffer, int actionId) {
         buffer.start(OP_CODE);
         buffer.writeInt(actionId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int actionId = buffer.readInt();
         operations.add(new HostActionOperation(actionId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "HostAction")
                 .description("Host action. This operation represents a host action")
                 .field(INT, "ACTION_ID", "Host Action ID");
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
index b674a58..e97e897 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/HostNamedActionOperation.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -33,31 +36,47 @@
 public class HostNamedActionOperation implements ActionOperation {
     private static final int OP_CODE = Operations.HOST_NAMED_ACTION;
 
-    int mTextId = -1;
+    public static final int FLOAT_TYPE = 0;
+    public static final int INT_TYPE = 1;
+    public static final int STRING_TYPE = 2;
+    public static final int NONE_TYPE = -1;
 
-    public HostNamedActionOperation(int id) {
+    int mTextId = -1;
+    int mType = NONE_TYPE;
+    int mValueId = -1;
+
+    public HostNamedActionOperation(int id, int type, int valueId) {
         mTextId = id;
+        mType = type;
+        mValueId = valueId;
     }
 
+    @NonNull
     @Override
     public String toString() {
-        return "HostNamedActionOperation(" + mTextId + ")";
+        return "HostNamedActionOperation(" + mTextId + " : " + mValueId + ")";
     }
 
+    @NonNull
     public String serializedName() {
         return "HOST_NAMED_ACTION";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
-        serializer.append(indent, serializedName() + " = " + mTextId);
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
+        if (mValueId != -1) {
+            serializer.append(indent, serializedName() + " = " + mTextId + " : " + mValueId);
+        } else {
+            serializer.append(indent, serializedName() + " = " + mTextId);
+        }
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -66,23 +85,42 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        context.runNamedAction(mTextId);
+            @NonNull RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y) {
+        Object value = null;
+        if (mValueId != -1) {
+            if (mType == INT_TYPE) {
+                value = context.mRemoteComposeState.getInteger(mValueId);
+            } else if (mType == STRING_TYPE) {
+                value = context.mRemoteComposeState.getFromId(mValueId);
+            } else if (mType == FLOAT_TYPE) {
+                value = context.mRemoteComposeState.getFloat(mValueId);
+            }
+        }
+        context.runNamedAction(mTextId, value);
     }
 
-    public static void apply(WireBuffer buffer, int textId) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int type, int valueId) {
         buffer.start(OP_CODE);
         buffer.writeInt(textId);
+        buffer.writeInt(type);
+        buffer.writeInt(valueId);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int textId = buffer.readInt();
-        operations.add(new HostNamedActionOperation(textId));
+        int type = buffer.readInt();
+        int valueId = buffer.readInt();
+        operations.add(new HostNamedActionOperation(textId, type, valueId));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "HostNamedAction")
                 .description("Host Named action. This operation represents a host action")
-                .field(INT, "TEXT_ID", "Named Host Action Text ID");
+                .field(INT, "TEXT_ID", "Named Host Action Text ID")
+                .field(INT, "VALUE_ID", "Named Host Action Value ID");
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
new file mode 100644
index 0000000..65fe345
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/OffsetModifierOperation.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Represents an offset modifier. */
+public class OffsetModifierOperation extends DecoratorModifierOperation {
+    private static final int OP_CODE = Operations.MODIFIER_OFFSET;
+    public static final String CLASS_NAME = "OffsetModifierOperation";
+
+    float mX;
+    float mY;
+
+    public OffsetModifierOperation(float x, float y) {
+        this.mX = x;
+        this.mY = y;
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public void setX(float x) {
+        this.mX = x;
+    }
+
+    public void setY(float y) {
+        this.mY = y;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer, mX, mY);
+    }
+
+    // @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, "OFFSET = [" + mX + ", " + mY + "]");
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        float x = context.getContext().mRemoteComposeState.getFloat(Utils.idFromNan(mX));
+        float y = context.getContext().mRemoteComposeState.getFloat(Utils.idFromNan(mY));
+        float density = context.getContext().getDensity();
+        x *= density;
+        y *= density;
+        context.translate(x, y);
+    }
+
+    @Override
+    public String toString() {
+        return "OffsetModifierOperation(" + mX + ", " + mY + ")";
+    }
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(WireBuffer buffer, float x, float y) {
+        buffer.start(OP_CODE);
+        buffer.writeFloat(x);
+        buffer.writeFloat(y);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        float x = buffer.readFloat();
+        float y = buffer.readFloat();
+        operations.add(new OffsetModifierOperation(x, y));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+                .description("define the Offset Modifier")
+                .field(FLOAT, "x", "")
+                .field(FLOAT, "y", "");
+    }
+
+    @Override
+    public void layout(RemoteContext context, float width, float height) {}
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
index e0ec1a6..ed5522e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/PaddingModifierOperation.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -78,12 +81,12 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mLeft, mTop, mRight, mBottom);
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent, "PADDING = [" + mLeft + ", " + mTop + ", " + mRight + ", " + mBottom + "]");
     }
@@ -91,11 +94,13 @@
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "PaddingModifierOperation("
@@ -109,6 +114,7 @@
                 + ")";
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -117,7 +123,8 @@
         return Operations.MODIFIER_PADDING;
     }
 
-    public static void apply(WireBuffer buffer, float left, float top, float right, float bottom) {
+    public static void apply(
+            @NonNull WireBuffer buffer, float left, float top, float right, float bottom) {
         buffer.start(Operations.MODIFIER_PADDING);
         buffer.writeFloat(left);
         buffer.writeFloat(top);
@@ -125,7 +132,7 @@
         buffer.writeFloat(bottom);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         float left = buffer.readFloat();
         float top = buffer.readFloat();
         float right = buffer.readFloat();
@@ -133,7 +140,7 @@
         operations.add(new PaddingModifierOperation(left, top, right, bottom));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the Padding Modifier")
                 .field(FLOAT, "left", "")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
index dc95fe7..6218dd5 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/RoundedClipRectModifierOperation.java
@@ -17,7 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 
-import com.android.internal.widget.remotecompose.core.CoreDocument;
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.PaintContext;
@@ -25,7 +26,6 @@
 import com.android.internal.widget.remotecompose.core.WireBuffer;
 import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
 import com.android.internal.widget.remotecompose.core.operations.DrawBase4;
-import com.android.internal.widget.remotecompose.core.operations.layout.Component;
 import com.android.internal.widget.remotecompose.core.operations.layout.DecoratorComponent;
 import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
 
@@ -37,7 +37,7 @@
     public static final int OP_CODE = Operations.MODIFIER_ROUNDED_CLIP_RECT;
     public static final String CLASS_NAME = "RoundedClipRectModifierOperation";
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Maker m = RoundedClipRectModifierOperation::new;
         read(m, buffer, operations);
     }
@@ -46,16 +46,17 @@
         return OP_CODE;
     }
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
 
     @Override
-    protected void write(WireBuffer buffer, float v1, float v2, float v3, float v4) {
+    protected void write(@NonNull WireBuffer buffer, float v1, float v2, float v3, float v4) {
         apply(buffer, v1, v2, v3, v4);
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", id(), "RoundedClipRectModifierOperation")
                 .description("clip with rectangle")
                 .field(
@@ -90,7 +91,7 @@
     }
 
     @Override
-    public void paint(PaintContext context) {
+    public void paint(@NonNull PaintContext context) {
         context.roundedClipRect(mWidth, mHeight, mX1, mY1, mX2, mY2);
     }
 
@@ -101,13 +102,7 @@
     }
 
     @Override
-    public void onClick(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
-        // nothing
-    }
-
-    @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent,
                 "ROUNDED_CLIP_RECT = ["
@@ -135,7 +130,11 @@
      * @param bottomEnd bottomEnd radius
      */
     public static void apply(
-            WireBuffer buffer, float topStart, float topEnd, float bottomStart, float bottomEnd) {
+            @NonNull WireBuffer buffer,
+            float topStart,
+            float topEnd,
+            float bottomStart,
+            float bottomEnd) {
         write(buffer, OP_CODE, topStart, topEnd, bottomStart, bottomEnd);
     }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
new file mode 100644
index 0000000..29ec828
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueFloatChangeActionOperation.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
+
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.layout.ActionOperation;
+import com.android.internal.widget.remotecompose.core.operations.layout.Component;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Apply a value change on an float variable. */
+public class ValueFloatChangeActionOperation implements ActionOperation {
+    private static final int OP_CODE = Operations.VALUE_FLOAT_CHANGE_ACTION;
+
+    int mTargetValueId = -1;
+    float mValue = -1;
+
+    public ValueFloatChangeActionOperation(int id, float value) {
+        mTargetValueId = id;
+        mValue = value;
+    }
+
+    @Override
+    public String toString() {
+        return "ValueFloatChangeActionOperation(" + mTargetValueId + ")";
+    }
+
+    public String serializedName() {
+        return "VALUE_FLOAT_CHANGE";
+    }
+
+    @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, serializedName() + " = " + mTargetValueId + " -> " + mValue);
+    }
+
+    @Override
+    public void apply(RemoteContext context) {}
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {}
+
+    @Override
+    public void runAction(
+            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+        System.out.println("OVERRIDE " + mTargetValueId + " TO " + mValue);
+        context.overrideFloat(mTargetValueId, mValue);
+    }
+
+    public static void apply(WireBuffer buffer, int valueId, float value) {
+        buffer.start(OP_CODE);
+        buffer.writeInt(valueId);
+        buffer.writeFloat(value);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        int valueId = buffer.readInt();
+        float value = buffer.readFloat();
+        operations.add(new ValueFloatChangeActionOperation(valueId, value));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Layout Operations", OP_CODE, "ValueFloatChangeActionOperation")
+                .description(
+                        "ValueIntegerChange action. "
+                                + " This operation represents a value change for the given id")
+                .field(INT, "TARGET_VALUE_ID", "Value ID")
+                .field(FLOAT, "VALUE", "float value to be assigned to the target");
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
index 8876720..d7ce8ac 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerChangeActionOperation.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -41,25 +44,28 @@
         mValue = value;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ValueChangeActionOperation(" + mTargetValueId + ")";
     }
 
+    @NonNull
     public String serializedName() {
         return "VALUE_INTEGER_CHANGE";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, serializedName() + " = " + mTargetValueId + " -> " + mValue);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -68,23 +74,27 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            @NonNull RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y) {
         context.overrideInteger(mTargetValueId, mValue);
     }
 
-    public static void apply(WireBuffer buffer, int valueId, int value) {
+    public static void apply(@NonNull WireBuffer buffer, int valueId, int value) {
         buffer.start(OP_CODE);
         buffer.writeInt(valueId);
         buffer.writeInt(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         int value = buffer.readInt();
         operations.add(new ValueIntegerChangeActionOperation(valueId, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerChangeActionOperation")
                 .description(
                         "ValueIntegerChange action. "
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
index fb5e911..75d13e7 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueIntegerExpressionChangeActionOperation.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -41,17 +44,19 @@
         mValueExpressionId = value;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ValueIntegerExpressionChangeActionOperation(" + mTargetValueId + ")";
     }
 
+    @NonNull
     public String serializedName() {
         return "VALUE_INTEGER_EXPRESSION_CHANGE";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(
                 indent, serializedName() + " = " + mTargetValueId + " -> " + mValueExpressionId);
     }
@@ -59,8 +64,9 @@
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -69,23 +75,27 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            @NonNull RemoteContext context,
+            @NonNull CoreDocument document,
+            Component component,
+            float x,
+            float y) {
         document.evaluateIntExpression(mValueExpressionId, (int) mTargetValueId, context);
     }
 
-    public static void apply(WireBuffer buffer, long valueId, long value) {
+    public static void apply(@NonNull WireBuffer buffer, long valueId, long value) {
         buffer.start(OP_CODE);
         buffer.writeLong(valueId);
         buffer.writeLong(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         long valueId = buffer.readLong();
         long value = buffer.readLong();
         operations.add(new ValueIntegerExpressionChangeActionOperation(valueId, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueIntegerExpressionChangeActionOperation")
                 .description(
                         "ValueIntegerExpressionChange action. "
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
index a64a492..26d7244 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ValueStringChangeActionOperation.java
@@ -17,6 +17,9 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import com.android.internal.widget.remotecompose.core.CoreDocument;
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
@@ -41,6 +44,7 @@
         mValueId = value;
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "ValueChangeActionOperation(" + mTargetValueId + ")";
@@ -50,20 +54,22 @@
         return mTargetValueId;
     }
 
+    @NonNull
     public String serializedName() {
         return "VALUE_CHANGE";
     }
 
     @Override
-    public void serializeToString(int indent, StringSerializer serializer) {
+    public void serializeToString(int indent, @NonNull StringSerializer serializer) {
         serializer.append(indent, serializedName() + " = " + mTargetValueId + " -> " + mValueId);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
-    public String deepToString(String indent) {
+    public String deepToString(@Nullable String indent) {
         return (indent != null ? indent : "") + toString();
     }
 
@@ -72,23 +78,27 @@
 
     @Override
     public void runAction(
-            RemoteContext context, CoreDocument document, Component component, float x, float y) {
+            @NonNull RemoteContext context,
+            CoreDocument document,
+            Component component,
+            float x,
+            float y) {
         context.overrideText(mTargetValueId, mValueId);
     }
 
-    public static void apply(WireBuffer buffer, int valueId, int value) {
+    public static void apply(@NonNull WireBuffer buffer, int valueId, int value) {
         buffer.start(OP_CODE);
         buffer.writeInt(valueId);
         buffer.writeInt(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int valueId = buffer.readInt();
         int value = buffer.readInt();
         operations.add(new ValueStringChangeActionOperation(valueId, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Layout Operations", OP_CODE, "ValueStringChangeActionOperation")
                 .description(
                         "ValueStrin gChange action. "
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
index 62403b3..e2f899c 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/WidthModifierOperation.java
@@ -18,6 +18,8 @@
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.WireBuffer;
@@ -30,6 +32,7 @@
     private static final int OP_CODE = Operations.MODIFIER_WIDTH;
     public static final String CLASS_NAME = "WidthModifierOperation";
 
+    @NonNull
     public static String name() {
         return CLASS_NAME;
     }
@@ -38,13 +41,13 @@
         return OP_CODE;
     }
 
-    public static void apply(WireBuffer buffer, int type, float value) {
+    public static void apply(@NonNull WireBuffer buffer, int type, float value) {
         buffer.start(OP_CODE);
         buffer.writeInt(type);
         buffer.writeFloat(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         Type type = Type.fromInt(buffer.readInt());
         float value = buffer.readFloat();
         Operation op = new WidthModifierOperation(type, value);
@@ -56,7 +59,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mType.ordinal(), mValue);
     }
 
@@ -68,17 +71,19 @@
         super(value);
     }
 
+    @NonNull
     @Override
     public String toString() {
-        return "Width(" + mValue + ")";
+        return "Width(" + mType + ", " + mValue + ")";
     }
 
+    @NonNull
     @Override
     public String serializedName() {
         return "WIDTH";
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
                 .description("define the animation")
                 .field(INT, "type", "")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
new file mode 100644
index 0000000..aa20e03
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/modifiers/ZIndexModifierOperation.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.layout.modifiers;
+
+import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.FLOAT;
+
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.PaintContext;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+import com.android.internal.widget.remotecompose.core.documentation.DocumentationBuilder;
+import com.android.internal.widget.remotecompose.core.operations.Utils;
+import com.android.internal.widget.remotecompose.core.operations.utilities.StringSerializer;
+
+import java.util.List;
+
+/** Represents a ZIndex modifier, allowing to change the z-index of a component. */
+public class ZIndexModifierOperation extends DecoratorModifierOperation {
+    private static final int OP_CODE = Operations.MODIFIER_ZINDEX;
+    public static final String CLASS_NAME = "ZIndexModifierOperation";
+    float mValue;
+    float mCurrentValue;
+
+    public ZIndexModifierOperation(float value) {
+        this.mValue = value;
+    }
+
+    public float getValue() {
+        return mCurrentValue;
+    }
+
+    public void setmValue(float value) {
+        this.mValue = value;
+    }
+
+    @Override
+    public void write(WireBuffer buffer) {
+        apply(buffer, mValue);
+    }
+
+    // @Override
+    public void serializeToString(int indent, StringSerializer serializer) {
+        serializer.append(indent, "ZINDEX = [" + mValue + "]");
+    }
+
+    @Override
+    public String deepToString(String indent) {
+        return (indent != null ? indent : "") + toString();
+    }
+
+    @Override
+    public void paint(PaintContext context) {
+        mCurrentValue = mValue;
+        if (Utils.isVariable(mValue)) {
+            mCurrentValue =
+                    context.getContext().mRemoteComposeState.getFloat(Utils.idFromNan(mValue));
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ZIndexModifierOperation(" + mValue + ")";
+    }
+
+    public static String name() {
+        return CLASS_NAME;
+    }
+
+    public static int id() {
+        return OP_CODE;
+    }
+
+    public static void apply(WireBuffer buffer, float value) {
+        buffer.start(OP_CODE);
+        buffer.writeFloat(value);
+    }
+
+    public static void read(WireBuffer buffer, List<Operation> operations) {
+        float value = buffer.readFloat();
+        operations.add(new ZIndexModifierOperation(value));
+    }
+
+    public static void documentation(DocumentationBuilder doc) {
+        doc.operation("Modifier Operations", OP_CODE, CLASS_NAME)
+                .description("define the Z-Index Modifier")
+                .field(FLOAT, "value", "");
+    }
+
+    @Override
+    public void layout(RemoteContext context, float width, float height) {}
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
index 4849b12..d8e49b0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/layout/utils/DebugLog.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.layout.utils;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 import java.util.ArrayList;
 
 /** Internal utility debug class */
@@ -23,12 +26,12 @@
     public static final boolean DEBUG_LAYOUT_ON = false;
 
     public static class Node {
-        public Node parent;
+        @Nullable public Node parent;
         public String name;
         public String endString;
-        public ArrayList<Node> list = new ArrayList<>();
+        @NonNull public ArrayList<Node> list = new ArrayList<>();
 
-        public Node(Node parent, String name) {
+        public Node(@Nullable Node parent, String name) {
             this.parent = parent;
             this.name = name;
             this.endString = name + " DONE";
@@ -48,21 +51,21 @@
         }
     }
 
-    public static Node node = new Node(null, "Root");
-    public static Node currentNode = node;
+    @NonNull public static Node node = new Node(null, "Root");
+    @NonNull public static Node currentNode = node;
 
     public static void clear() {
         node = new Node(null, "Root");
         currentNode = node;
     }
 
-    public static void s(StringValueSupplier valueSupplier) {
+    public static void s(@NonNull StringValueSupplier valueSupplier) {
         if (DEBUG_LAYOUT_ON) {
             currentNode = new Node(currentNode, valueSupplier.getString());
         }
     }
 
-    public static void log(StringValueSupplier valueSupplier) {
+    public static void log(@NonNull StringValueSupplier valueSupplier) {
         if (DEBUG_LAYOUT_ON) {
             new LogNode(currentNode, valueSupplier.getString());
         }
@@ -78,7 +81,7 @@
         }
     }
 
-    public static void e(StringValueSupplier valueSupplier) {
+    public static void e(@NonNull StringValueSupplier valueSupplier) {
         if (DEBUG_LAYOUT_ON) {
             currentNode.endString = valueSupplier.getString();
             if (currentNode.parent != null) {
@@ -89,7 +92,7 @@
         }
     }
 
-    public static void printNode(int indent, Node node, StringBuilder builder) {
+    public static void printNode(int indent, @NonNull Node node, @NonNull StringBuilder builder) {
         if (DEBUG_LAYOUT_ON) {
             StringBuilder indentationBuilder = new StringBuilder();
             for (int i = 0; i < indent; i++) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java
index 9a3cd54..a808cf0 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/paint/Painter.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.paint;
 
+import android.annotation.NonNull;
+
 /** Provides a Builder pattern for a PaintBundle */
 class Painter {
     PaintBundle mPaint;
@@ -24,16 +26,19 @@
         return mPaint;
     }
 
+    @NonNull
     public Painter setAntiAlias(boolean aa) {
         mPaint.setAntiAlias(aa);
         return this;
     }
 
+    @NonNull
     public Painter setColor(int color) {
         mPaint.setColor(color);
         return this;
     }
 
+    @NonNull
     public Painter setColorId(int colorId) {
         mPaint.setColorId(colorId);
         return this;
@@ -44,6 +49,7 @@
      *
      * @param join set the paint's Join, used whenever the paint's style is Stroke or StrokeAndFill.
      */
+    @NonNull
     public Painter setStrokeJoin(int join) {
         mPaint.setStrokeJoin(join);
         return this;
@@ -56,6 +62,7 @@
      * @param width set the paint's stroke width, used whenever the paint's style is Stroke or
      *     StrokeAndFill.
      */
+    @NonNull
     public Painter setStrokeWidth(float width) {
         mPaint.setStrokeWidth(width);
         return this;
@@ -67,6 +74,7 @@
      *
      * @param style The new style to set in the paint
      */
+    @NonNull
     public Painter setStyle(int style) {
         mPaint.setStyle(style);
         return this;
@@ -78,6 +86,7 @@
      * @param cap set the paint's line cap style, used whenever the paint's style is Stroke or
      *     StrokeAndFill.
      */
+    @NonNull
     public Painter setStrokeCap(int cap) {
         mPaint.setStrokeCap(cap);
         return this;
@@ -90,6 +99,7 @@
      * @param miter set the miter limit on the paint, used whenever the paint's style is Stroke or
      *     StrokeAndFill.
      */
+    @NonNull
     public Painter setStrokeMiter(float miter) {
         mPaint.setStrokeMiter(miter);
         return this;
@@ -101,6 +111,7 @@
      *
      * @param alpha set the alpha component [0..1.0] of the paint's color.
      */
+    @NonNull
     public Painter setAlpha(float alpha) {
         mPaint.setAlpha((alpha > 2) ? alpha / 255f : alpha);
         return this;
@@ -112,6 +123,7 @@
      * @param color The ARGB source color used with the specified Porter-Duff mode
      * @param mode The porter-duff mode that is applied
      */
+    @NonNull
     public Painter setPorterDuffColorFilter(int color, int mode) {
         mPaint.setColorFilter(color, mode);
         return this;
@@ -130,6 +142,7 @@
      *     line.
      * @param tileMode The Shader tiling mode
      */
+    @NonNull
     public Painter setLinearGradient(
             float startX,
             float startY,
@@ -155,6 +168,7 @@
      *     circle.
      * @param tileMode The Shader tiling mode
      */
+    @NonNull
     public Painter setRadialGradient(
             float centerX,
             float centerY,
@@ -178,6 +192,7 @@
      *     may produce unexpected results. If positions is NULL, then the colors are automatically
      *     spaced evenly.
      */
+    @NonNull
     public Painter setSweepGradient(float centerX, float centerY, int[] colors, float[] positions) {
         mPaint.setSweepGradient(colors, 0, positions, centerX, centerY);
         return this;
@@ -188,6 +203,7 @@
      *
      * @param size set the paint's text size in pixel units.
      */
+    @NonNull
     public Painter setTextSize(float size) {
         mPaint.setTextSize(size);
         return this;
@@ -215,16 +231,19 @@
      * @param weight The desired weight to be drawn.
      * @param italic {@code true} if italic style is desired to be drawn. Otherwise, {@code false}
      */
+    @NonNull
     public Painter setTypeface(int fontType, int weight, boolean italic) {
         mPaint.setTextStyle(fontType, weight, italic);
         return this;
     }
 
+    @NonNull
     public Painter setFilterBitmap(boolean filter) {
         mPaint.setFilterBitmap(filter);
         return this;
     }
 
+    @NonNull
     public Painter setShader(int id) {
         mPaint.setShader(id);
         return this;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
index 1d673c4..b25f4cd 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/AnimatedFloatExpression.java
@@ -15,11 +15,12 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
-/**
- * high performance floating point expression evaluator used in animation
- */
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/** high performance floating point expression evaluator used in animation */
 public class AnimatedFloatExpression {
-    static IntMap<String> sNames = new IntMap<>();
+    @NonNull static IntMap<String> sNames = new IntMap<>();
     public static final int OFFSET = 0x310_000;
     public static final float ADD = asNan(OFFSET + 1);
     public static final float SUB = asNan(OFFSET + 2);
@@ -74,7 +75,7 @@
     private static final float FP_TO_DEG = 0.017453292f; // 180/PI
 
     float[] mStack;
-    float[] mLocalStack = new float[128];
+    @NonNull float[] mLocalStack = new float[128];
     float[] mVar;
     CollectionsAccess mCollectionsAccess;
 
@@ -201,7 +202,7 @@
      * @param var
      * @return
      */
-    public float eval(float[] exp, int len, float... var) {
+    public float eval(@NonNull float[] exp, int len, float... var) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
         mStack = mLocalStack;
         mVar = var;
@@ -224,7 +225,7 @@
      * @param var
      * @return
      */
-    public float evalDB(float[] exp, float... var) {
+    public float evalDB(@NonNull float[] exp, float... var) {
         mStack = exp;
         mVar = var;
         int sp = -1;
@@ -240,195 +241,281 @@
         return mStack[sp];
     }
 
-    Op[] mOps = {
+    @NonNull Op[] mOps;
+
+    {
+        Op mADD =
+                (sp) -> { // ADD
+                    mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
+                    return sp - 1;
+                };
+        Op mSUB =
+                (sp) -> { // SUB
+                    mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
+                    return sp - 1;
+                };
+        Op mMUL =
+                (sp) -> { // MUL
+                    mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
+                    return sp - 1;
+                };
+        Op mDIV =
+                (sp) -> { // DIV
+                    mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
+                    return sp - 1;
+                };
+        Op mMOD =
+                (sp) -> { // MOD
+                    mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
+                    return sp - 1;
+                };
+        Op mMIN =
+                (sp) -> { // MIN
+                    mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mMAX =
+                (sp) -> { // MAX
+                    mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mPOW =
+                (sp) -> { // POW
+                    mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mSQRT =
+                (sp) -> { // SQRT
+                    mStack[sp] = (float) Math.sqrt(mStack[sp]);
+                    return sp;
+                };
+        Op mABS =
+                (sp) -> { // ABS
+                    mStack[sp] = (float) Math.abs(mStack[sp]);
+                    return sp;
+                };
+        Op mSIGN =
+                (sp) -> { // SIGN
+                    mStack[sp] = (float) Math.signum(mStack[sp]);
+                    return sp;
+                };
+        Op mCOPY_SIGN =
+                (sp) -> { // copySign
+                    mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mEXP =
+                (sp) -> { // EXP
+                    mStack[sp] = (float) Math.exp(mStack[sp]);
+                    return sp;
+                };
+        Op mFLOOR =
+                (sp) -> { // FLOOR
+                    mStack[sp] = (float) Math.floor(mStack[sp]);
+                    return sp;
+                };
+        Op mLOG =
+                (sp) -> { // LOG
+                    mStack[sp] = (float) Math.log10(mStack[sp]);
+                    return sp;
+                };
+        Op mLN =
+                (sp) -> { // LN
+                    mStack[sp] = (float) Math.log(mStack[sp]);
+                    return sp;
+                };
+        Op mROUND =
+                (sp) -> { // ROUND
+                    mStack[sp] = (float) Math.round(mStack[sp]);
+                    return sp;
+                };
+        Op mSIN =
+                (sp) -> { // SIN
+                    mStack[sp] = (float) Math.sin(mStack[sp]);
+                    return sp;
+                };
+        Op mCOS =
+                (sp) -> { // COS
+                    mStack[sp] = (float) Math.cos(mStack[sp]);
+                    return sp;
+                };
+        Op mTAN =
+                (sp) -> { // TAN
+                    mStack[sp] = (float) Math.tan(mStack[sp]);
+                    return sp;
+                };
+        Op mASIN =
+                (sp) -> { // ASIN
+                    mStack[sp] = (float) Math.asin(mStack[sp]);
+                    return sp;
+                };
+        Op mACOS =
+                (sp) -> { // ACOS
+                    mStack[sp] = (float) Math.acos(mStack[sp]);
+                    return sp;
+                };
+        Op mATAN =
+                (sp) -> { // ATAN
+                    mStack[sp] = (float) Math.atan(mStack[sp]);
+                    return sp;
+                };
+        Op mATAN2 =
+                (sp) -> { // ATAN2
+                    mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mMAD =
+                (sp) -> { // MAD
+                    mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
+                    return sp - 2;
+                };
+        Op mTERNARY_CONDITIONAL =
+                (sp) -> { // TERNARY_CONDITIONAL
+                    mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
+                    return sp - 2;
+                };
+        Op mCLAMP =
+                (sp) -> { // CLAMP
+                    mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
+                    return sp - 2;
+                };
+        Op mCBRT =
+                (sp) -> { // CBRT
+                    mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.);
+                    return sp;
+                };
+        Op mDEG =
+                (sp) -> { // DEG
+                    mStack[sp] = mStack[sp] * FP_TO_RAD;
+                    return sp;
+                };
+        Op mRAD =
+                (sp) -> { // RAD
+                    mStack[sp] = mStack[sp] * FP_TO_DEG;
+                    return sp;
+                };
+        Op mCEIL =
+                (sp) -> { // CEIL
+                    mStack[sp] = (float) Math.ceil(mStack[sp]);
+                    return sp;
+                };
+        Op mA_DEREF =
+                (sp) -> { // A_DEREF
+                    int id = fromNaN(mStack[sp]);
+                    mStack[sp - 1] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp - 1]);
+                    return sp - 1;
+                };
+        Op mA_MAX =
+                (sp) -> { // A_MAX
+                    int id = fromNaN(mStack[sp]);
+                    float[] array = mCollectionsAccess.getFloats(id);
+                    float max = array[0];
+                    for (int i = 1; i < array.length; i++) {
+                        max = Math.max(max, array[i]);
+                    }
+                    mStack[sp] = max;
+                    return sp;
+                };
+        Op mA_MIN =
+                (sp) -> { // A_MIN
+                    int id = fromNaN(mStack[sp]);
+                    float[] array = mCollectionsAccess.getFloats(id);
+                    float max = array[0];
+                    for (int i = 1; i < array.length; i++) {
+                        max = Math.max(max, array[i]);
+                    }
+                    mStack[sp] = max;
+                    return sp;
+                };
+        Op mA_SUM =
+                (sp) -> { // A_SUM
+                    int id = fromNaN(mStack[sp]);
+                    float[] array = mCollectionsAccess.getFloats(id);
+                    float sum = 0;
+                    for (int i = 0; i < array.length; i++) {
+                        sum += array[i];
+                    }
+                    mStack[sp] = sum;
+                    return sp;
+                };
+        Op mA_AVG =
+                (sp) -> { // A_AVG
+                    int id = fromNaN(mStack[sp]);
+                    float[] array = mCollectionsAccess.getFloats(id);
+                    float sum = 0;
+                    for (int i = 0; i < array.length; i++) {
+                        sum += array[i];
+                    }
+                    mStack[sp] = sum / array.length;
+                    return sp;
+                };
+        Op mA_LEN =
+                (sp) -> { // A_LEN
+                    int id = fromNaN(mStack[sp]);
+                    mStack[sp] = mCollectionsAccess.getListLength(id);
+                    return sp;
+                };
+        Op mFIRST_VAR =
+                (sp) -> { // FIRST_VAR
+                    mStack[sp] = mVar[0];
+                    return sp;
+                };
+        Op mSECOND_VAR =
+                (sp) -> { // SECOND_VAR
+                    mStack[sp] = mVar[1];
+                    return sp;
+                };
+        Op mTHIRD_VAR =
+                (sp) -> { // THIRD_VAR
+                    mStack[sp] = mVar[2];
+                    return sp;
+                };
+
+        Op[] ops = {
             null,
-            (sp) -> { // ADD
-                mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // SUB
-                mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MUL
-                mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // DIV
-                mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MOD
-                mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MIN
-                mStack[sp - 1] = (float) Math.min(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // MAX
-                mStack[sp - 1] = (float) Math.max(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // POW
-                mStack[sp - 1] = (float) Math.pow(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // SQRT
-                mStack[sp] = (float) Math.sqrt(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ABS
-                mStack[sp] = (float) Math.abs(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // SIGN
-                mStack[sp] = (float) Math.signum(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // copySign
-                mStack[sp - 1] = (float) Math.copySign(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // EXP
-                mStack[sp] = (float) Math.exp(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // FLOOR
-                mStack[sp] = (float) Math.floor(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // LOG
-                mStack[sp] = (float) Math.log10(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // LN
-                mStack[sp] = (float) Math.log(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ROUND
-                mStack[sp] = (float) Math.round(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // SIN
-                mStack[sp] = (float) Math.sin(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // COS
-                mStack[sp] = (float) Math.cos(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // TAN
-                mStack[sp] = (float) Math.tan(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ASIN
-                mStack[sp] = (float) Math.asin(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ACOS
-                mStack[sp] = (float) Math.acos(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ATAN
-                mStack[sp] = (float) Math.atan(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // ATAN2
-                mStack[sp - 1] = (float) Math.atan2(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // MAD
-                mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
-                return sp - 2;
-            },
-            (sp) -> { // Ternary conditional
-                mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
-                return sp - 2;
-            },
-            (sp) -> { // CLAMP(min,max, val)
-                mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
-                return sp - 2;
-            },
-            (sp) -> { // CBRT cuberoot
-                mStack[sp] = (float) Math.pow(mStack[sp], 1 / 3.);
-                return sp;
-            },
-            (sp) -> { // DEG
-                mStack[sp] = mStack[sp] * FP_TO_RAD;
-                return sp;
-            },
-            (sp) -> { // RAD
-                mStack[sp] = mStack[sp] * FP_TO_DEG;
-                return sp;
-            },
-            (sp) -> { // CEIL
-                mStack[sp] = (float) Math.ceil(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // A_DEREF
-                int id = fromNaN(mStack[sp]);
-                mStack[sp] = mCollectionsAccess.getFloatValue(id, (int) mStack[sp - 1]);
-                return sp - 1;
-            },
-            (sp) -> { // A_MAX
-                int id = fromNaN(mStack[sp]);
-                float[] array = mCollectionsAccess.getFloats(id);
-                float max = array[0];
-                for (int i = 1; i < array.length; i++) {
-                    max = Math.max(max, array[i]);
-                }
-                mStack[sp] = max;
-                return sp;
-            },
-            (sp) -> { // A_MIN
-                int id = fromNaN(mStack[sp]);
-                float[] array = mCollectionsAccess.getFloats(id);
-                float max = array[0];
-                for (int i = 1; i < array.length; i++) {
-                    max = Math.max(max, array[i]);
-                }
-                mStack[sp] = max;
-                return sp;
-            },
-            (sp) -> { // A_SUM
-                int id = fromNaN(mStack[sp]);
-                float[] array = mCollectionsAccess.getFloats(id);
-                float sum = 0;
-                for (int i = 0; i < array.length; i++) {
-                    sum += array[i];
-                }
-                mStack[sp] = sum;
-                return sp;
-            },
-            (sp) -> { // A_AVG
-                int id = fromNaN(mStack[sp]);
-                float[] array = mCollectionsAccess.getFloats(id);
-                float sum = 0;
-                for (int i = 0; i < array.length; i++) {
-                    sum += array[i];
-                }
-                mStack[sp] = sum / array.length;
-                return sp;
-            },
-            (sp) -> { // A_LEN
-                int id = fromNaN(mStack[sp]);
-                mStack[sp] = mCollectionsAccess.getListLength(id);
-                return sp;
-            },
-            (sp) -> { // first var =
-                mStack[sp] = mVar[0];
-                return sp;
-            },
-            (sp) -> { // second var y?
-                mStack[sp] = mVar[1];
-                return sp;
-            },
-            (sp) -> { // 3rd var z?
-                mStack[sp] = mVar[2];
-                return sp;
-            },
-    };
+            mADD,
+            mSUB,
+            mMUL,
+            mDIV,
+            mMOD,
+            mMIN,
+            mMAX,
+            mPOW,
+            mSQRT,
+            mABS,
+            mSIGN,
+            mCOPY_SIGN,
+            mEXP,
+            mFLOOR,
+            mLOG,
+            mLN,
+            mROUND,
+            mSIN,
+            mCOS,
+            mTAN,
+            mASIN,
+            mACOS,
+            mATAN,
+            mATAN2,
+            mMAD,
+            mTERNARY_CONDITIONAL,
+            mCLAMP,
+            mCBRT,
+            mDEG,
+            mRAD,
+            mCEIL,
+            mA_DEREF,
+            mA_MAX,
+            mA_MIN,
+            mA_SUM,
+            mA_AVG,
+            mA_LEN,
+            mFIRST_VAR,
+            mSECOND_VAR,
+            mTHIRD_VAR,
+        };
+        mOps = ops;
+    }
 
     static {
         int k = 0;
@@ -483,6 +570,7 @@
      * @param f
      * @return
      */
+    @Nullable
     public static String toMathName(float f) {
         int id = fromNaN(f) - OFFSET;
         return sNames.get(id);
@@ -495,7 +583,8 @@
      * @param labels
      * @return
      */
-    public static String toString(float[] exp, String[] labels) {
+    @NonNull
+    public static String toString(@NonNull float[] exp, @Nullable String[] labels) {
         StringBuilder s = new StringBuilder();
         for (int i = 0; i < exp.length; i++) {
             float v = exp[i];
@@ -525,7 +614,7 @@
         return s.toString();
     }
 
-    static String toString(float[] exp, int sp) {
+    static String toString(@NonNull float[] exp, int sp) {
         //        String[] str = new String[exp.length];
         if (Float.isNaN(exp[sp])) {
             int id = fromNaN(exp[sp]) - OFFSET;
@@ -575,42 +664,42 @@
     }
 
     static final int[] NO_OF_OPS = {
-            -1, // no op
-            2,
-            2,
-            2,
-            2,
-            2, // + - * / %
-            2,
-            2,
-            2, // min max, power
-            1,
-            1,
-            1,
-            1,
-            1,
-            1,
-            1,
-            1, // sqrt,abs,CopySign,exp,floor,log,ln
-            1,
-            1,
-            1,
-            1,
-            1,
-            1,
-            1,
-            2, // round,sin,cos,tan,asin,acos,atan,atan2
-            3,
-            3,
-            3,
-            1,
-            1,
-            1,
-            1,
-            0,
-            0,
-            0 // mad, ?:,
-            // a[0],a[1],a[2]
+        -1, // no op
+        2,
+        2,
+        2,
+        2,
+        2, // + - * / %
+        2,
+        2,
+        2, // min max, power
+        1,
+        1,
+        1,
+        1,
+        1,
+        1,
+        1,
+        1, // sqrt,abs,CopySign,exp,floor,log,ln
+        1,
+        1,
+        1,
+        1,
+        1,
+        1,
+        1,
+        2, // round,sin,cos,tan,asin,acos,atan,atan2
+        3,
+        3,
+        3,
+        1,
+        1,
+        1,
+        1,
+        0,
+        0,
+        0 // mad, ?:,
+        // a[0],a[1],a[2]
     };
 
     /**
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
index 00c87c1..e74b335 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/ImageScaling.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.NonNull;
+
 /** Implement the scaling logic for Compose Image or ImageView */
 public class ImageScaling {
 
@@ -97,6 +99,7 @@
         adjustDrawToType();
     }
 
+    @NonNull
     static String str(float v) {
         String s = "  " + (int) v;
         return s.substring(s.length() - 3);
@@ -210,6 +213,7 @@
         }
     }
 
+    @NonNull
     public static String typeToString(int type) {
         String[] typeString = {
             "none",
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
index 84e7843..749c0fe 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntMap.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.Nullable;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -42,6 +44,7 @@
         mSize = 0;
     }
 
+    @Nullable
     public T put(int key, T value) {
         if (key == NOT_PRESENT) throw new IllegalArgumentException("Key cannot be NOT_PRESENT");
         if (mSize > mKeys.length * LOAD_FACTOR) {
@@ -50,6 +53,7 @@
         return insert(key, value);
     }
 
+    @Nullable
     public T get(int key) {
         int index = findKey(key);
         if (index == -1) {
@@ -61,6 +65,7 @@
         return mSize;
     }
 
+    @Nullable
     private T insert(int key, T value) {
         int index = hash(key) % mKeys.length;
         while (mKeys[index] != NOT_PRESENT && mKeys[index] != key) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
index baa144d..8905431 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/IntegerExpressionEvaluator.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /**
  * High performance Integer expression evaluator
  *
@@ -22,7 +25,7 @@
  * 0)
  */
 public class IntegerExpressionEvaluator {
-    static IntMap<String> sNames = new IntMap<>();
+    @NonNull static IntMap<String> sNames = new IntMap<>();
     public static final int OFFSET = 0x10000;
     // add, sub, mul,div,mod,min,max, shl, shr, ushr, OR, AND , XOR, COPY_SIGN
     public static final int I_ADD = OFFSET + 1;
@@ -57,7 +60,7 @@
     public static final int I_VAR2 = OFFSET + 25;
 
     int[] mStack;
-    int[] mLocalStack = new int[128];
+    @NonNull int[] mLocalStack = new int[128];
     int[] mVar;
 
     interface Op {
@@ -68,8 +71,8 @@
      * Evaluate an integer expression
      *
      * @param mask bits that are operators
-     * @param exp  rpn sequence of values and operators
-     * @param var  variables if the expression is a function
+     * @param exp rpn sequence of values and operators
+     * @param var variables if the expression is a function
      * @return return the results of evaluating the expression
      */
     public int eval(int mask, int[] exp, int... var) {
@@ -91,12 +94,12 @@
      * Evaluate a integer expression
      *
      * @param mask bits that are operators
-     * @param exp  rpn sequence of values and operators
-     * @param len  the number of values in the expression
-     * @param var  variables if the expression is a function
+     * @param exp rpn sequence of values and operators
+     * @param len the number of values in the expression
+     * @param var variables if the expression is a function
      * @return return the results of evaluating the expression
      */
-    public int eval(int mask, int[] exp, int len, int... var) {
+    public int eval(int mask, @NonNull int[] exp, int len, int... var) {
         System.arraycopy(exp, 0, mLocalStack, 0, len);
         mStack = mLocalStack;
         mVar = var;
@@ -116,11 +119,11 @@
      * Evaluate a int expression
      *
      * @param opMask bits that are operators
-     * @param exp    rpn sequence of values and operators
-     * @param var    variables if the expression is a function
+     * @param exp rpn sequence of values and operators
+     * @param var variables if the expression is a function
      * @return return the results of evaluating the expression
      */
-    public int evalDB(int opMask, int[] exp, int... var) {
+    public int evalDB(int opMask, @NonNull int[] exp, int... var) {
         mStack = exp;
         mVar = var;
         int sp = -1;
@@ -137,113 +140,172 @@
         return mStack[sp];
     }
 
-    Op[] mOps = {
+    @NonNull Op[] mOps;
+
+    {
+        Op mADD =
+                (sp) -> { // ADD
+                    mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
+                    return sp - 1;
+                };
+        Op mSUB =
+                (sp) -> { // SUB
+                    mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
+                    return sp - 1;
+                };
+        Op mMUL =
+                (sp) -> { // MUL
+                    mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
+                    return sp - 1;
+                };
+        Op mDIV =
+                (sp) -> { // DIV
+                    mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
+                    return sp - 1;
+                };
+        Op mMOD =
+                (sp) -> { // MOD
+                    mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
+                    return sp - 1;
+                };
+        Op mSHL =
+                (sp) -> { // SHL
+                    mStack[sp - 1] = mStack[sp - 1] << mStack[sp];
+                    return sp - 1;
+                };
+        Op mSHR =
+                (sp) -> { // SHR
+                    mStack[sp - 1] = mStack[sp - 1] >> mStack[sp];
+                    return sp - 1;
+                };
+        Op mUSHR =
+                (sp) -> { // USHR
+                    mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp];
+                    return sp - 1;
+                };
+        Op mOR =
+                (sp) -> { // OR
+                    mStack[sp - 1] = mStack[sp - 1] | mStack[sp];
+                    return sp - 1;
+                };
+        Op mAND =
+                (sp) -> { // AND
+                    mStack[sp - 1] = mStack[sp - 1] & mStack[sp];
+                    return sp - 1;
+                };
+        Op mXOR =
+                (sp) -> { // XOR
+                    mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp];
+                    return sp - 1;
+                };
+        Op mCOPY_SIGN =
+                (sp) -> { // COPY_SIGN
+                    mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) - (mStack[sp] >> 31);
+                    return sp - 1;
+                };
+        Op mMIN =
+                (sp) -> { // MIN
+                    mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mMAX =
+                (sp) -> { // MAX
+                    mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]);
+                    return sp - 1;
+                };
+        Op mNEG =
+                (sp) -> { // NEG
+                    mStack[sp] = -mStack[sp];
+                    return sp;
+                };
+        Op mABS =
+                (sp) -> { // ABS
+                    mStack[sp] = Math.abs(mStack[sp]);
+                    return sp;
+                };
+        Op mINCR =
+                (sp) -> { // INCR
+                    mStack[sp] = mStack[sp] + 1;
+                    return sp;
+                };
+        Op mDECR =
+                (sp) -> { // DECR
+                    mStack[sp] = mStack[sp] - 1;
+                    return sp;
+                };
+        Op mNOT =
+                (sp) -> { // NOT
+                    mStack[sp] = ~mStack[sp];
+                    return sp;
+                };
+        Op mSIGN =
+                (sp) -> { // SIGN
+                    mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31);
+                    return sp;
+                };
+        Op mCLAMP =
+                (sp) -> { // CLAMP
+                    mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
+                    return sp - 2;
+                };
+        Op mTERNARY_CONDITIONAL =
+                (sp) -> { // TERNARY_CONDITIONAL
+                    mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
+                    return sp - 2;
+                };
+        Op mMAD =
+                (sp) -> { // MAD
+                    mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
+                    return sp - 2;
+                };
+        Op mFIRST_VAR =
+                (sp) -> { // FIRST_VAR
+                    mStack[sp] = mVar[0];
+                    return sp;
+                };
+        Op mSECOND_VAR =
+                (sp) -> { // SECOND_VAR
+                    mStack[sp] = mVar[1];
+                    return sp;
+                };
+        Op mTHIRD_VAR =
+                (sp) -> { // THIRD_VAR
+                    mStack[sp] = mVar[2];
+                    return sp;
+                };
+
+        Op[] ops = {
             null,
-            (sp) -> { // ADD
-                mStack[sp - 1] = mStack[sp - 1] + mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // SUB
-                mStack[sp - 1] = mStack[sp - 1] - mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MUL
-                mStack[sp - 1] = mStack[sp - 1] * mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // DIV
-                mStack[sp - 1] = mStack[sp - 1] / mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // MOD
-                mStack[sp - 1] = mStack[sp - 1] % mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // SHL shift left
-                mStack[sp - 1] = mStack[sp - 1] << mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // SHR shift right
-                mStack[sp - 1] = mStack[sp - 1] >> mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // USHR unsigned shift right
-                mStack[sp - 1] = mStack[sp - 1] >>> mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // OR operator
-                mStack[sp - 1] = mStack[sp - 1] | mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // AND operator
-                mStack[sp - 1] = mStack[sp - 1] & mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // XOR xor operator
-                mStack[sp - 1] = mStack[sp - 1] ^ mStack[sp];
-                return sp - 1;
-            },
-            (sp) -> { // COPY_SIGN copy the sing of (using bit magic)
-                mStack[sp - 1] = (mStack[sp - 1] ^ (mStack[sp] >> 31)) - (mStack[sp] >> 31);
-                return sp - 1;
-            },
-            (sp) -> { // MIN
-                mStack[sp - 1] = Math.min(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // MAX
-                mStack[sp - 1] = Math.max(mStack[sp - 1], mStack[sp]);
-                return sp - 1;
-            },
-            (sp) -> { // NEG
-                mStack[sp] = -mStack[sp];
-                return sp;
-            },
-            (sp) -> { // ABS
-                mStack[sp] = Math.abs(mStack[sp]);
-                return sp;
-            },
-            (sp) -> { // INCR increment
-                mStack[sp] = mStack[sp] + 1;
-                return sp;
-            },
-            (sp) -> { // DECR decrement
-                mStack[sp] = mStack[sp] - 1;
-                return sp;
-            },
-            (sp) -> { // NOT Bit invert
-                mStack[sp] = ~mStack[sp];
-                return sp;
-            },
-            (sp) -> { // SIGN x<0 = -1,x==0 =  0 , x>0 = 1
-                mStack[sp] = (mStack[sp] >> 31) | (-mStack[sp] >>> 31);
-                return sp;
-            },
-            (sp) -> { // CLAMP(min,max, val)
-                mStack[sp - 2] = Math.min(Math.max(mStack[sp - 2], mStack[sp]), mStack[sp - 1]);
-                return sp - 2;
-            },
-            (sp) -> { // Ternary conditional
-                mStack[sp - 2] = (mStack[sp] > 0) ? mStack[sp - 1] : mStack[sp - 2];
-                return sp - 2;
-            },
-            (sp) -> { // MAD
-                mStack[sp - 2] = mStack[sp] + mStack[sp - 1] * mStack[sp - 2];
-                return sp - 2;
-            },
-            (sp) -> { // first var =
-                mStack[sp] = mVar[0];
-                return sp;
-            },
-            (sp) -> { // second var y?
-                mStack[sp] = mVar[1];
-                return sp;
-            },
-            (sp) -> { // 3rd var z?
-                mStack[sp] = mVar[2];
-                return sp;
-            },
-    };
+            mADD,
+            mSUB,
+            mMUL,
+            mDIV,
+            mMOD,
+            mSHL,
+            mSHR,
+            mUSHR,
+            mOR,
+            mAND,
+            mXOR,
+            mCOPY_SIGN,
+            mMIN,
+            mMAX,
+            mNEG,
+            mABS,
+            mINCR,
+            mDECR,
+            mNOT,
+            mSIGN,
+            mCLAMP,
+            mTERNARY_CONDITIONAL,
+            mMAD,
+            mFIRST_VAR,
+            mSECOND_VAR,
+            mTHIRD_VAR,
+        };
+
+        mOps = ops;
+    }
 
     static {
         int k = 0;
@@ -283,6 +345,7 @@
      * @param f the numerical value of the function + offset
      * @return the math name of the function
      */
+    @Nullable
     public static String toMathName(int f) {
         int id = f - OFFSET;
         return sNames.get(id);
@@ -292,11 +355,12 @@
      * Convert an expression encoded as an array of ints int to a string
      *
      * @param opMask bits that are operators
-     * @param exp    rpn sequence of values and operators
+     * @param exp rpn sequence of values and operators
      * @param labels String that represent the variable names
      * @return
      */
-    public static String toString(int opMask, int[] exp, String[] labels) {
+    @NonNull
+    public static String toString(int opMask, @NonNull int[] exp, String[] labels) {
         StringBuilder s = new StringBuilder();
         for (int i = 0; i < exp.length; i++) {
             int v = exp[i];
@@ -324,10 +388,11 @@
      * Convert an expression encoded as an array of ints int ot a string
      *
      * @param opMask bit mask of operators vs commands
-     * @param exp    rpn sequence of values and operators
+     * @param exp rpn sequence of values and operators
      * @return string representation of the expression
      */
-    public static String toString(int opMask, int[] exp) {
+    @NonNull
+    public static String toString(int opMask, @NonNull int[] exp) {
         StringBuilder s = new StringBuilder();
         s.append(Integer.toBinaryString(opMask));
         s.append(" : ");
@@ -355,13 +420,15 @@
      * This creates an infix string expression
      *
      * @param opMask The bits that are operators
-     * @param exp    the array of expressions
+     * @param exp the array of expressions
      * @return infix string
      */
-    public static String toStringInfix(int opMask, int[] exp) {
+    @NonNull
+    public static String toStringInfix(int opMask, @NonNull int[] exp) {
         return toString(opMask, exp, exp.length - 1);
     }
 
+    @NonNull
     static String toString(int mask, int[] exp, int sp) {
         if (((1 << sp) & mask) != 0) {
             int id = exp[sp] - OFFSET;
@@ -412,34 +479,34 @@
     }
 
     static final int[] NO_OF_OPS = {
-            -1, // no op
-            2,
-            2,
-            2,
-            2,
-            2, // + - * / %
-            2,
-            2,
-            2,
-            2,
-            2,
-            2,
-            2,
-            2,
-            2, // <<, >> , >>> , | , &, ^, min max
-            1,
-            1,
-            1,
-            1,
-            1,
-            1, // neg, abs, ++, -- , not , sign
-            3,
-            3,
-            3, // clamp, ifElse, mad,
-            0,
-            0,
-            0 // mad, ?:,
-            // a[0],a[1],a[2]
+        -1, // no op
+        2,
+        2,
+        2,
+        2,
+        2, // + - * / %
+        2,
+        2,
+        2,
+        2,
+        2,
+        2,
+        2,
+        2,
+        2, // <<, >> , >>> , | , &, ^, min max
+        1,
+        1,
+        1,
+        1,
+        1,
+        1, // neg, abs, ++, -- , not , sign
+        3,
+        3,
+        3, // clamp, ifElse, mad,
+        0,
+        0,
+        0 // mad, ?:,
+        // a[0],a[1],a[2]
     };
 
     /**
@@ -456,7 +523,7 @@
      * is it an id or operation
      *
      * @param opMask the bits that mark elements as an operation
-     * @param i      the bit to check
+     * @param i the bit to check
      * @return true if the bit is 1
      */
     public static boolean isOperation(int opMask, int i) {
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
index ab7576e..92127c1 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringSerializer.java
@@ -15,9 +15,14 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /** Utility serializer maintaining an indent buffer */
 public class StringSerializer {
-    StringBuffer mBuffer = new StringBuffer();
+    @NonNull StringBuffer mBuffer = new StringBuffer();
+
+    @NonNull
     String mIndentBuffer = "                                                                      ";
 
     /**
@@ -26,7 +31,7 @@
      * @param indent the indentation level to use
      * @param content content to append
      */
-    public void append(int indent, String content) {
+    public void append(int indent, @Nullable String content) {
         String indentation = mIndentBuffer.substring(0, indent);
         mBuffer.append(indentation);
         mBuffer.append(indentation);
@@ -44,6 +49,7 @@
      *
      * @return string representation
      */
+    @NonNull
     @Override
     public String toString() {
         return mBuffer.toString();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java
index f2ccb40..a95a175 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/StringUtils.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities;
 
+import android.annotation.NonNull;
+
 import java.util.Arrays;
 
 /** Utilities for string manipulation */
@@ -30,6 +32,7 @@
      * @param post character to pad width 0 = no pad typically ' ' or '0'
      * @return
      */
+    @NonNull
     public static String floatToString(
             float value, int beforeDecimalPoint, int afterDecimalPoint, char pre, char post) {
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java
index 60a59cf..1343345 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/CubicEasing.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+
 class CubicEasing extends Easing {
     float mX1 = 0f;
     float mY1 = 0f;
@@ -62,7 +64,7 @@
         mType = type;
     }
 
-    void setup(float[] values) {
+    void setup(@NonNull float[] values) {
         setup(values[0], values[1], values[2], values[3]);
     }
 
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
index a29b8af..ebb22b6 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/FloatAnimation.java
@@ -15,6 +15,9 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
 /** Support Animation of the FloatExpression */
 public class FloatAnimation extends Easing {
     float[] mSpec;
@@ -31,6 +34,7 @@
     //    private float mScale = 1;
     float mOffset = 0;
 
+    @NonNull
     @Override
     public String toString() {
 
@@ -74,7 +78,7 @@
      * @return
      */
     public static float[] packToFloatArray(
-            float duration, int type, float[] spec, float initialValue, float wrap) {
+            float duration, int type, @Nullable float[] spec, float initialValue, float wrap) {
         int count = 0;
 
         if (!Float.isNaN(initialValue)) {
@@ -129,6 +133,90 @@
     }
 
     /**
+     * Useful to debug the packed form of an animation string
+     *
+     * @param description
+     * @return
+     */
+    public static String unpackAnimationToString(float[] description) {
+        float[] mSpec = description;
+        float mDuration = (mSpec.length == 0) ? 1 : mSpec[0];
+        int len = 0;
+        int type = 0;
+        float wrapValue = Float.NaN;
+        float initialValue = Float.NaN;
+        if (mSpec.length > 1) {
+            int num_type = Float.floatToRawIntBits(mSpec[1]);
+            type = num_type & 0xFF;
+            boolean wrap = ((num_type >> 8) & 0x1) > 0;
+            boolean init = ((num_type >> 8) & 0x2) > 0;
+            len = (num_type >> 16) & 0xFFFF;
+            int off = 2 + len;
+            if (init) {
+                initialValue = mSpec[off++];
+            }
+            if (wrap) {
+                wrapValue = mSpec[off];
+            }
+        }
+        float[] params = description;
+        int offset = 2;
+
+        String typeStr = "";
+        switch (type) {
+            case CUBIC_STANDARD:
+                typeStr = "CUBIC_STANDARD";
+                break;
+            case CUBIC_ACCELERATE:
+                typeStr = "CUBIC_ACCELERATE";
+                break;
+            case CUBIC_DECELERATE:
+                typeStr = "CUBIC_DECELERATE";
+                break;
+            case CUBIC_LINEAR:
+                typeStr = "CUBIC_LINEAR";
+                break;
+            case CUBIC_ANTICIPATE:
+                typeStr = "CUBIC_ANTICIPATE";
+                break;
+            case CUBIC_OVERSHOOT:
+                typeStr = "CUBIC_OVERSHOOT";
+
+                break;
+            case CUBIC_CUSTOM:
+                typeStr = "CUBIC_CUSTOM (";
+                typeStr += params[offset + 0] + " ";
+                typeStr += params[offset + 1] + " ";
+                typeStr += params[offset + 2] + " ";
+                typeStr += params[offset + 3] + " )";
+                break;
+            case EASE_OUT_BOUNCE:
+                typeStr = "EASE_OUT_BOUNCE";
+
+                break;
+            case EASE_OUT_ELASTIC:
+                typeStr = "EASE_OUT_ELASTIC";
+                break;
+            case SPLINE_CUSTOM:
+                typeStr = "SPLINE_CUSTOM (";
+                for (int i = offset; i < offset + len; i++) {
+                    typeStr += params[i] + " ";
+                }
+                typeStr += ")";
+                break;
+        }
+
+        String str = mDuration + " " + typeStr;
+        if (!Float.isNaN(initialValue)) {
+            str += " init =" + initialValue;
+        }
+        if (!Float.isNaN(wrapValue)) {
+            str += " wrap =" + wrapValue;
+        }
+        return str;
+    }
+
+    /**
      * Create an animation based on a float encoding of the animation
      *
      * @param description
@@ -208,21 +296,43 @@
         setScaleOffset();
     }
 
+    private static float wrap(float wrap, float value) {
+        value = value % wrap;
+        if (value < 0) {
+            value += wrap;
+        }
+        return value;
+    }
+
+    float wrapDistance(float wrap, float from, float to) {
+        float delta = (to - from) % 360;
+        if (delta < -wrap / 2) {
+            delta += wrap;
+        } else if (delta > wrap / 2) {
+            delta -= wrap;
+        }
+        return delta;
+    }
+
     /**
      * Set the target value to interpolate to
      *
      * @param value
      */
     public void setTargetValue(float value) {
-        if (Float.isNaN(mWrap)) {
-            mTargetValue = value;
-        } else {
-            if (Math.abs((value % mWrap) + mWrap - mInitialValue)
-                    < Math.abs((value % mWrap) - mInitialValue)) {
-                mTargetValue = (value % mWrap) + mWrap;
+        mTargetValue = value;
+        if (!Float.isNaN(mWrap)) {
+            mInitialValue = wrap(mWrap, mInitialValue);
+            mTargetValue = wrap(mWrap, mTargetValue);
+            if (Float.isNaN(mInitialValue)) {
+                mInitialValue = mTargetValue;
+            }
 
-            } else {
-                mTargetValue = value % mWrap;
+            float dist = wrapDistance(mWrap, mInitialValue, mTargetValue);
+            if ((dist > 0) && (mTargetValue < mInitialValue)) {
+                mTargetValue += mWrap;
+            } else if ((dist < 0) && (mTargetValue > mInitialValue)) {
+                mTargetValue -= mWrap;
             }
         }
         setScaleOffset();
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
index 75a6032..90b65bf 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/GeneralEasing.java
@@ -15,10 +15,12 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+
 /** Provides and interface to create easing functions */
 public class GeneralEasing extends Easing {
     float[] mEasingData = new float[0];
-    Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD);
+    @NonNull Easing mEasingCurve = new CubicEasing(CUBIC_STANDARD);
 
     /**
      * Set the curve based on the float encoding of it
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
index 9355cac..f540e70 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/MonotonicCurveFit.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+
 import java.util.Arrays;
 
 /** This performs a spline interpolation in multiple dimensions */
@@ -32,7 +34,7 @@
      * @param time the point along the curve
      * @param y the parameter at those points
      */
-    public MonotonicCurveFit(double[] time, double[][] y) {
+    public MonotonicCurveFit(@NonNull double[] time, @NonNull double[][] y) {
         final int n = time.length;
         final int dim = y[0].length;
         mSlopeTemp = new double[dim];
@@ -331,7 +333,8 @@
     }
 
     /** This builds a monotonic spline to be used as a wave function */
-    public static MonotonicCurveFit buildWave(String configString) {
+    @NonNull
+    public static MonotonicCurveFit buildWave(@NonNull String configString) {
         // done this way for efficiency
         String str = configString;
         double[] values = new double[str.length() / 2];
@@ -350,7 +353,8 @@
         return buildWave(Arrays.copyOf(values, count));
     }
 
-    private static MonotonicCurveFit buildWave(double[] values) {
+    @NonNull
+    private static MonotonicCurveFit buildWave(@NonNull double[] values) {
         int length = values.length * 3 - 2;
         int len = values.length - 1;
         double gap = 1.0 / len;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
index b459689..c7be3ca 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/easing/StepCurve.java
@@ -15,6 +15,8 @@
  */
 package com.android.internal.widget.remotecompose.core.operations.utilities.easing;
 
+import android.annotation.NonNull;
+
 /**
  * This class translates a series of floating point values into a continuous curve for use in an
  * easing function including quantize functions it is used with the "spline(0,0.3,0.3,0.5,...0.9,1)"
@@ -28,6 +30,7 @@
         mCurveFit = genSpline(params, offset, len);
     }
 
+    @NonNull
     private static MonotonicCurveFit genSpline(float[] values, int off, int arrayLen) {
         int length = arrayLen * 3 - 2;
         int len = arrayLen - 1;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
new file mode 100644
index 0000000..3e24372
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/utilities/touch/VelocityEasing.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.widget.remotecompose.core.operations.utilities.touch;
+
+/*
+ * Copyright (C) 2024 The Android Open 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.
+ */
+
+public class VelocityEasing {
+    private float mStartPos = 0;
+    private float mStartV = 0;
+    private float mEndPos = 0;
+    private float mDuration = 0;
+
+    private Stage[] mStage = {new Stage(1), new Stage(2), new Stage(3)};
+    private int mNumberOfStages = 0;
+    private Easing mEasing;
+    private double mEasingAdapterDistance = 0;
+    private double mEasingAdapterA = 0;
+    private double mEasingAdapterB = 0;
+    private boolean mOneDimension = true;
+    private float mTotalEasingDuration = 0;
+
+    public float getDuration() {
+        if (mEasing != null) {
+            return mTotalEasingDuration;
+        }
+        return mDuration;
+    }
+
+    public float getV(float t) {
+        if (mEasing == null) {
+            for (int i = 0; i < mNumberOfStages; i++) {
+                if (mStage[i].mEndTime > t) {
+                    return mStage[i].getVel(t);
+                }
+            }
+            return 0f;
+        }
+        int lastStages = mNumberOfStages - 1;
+        for (int i = 0; i < lastStages; i++) {
+            if (mStage[i].mEndTime > t) {
+                return mStage[i].getVel(t);
+            }
+        }
+        return (float) getEasingDiff((t - mStage[lastStages].mStartTime));
+    }
+
+    public float getPos(float t) {
+        if (mEasing == null) {
+            for (int i = 0; i < mNumberOfStages; i++) {
+                if (mStage[i].mEndTime > t) {
+                    return mStage[i].getPos(t);
+                }
+            }
+            return mEndPos;
+        }
+        int lastStages = mNumberOfStages - 1;
+        for (int i = 0; i < lastStages; i++) {
+            if (mStage[i].mEndTime > t) {
+                return mStage[i].getPos(t);
+            }
+        }
+        var ret = (float) getEasing((t - mStage[lastStages].mStartTime));
+        ret += mStage[lastStages].mStartPos;
+        return ret;
+    }
+
+    public String toString() {
+        var s = " ";
+        for (int i = 0; i < mNumberOfStages; i++) {
+            Stage stage = mStage[i];
+            s += " $i $stage";
+        }
+        return s;
+    }
+
+    public void config(
+            float currentPos,
+            float destination,
+            float currentVelocity,
+            float maxTime,
+            float maxAcceleration,
+            float maxVelocity,
+            Easing easing) {
+        float pos = currentPos;
+        float velocity = currentVelocity;
+        if (pos == destination) {
+            pos += 1f;
+        }
+        mStartPos = pos;
+        mEndPos = destination;
+        if (easing != null) {
+            this.mEasing = easing.clone();
+        }
+        float dir = Math.signum(destination - pos);
+        float maxV = maxVelocity * dir;
+        float maxA = maxAcceleration * dir;
+        if (velocity == 0.0) {
+            velocity = 0.0001f * dir;
+        }
+        mStartV = velocity;
+        if (!rampDown(pos, destination, velocity, maxTime)) {
+            if (!(mOneDimension
+                    && cruseThenRampDown(pos, destination, velocity, maxTime, maxA, maxV))) {
+                if (!rampUpRampDown(pos, destination, velocity, maxA, maxV, maxTime)) {
+                    rampUpCruseRampDown(pos, destination, velocity, maxA, maxV, maxTime);
+                }
+            }
+        }
+        if (mOneDimension) {
+            configureEasingAdapter();
+        }
+    }
+
+    private boolean rampDown(
+            float currentPos, float destination, float currentVelocity, float maxTime) {
+        float timeToDestination = 2 * ((destination - currentPos) / currentVelocity);
+        if (timeToDestination > 0 && timeToDestination <= maxTime) { // hit the brakes
+            mNumberOfStages = 1;
+            mStage[0].setUp(currentVelocity, currentPos, 0f, 0f, destination, timeToDestination);
+            mDuration = timeToDestination;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean cruseThenRampDown(
+            float currentPos,
+            float destination,
+            float currentVelocity,
+            float maxTime,
+            float maxA,
+            float maxV) {
+        float timeToBreak = currentVelocity / maxA;
+        float brakeDist = currentVelocity * timeToBreak / 2;
+        float cruseDist = destination - currentPos - brakeDist;
+        float cruseTime = cruseDist / currentVelocity;
+        float totalTime = cruseTime + timeToBreak;
+        if (totalTime > 0 && totalTime < maxTime) {
+            mNumberOfStages = 2;
+            mStage[0].setUp(currentVelocity, currentPos, 0f, currentVelocity, cruseDist, cruseTime);
+            mStage[1].setUp(
+                    currentVelocity,
+                    currentPos + cruseDist,
+                    cruseTime,
+                    0f,
+                    destination,
+                    cruseTime + timeToBreak);
+            mDuration = cruseTime + timeToBreak;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean rampUpRampDown(
+            float currentPos,
+            float destination,
+            float currentVelocity,
+            float maxA,
+            float maxVelocity,
+            float maxTime) {
+        float peak_v =
+                Math.signum(maxA)
+                        * (float)
+                                Math.sqrt(
+                                        (maxA * (destination - currentPos)
+                                                + currentVelocity * currentVelocity / 2));
+        if (maxVelocity / peak_v > 1) {
+            float t1 = (peak_v - currentVelocity) / maxA;
+            float d1 = (peak_v + currentVelocity) * t1 / 2 + currentPos;
+            float t2 = peak_v / maxA;
+            mNumberOfStages = 2;
+            mStage[0].setUp(currentVelocity, currentPos, 0f, peak_v, d1, t1);
+            mStage[1].setUp(peak_v, d1, t1, 0f, destination, t2 + t1);
+            mDuration = t2 + t1;
+            if (mDuration > maxTime) {
+                return false;
+            }
+            if (mDuration < maxTime / 2) {
+                t1 = mDuration / 2;
+                t2 = t1;
+                peak_v = (2 * (destination - currentPos) / t1 - currentVelocity) / 2;
+                d1 = (peak_v + currentVelocity) * t1 / 2 + currentPos;
+                mNumberOfStages = 2;
+                mStage[0].setUp(currentVelocity, currentPos, 0f, peak_v, d1, t1);
+                mStage[1].setUp(peak_v, d1, t1, 0f, destination, t2 + t1);
+                mDuration = t2 + t1;
+                if (mDuration > maxTime) {
+                    System.out.println(" fail ");
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void rampUpCruseRampDown(
+            float currentPos,
+            float destination,
+            float currentVelocity,
+            float maxA,
+            float maxV,
+            float maxTime) {
+        float t1 = maxTime / 3;
+        float t2 = t1 * 2;
+        float distance = destination - currentPos;
+        float dt2 = t2 - t1;
+        float dt3 = maxTime - t2;
+        float v1 = (2 * distance - currentVelocity * t1) / (t1 + 2 * dt2 + dt3);
+        mDuration = maxTime;
+        float d1 = (currentVelocity + v1) * t1 / 2;
+        float d2 = (v1 + v1) * (t2 - t1) / 2;
+        mNumberOfStages = 3;
+        float acc = (v1 - currentVelocity) / t1;
+        float dec = v1 / dt3;
+        mStage[0].setUp(currentVelocity, currentPos, 0f, v1, currentPos + d1, t1);
+        mStage[1].setUp(v1, currentPos + d1, t1, v1, currentPos + d1 + d2, t2);
+        mStage[2].setUp(v1, currentPos + d1 + d2, t2, 0f, destination, maxTime);
+        mDuration = maxTime;
+    }
+
+    double getEasing(double t) {
+        double gx = t * t * mEasingAdapterA + t * mEasingAdapterB;
+        if (gx > 1) {
+            return mEasingAdapterDistance;
+        } else {
+            return mEasing.get(gx) * mEasingAdapterDistance;
+        }
+    }
+
+    private double getEasingDiff(double t) {
+        double gx = t * t * mEasingAdapterA + t * mEasingAdapterB;
+        if (gx > 1) {
+            return 0.0;
+        } else {
+            return mEasing.getDiff(gx)
+                    * mEasingAdapterDistance
+                    * (t * mEasingAdapterA + mEasingAdapterB);
+        }
+    }
+
+    protected void configureEasingAdapter() {
+        if (mEasing == null) {
+            return;
+        }
+        int last = mNumberOfStages - 1;
+        float initialVelocity = mStage[last].mStartV;
+        float distance = mStage[last].mEndPos - mStage[last].mStartPos;
+        float duration = mStage[last].mEndTime - mStage[last].mStartTime;
+        double baseVel = mEasing.getDiff(0.0);
+        mEasingAdapterB = initialVelocity / (baseVel * distance);
+        mEasingAdapterA = 1 - mEasingAdapterB;
+        mEasingAdapterDistance = distance;
+        double easingDuration =
+                (Math.sqrt(4 * mEasingAdapterA + mEasingAdapterB * mEasingAdapterB)
+                                - mEasingAdapterB)
+                        / (2 * mEasingAdapterA);
+        mTotalEasingDuration = (float) (easingDuration + mStage[last].mStartTime);
+    }
+
+    interface Easing {
+        double get(double t);
+
+        double getDiff(double t);
+
+        Easing clone();
+    }
+
+    class Stage {
+        private float mStartV = 0;
+        private float mStartPos = 0;
+        private float mStartTime = 0;
+        private float mEndV = 0;
+        private float mEndPos = 0;
+        private float mEndTime = 0;
+        private float mDeltaV = 0;
+        private float mDeltaT = 0;
+        final int mStage;
+
+        Stage(int n) {
+            mStage = n;
+        }
+
+        void setUp(
+                float startV,
+                float startPos,
+                float startTime,
+                float endV,
+                float endPos,
+                float endTime) {
+            this.mStartV = startV;
+            this.mStartPos = startPos;
+            this.mStartTime = startTime;
+            this.mEndV = endV;
+            this.mEndTime = endTime;
+            this.mEndPos = endPos;
+            mDeltaV = this.mEndV - this.mStartV;
+            mDeltaT = this.mEndTime - this.mStartTime;
+        }
+
+        float getPos(float t) {
+            float dt = t - mStartTime;
+            float pt = dt / mDeltaT;
+            float v = mStartV + mDeltaV * pt;
+            return dt * (mStartV + v) / 2 + mStartPos;
+        }
+
+        float getVel(float t) {
+            float dt = t - mStartTime;
+            float pt = dt / (mEndTime - mStartTime);
+            return mStartV + mDeltaV * pt;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
index 57a8042..3fba8ac 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/BooleanConstant.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.BYTE;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -47,23 +49,26 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mValue);
     }
 
     @Override
     public void apply(RemoteContext context) {}
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "BooleanConstant[" + mId + "] = " + mValue + "";
     }
 
+    @NonNull
     public static String name() {
         return "OrigamiBoolean";
     }
@@ -79,20 +84,20 @@
      * @param id
      * @param value
      */
-    public static void apply(WireBuffer buffer, int id, boolean value) {
+    public static void apply(@NonNull WireBuffer buffer, int id, boolean value) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeBoolean(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
 
         boolean value = buffer.readBoolean();
         operations.add(new BooleanConstant(id, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, "BooleanConstant")
                 .description("A boolean and its associated id")
                 .field(DocumentedOperation.INT, "id", "id of Int")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
index 3ef9db9..79f2a8d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/IntegerConstant.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.INT;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -37,25 +39,28 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mValue);
     }
 
     @Override
-    public void apply(RemoteContext context) {
+    public void apply(@NonNull RemoteContext context) {
         context.loadInteger(mId, mValue);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "IntegerConstant[" + mId + "] = " + mValue + "";
     }
 
+    @NonNull
     public static String name() {
         return "IntegerConstant";
     }
@@ -71,20 +76,20 @@
      * @param textId
      * @param value
      */
-    public static void apply(WireBuffer buffer, int textId, int value) {
+    public static void apply(@NonNull WireBuffer buffer, int textId, int value) {
         buffer.start(Operations.DATA_INT);
         buffer.writeInt(textId);
         buffer.writeInt(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
 
         int value = buffer.readInt();
         operations.add(new IntegerConstant(id, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", id(), "IntegerConstant")
                 .description("A integer and its associated id")
                 .field(DocumentedOperation.INT, "id", "id of Int")
diff --git a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
index 6d51d19..01672b4 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/types/LongConstant.java
@@ -17,6 +17,8 @@
 
 import static com.android.internal.widget.remotecompose.core.documentation.DocumentedOperation.LONG;
 
+import android.annotation.NonNull;
+
 import com.android.internal.widget.remotecompose.core.Operation;
 import com.android.internal.widget.remotecompose.core.Operations;
 import com.android.internal.widget.remotecompose.core.RemoteContext;
@@ -47,7 +49,7 @@
     }
 
     @Override
-    public void write(WireBuffer buffer) {
+    public void write(@NonNull WireBuffer buffer) {
         apply(buffer, mId, mValue);
     }
 
@@ -56,11 +58,13 @@
         context.putObject(mId, this);
     }
 
+    @NonNull
     @Override
     public String deepToString(String indent) {
         return toString();
     }
 
+    @NonNull
     @Override
     public String toString() {
         return "LongConstant[" + mId + "] = " + mValue + "";
@@ -73,20 +77,20 @@
      * @param id
      * @param value
      */
-    public static void apply(WireBuffer buffer, int id, long value) {
+    public static void apply(@NonNull WireBuffer buffer, int id, long value) {
         buffer.start(OP_CODE);
         buffer.writeInt(id);
         buffer.writeLong(value);
     }
 
-    public static void read(WireBuffer buffer, List<Operation> operations) {
+    public static void read(@NonNull WireBuffer buffer, @NonNull List<Operation> operations) {
         int id = buffer.readInt();
 
         long value = buffer.readLong();
         operations.add(new LongConstant(id, value));
     }
 
-    public static void documentation(DocumentationBuilder doc) {
+    public static void documentation(@NonNull DocumentationBuilder doc) {
         doc.operation("Expressions Operations", OP_CODE, "LongConstant")
                 .description("A boolean and its associated id")
                 .field(DocumentedOperation.INT, "id", "id of Int")
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
index 906282c..aaee9c5 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
@@ -112,6 +112,16 @@
     }
 
     /**
+     * Gets a array of Names of the named variables of a specific type defined in the doc.
+     *
+     * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc
+     * @return array of name or null
+     */
+    public String[] getNamedVariables(int type) {
+        return mDocument.getNamedVariables(type);
+    }
+
+    /**
      * Return a component associated with id
      *
      * @param id the component id
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index 06bf4cd..cc74b11 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -21,11 +21,14 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
+import android.view.HapticFeedbackConstants;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 import android.widget.HorizontalScrollView;
 import android.widget.ScrollView;
 
+import com.android.internal.widget.remotecompose.core.CoreDocument;
+import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
 import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
 import com.android.internal.widget.remotecompose.player.platform.RemoteComposeCanvas;
 
@@ -57,11 +60,7 @@
      * @param debugFlags 1 to set debug on
      */
     public void setDebug(int debugFlags) {
-        if (debugFlags == 1) {
-            mInner.setDebug(true);
-        } else {
-            mInner.setDebug(false);
-        }
+        mInner.setDebug(debugFlags);
     }
 
     public RemoteComposeDocument getDocument() {
@@ -82,6 +81,14 @@
             mInner.setDocument(null);
         }
         mapColors();
+        mInner.setHapticEngine(
+                new CoreDocument.HapticEngine() {
+
+                    @Override
+                    public void haptic(int type) {
+                        provideHapticFeedback(type);
+                    }
+                });
     }
 
     /**
@@ -259,13 +266,40 @@
     /**
      * This returns a list of colors that have names in the Document.
      *
-     * @return
+     * @return the names of named Strings or null
      */
     public String[] getNamedColors() {
         return mInner.getNamedColors();
     }
 
     /**
+     * This returns a list of floats that have names in the Document.
+     *
+     * @return return the names of named floats in the document
+     */
+    public String[] getNamedFloats() {
+        return mInner.getNamedVariables(NamedVariable.FLOAT_TYPE);
+    }
+
+    /**
+     * This returns a list of string name that have names in the Document.
+     *
+     * @return the name of named string (not the string itself)
+     */
+    public String[] getNamedStrings() {
+        return mInner.getNamedVariables(NamedVariable.STRING_TYPE);
+    }
+
+    /**
+     * This returns a list of images that have names in the Document.
+     *
+     * @return
+     */
+    public String[] getNamedImages() {
+        return mInner.getNamedVariables(NamedVariable.IMAGE_TYPE);
+    }
+
+    /**
      * This sets a color based on its name. Overriding the color set in the document.
      *
      * @param colorName Name of the color
@@ -481,4 +515,32 @@
             return color;
         }
     }
+
+    private static int[] sHapticTable = {
+        HapticFeedbackConstants.NO_HAPTICS,
+        HapticFeedbackConstants.LONG_PRESS,
+        HapticFeedbackConstants.VIRTUAL_KEY,
+        HapticFeedbackConstants.KEYBOARD_TAP,
+        HapticFeedbackConstants.CLOCK_TICK,
+        HapticFeedbackConstants.CONTEXT_CLICK,
+        HapticFeedbackConstants.KEYBOARD_PRESS,
+        HapticFeedbackConstants.KEYBOARD_RELEASE,
+        HapticFeedbackConstants.VIRTUAL_KEY_RELEASE,
+        HapticFeedbackConstants.TEXT_HANDLE_MOVE,
+        HapticFeedbackConstants.GESTURE_START,
+        HapticFeedbackConstants.GESTURE_END,
+        HapticFeedbackConstants.CONFIRM,
+        HapticFeedbackConstants.REJECT,
+        HapticFeedbackConstants.TOGGLE_ON,
+        HapticFeedbackConstants.TOGGLE_OFF,
+        HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE,
+        HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE,
+        HapticFeedbackConstants.DRAG_START,
+        HapticFeedbackConstants.SEGMENT_TICK,
+        HapticFeedbackConstants.SEGMENT_FREQUENT_TICK,
+    };
+
+    private void provideHapticFeedback(int type) {
+        performHapticFeedback(sHapticTable[type % sHapticTable.length]);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
index f59a0d3..0b650a9 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPaintContext.java
@@ -26,6 +26,8 @@
 import android.graphics.RadialGradient;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.RenderEffect;
+import android.graphics.RenderNode;
 import android.graphics.RuntimeShader;
 import android.graphics.Shader;
 import android.graphics.SweepGradient;
@@ -51,6 +53,8 @@
     List<Paint> mPaintList = new ArrayList<>();
     Canvas mCanvas;
     Rect mTmpRect = new Rect(); // use in calculation of bounds
+    RenderNode mNode = null;
+    Canvas mPreviousCanvas = null;
 
     public AndroidPaintContext(RemoteContext context, Canvas canvas) {
         super(context);
@@ -122,6 +126,53 @@
     }
 
     @Override
+    public void startGraphicsLayer(int w, int h) {
+        mNode = new RenderNode("layer");
+        mNode.setPosition(0, 0, w, h);
+        mPreviousCanvas = mCanvas;
+        mCanvas = mNode.beginRecording();
+    }
+
+    @Override
+    public void setGraphicsLayer(
+            float scaleX,
+            float scaleY,
+            float rotationX,
+            float rotationY,
+            float rotationZ,
+            float shadowElevation,
+            float transformOriginX,
+            float transformOriginY,
+            float alpha,
+            int renderEffectId) {
+        if (mNode == null) {
+            return;
+        }
+        mNode.setScaleX(scaleX);
+        mNode.setScaleY(scaleY);
+        mNode.setRotationX(rotationX);
+        mNode.setRotationY(rotationY);
+        mNode.setRotationZ(rotationZ);
+        mNode.setPivotX(transformOriginX * mNode.getWidth());
+        mNode.setPivotY(transformOriginY * mNode.getHeight());
+        mNode.setAlpha(alpha);
+        if (renderEffectId == 1) {
+
+            RenderEffect effect = RenderEffect.createBlurEffect(8f, 8f, Shader.TileMode.CLAMP);
+            mNode.setRenderEffect(effect);
+        }
+    }
+
+    @Override
+    public void endGraphicsLayer() {
+        mNode.endRecording();
+        mCanvas = mPreviousCanvas;
+        mCanvas.drawRenderNode(mNode);
+        // node.discardDisplayList();
+        mNode = null;
+    }
+
+    @Override
     public void translate(float translateX, float translateY) {
         mCanvas.translate(translateX, translateY);
     }
@@ -241,6 +292,8 @@
             if (start != 0) {
                 textToPaint = textToPaint.substring(start);
             }
+        } else if (end > textToPaint.length()) {
+            textToPaint = textToPaint.substring(start);
         } else {
             textToPaint = textToPaint.substring(start, end);
         }
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
index f9b22a2..f28e85a 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidPlatformServices.java
@@ -18,6 +18,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Path;
 import android.graphics.PathIterator;
+import android.util.Log;
 
 import com.android.internal.widget.remotecompose.core.Platform;
 import com.android.internal.widget.remotecompose.core.operations.PathData;
@@ -27,6 +28,8 @@
 
 /** Services that are needed to be provided by the platform during encoding. */
 public class AndroidPlatformServices implements Platform {
+    private static final String LOG_TAG = "RemoteCompose";
+
     @Override
     public byte[] imageToByteArray(Object image) {
         if (image instanceof Bitmap) {
@@ -67,6 +70,24 @@
         return null;
     }
 
+    @Override
+    public void log(LogCategory category, String message) {
+        switch (category) {
+            case DEBUG:
+                Log.d(LOG_TAG, message);
+                break;
+            case INFO:
+                Log.i(LOG_TAG, message);
+                break;
+            case WARN:
+                Log.w(LOG_TAG, message);
+                break;
+            default:
+                Log.e(LOG_TAG, message);
+                break;
+        }
+    }
+
     private float[] androidPathToFloatArray(Path path) {
         PathIterator i = path.getPathIterator();
         int estimatedSize = 0;
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index e7c0cc8..7a7edba 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -20,6 +20,7 @@
 import android.graphics.Canvas;
 
 import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.TouchListener;
 import com.android.internal.widget.remotecompose.core.VariableSupport;
 import com.android.internal.widget.remotecompose.core.operations.FloatExpression;
 import com.android.internal.widget.remotecompose.core.operations.ShaderData;
@@ -143,9 +144,9 @@
     }
 
     @Override
-    public void runNamedAction(int id) {
+    public void runNamedAction(int id, Object value) {
         String text = getText(id);
-        mDocument.runNamedAction(text);
+        mDocument.runNamedAction(text, value);
     }
 
     /**
@@ -200,6 +201,11 @@
     }
 
     @Override
+    public void overrideFloat(int id, float value) {
+        mRemoteComposeState.overrideFloat(id, value);
+    }
+
+    @Override
     public void loadInteger(int id, int value) {
         mRemoteComposeState.updateInteger(id, value);
     }
@@ -268,6 +274,11 @@
         return (ShaderData) mRemoteComposeState.getFromId(id);
     }
 
+    @Override
+    public void addTouchListener(TouchListener touchExpression) {
+        mDocument.addTouchListener(touchExpression);
+    }
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     // Click handling
     ///////////////////////////////////////////////////////////////////////////////////////////////
@@ -285,4 +296,8 @@
         String metadata = (String) mRemoteComposeState.getFromId(metadataId);
         mDocument.addClickArea(id, contentDescription, left, top, right, bottom, metadata);
     }
+
+    public void hapticEffect(int type) {
+        mDocument.haptic(type);
+    }
 }
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index 7de6988..b54ed8a 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -21,6 +21,7 @@
 import android.graphics.Point;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
 import android.widget.FrameLayout;
 
@@ -38,7 +39,7 @@
     RemoteComposeDocument mDocument = null;
     int mTheme = Theme.LIGHT;
     boolean mInActionDown = false;
-    boolean mDebug = false;
+    int mDebug = 0;
     boolean mHasClickAreas = false;
     Point mActionDownPoint = new Point(0, 0);
     AndroidRemoteContext mARContext = new AndroidRemoteContext();
@@ -65,14 +66,14 @@
         }
     }
 
-    public void setDebug(boolean value) {
+    public void setDebug(int value) {
         if (mDebug != value) {
             mDebug = value;
             if (USE_VIEW_AREA_CLICK) {
                 for (int i = 0; i < getChildCount(); i++) {
                     View child = getChildAt(i);
                     if (child instanceof ClickAreaView) {
-                        ((ClickAreaView) child).setDebug(mDebug);
+                        ((ClickAreaView) child).setDebug(mDebug == 1);
                     }
                 }
             }
@@ -107,7 +108,7 @@
                 ClickAreaView viewArea =
                         new ClickAreaView(
                                 getContext(),
-                                mDebug,
+                                mDebug == 1,
                                 area.getId(),
                                 area.getContentDescription(),
                                 area.getMetadata());
@@ -128,6 +129,10 @@
         }
     }
 
+    public void setHapticEngine(CoreDocument.HapticEngine engine) {
+        mDocument.getDocument().setHapticEngine(engine);
+    }
+
     @Override
     public void onViewDetachedFromWindow(View view) {
         removeAllViews();
@@ -138,6 +143,16 @@
     }
 
     /**
+     * Gets a array of Names of the named variables of a specific type defined in the loaded doc.
+     *
+     * @param type the type of variable NamedVariable.COLOR_TYPE, STRING_TYPE, etc
+     * @return array of name or null
+     */
+    public String[] getNamedVariables(int type) {
+        return mDocument.getNamedVariables(type);
+    }
+
+    /**
      * set the color associated with this name.
      *
      * @param colorName Name of color typically "android.xxx"
@@ -198,7 +213,12 @@
         this.mTheme = theme;
     }
 
+    private VelocityTracker mVelocityTracker = null;
+
     public boolean onTouchEvent(MotionEvent event) {
+        int index = event.getActionIndex();
+        int action = event.getActionMasked();
+        int pointerId = event.getPointerId(index);
         if (USE_VIEW_AREA_CLICK && mHasClickAreas) {
             return super.onTouchEvent(event);
         }
@@ -207,15 +227,51 @@
                 mActionDownPoint.x = (int) event.getX();
                 mActionDownPoint.y = (int) event.getY();
                 mInActionDown = true;
+                CoreDocument doc = mDocument.getDocument();
+                if (doc.hasTouchListener()) {
+                    if (mVelocityTracker == null) {
+                        mVelocityTracker = VelocityTracker.obtain();
+                    } else {
+                        mVelocityTracker.clear();
+                    }
+                    mVelocityTracker.addMovement(event);
+                    doc.touchDown(mARContext, event.getX(), event.getY());
+                }
                 return true;
+
             case MotionEvent.ACTION_CANCEL:
                 mInActionDown = false;
+                doc = mDocument.getDocument();
+                if (doc.hasTouchListener()) {
+                    mVelocityTracker.computeCurrentVelocity(1000);
+                    float dx = mVelocityTracker.getXVelocity(pointerId);
+                    float dy = mVelocityTracker.getYVelocity(pointerId);
+                    doc.touchCancel(mARContext, event.getX(), event.getY(), dx, dy);
+                }
                 return true;
             case MotionEvent.ACTION_UP:
                 mInActionDown = false;
                 performClick();
+                doc = mDocument.getDocument();
+                if (doc.hasTouchListener()) {
+                    mVelocityTracker.computeCurrentVelocity(1000);
+                    float dx = mVelocityTracker.getXVelocity(pointerId);
+                    float dy = mVelocityTracker.getYVelocity(pointerId);
+                    doc.touchUp(mARContext, event.getX(), event.getY(), dx, dy);
+                }
                 return true;
+
             case MotionEvent.ACTION_MOVE:
+                if (mInActionDown) {
+                    if (mVelocityTracker != null) {
+                        mVelocityTracker.addMovement(event);
+                        doc = mDocument.getDocument();
+                        boolean repaint = doc.touchDrag(mARContext, event.getX(), event.getY());
+                        if (repaint) {
+                            invalidate();
+                        }
+                    }
+                }
         }
         return false;
     }
@@ -292,7 +348,7 @@
         mARContext.mWidth = getWidth();
         mARContext.mHeight = getHeight();
         mDocument.paint(mARContext, mTheme);
-        if (mDebug) {
+        if (mDebug == 1) {
             mCount++;
             if (System.nanoTime() - mTime > 1000000000L) {
                 System.out.println(" count " + mCount + " fps");
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 66c2e12..0e4e22b 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -158,6 +158,7 @@
     flags_packages: [
         "android.app.appfunctions.flags-aconfig",
         "android.app.contextualsearch.flags-aconfig",
+        "android.app.flags-aconfig",
         "android.appwidget.flags-aconfig",
         "android.content.pm.flags-aconfig",
         "android.provider.flags-aconfig",
@@ -171,7 +172,9 @@
         "android.security.flags-aconfig",
         "com.android.hardware.input.input-aconfig",
         "aconfig_trade_in_mode_flags",
+        "art-aconfig-flags",
         "ranging_aconfig_flags",
+        "aconfig_settingslib_flags",
     ],
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5913992..d09802a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2632,13 +2632,22 @@
 
     <!-- @SystemApi Allows access to perform vendor effects in the vibrator.
          <p>Protection level: signature
-         @FlaggedApi("android.os.vibrator.vendor_vibration_effects")
+         @FlaggedApi(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
          @hide
     -->
     <permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS"
         android:protectionLevel="signature|privileged"
         android:featureFlag="android.os.vibrator.vendor_vibration_effects" />
 
+    <!-- @SystemApi Allows access to start a vendor vibration session.
+         <p>Protection level: signature
+        @FlaggedApi(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+        @hide
+    -->
+    <permission android:name="android.permission.START_VIBRATION_SESSIONS"
+        android:protectionLevel="signature|privileged"
+        android:featureFlag="android.os.vibrator.vendor_vibration_effects" />
+
     <!-- @SystemApi Allows access to the vibrator state.
          <p>Protection level: signature
          @hide
@@ -4454,6 +4463,18 @@
                 android:description="@string/permdesc_hideOverlayWindows"
                 android:protectionLevel="normal" />
 
+    <!-- Allows an app to enter Picture-in-Picture mode when the user is not explicitly requesting
+        it. This includes using {@link PictureInPictureParams.Builder#setAutoEnterEnabled} as well
+        as lifecycle methods such as {@link Activity#onUserLeaveHint} and {@link Activity#onPause}
+        to enter PiP when the user leaves the app.
+        This permission should only be used for certain PiP
+        <a href="{@docRoot}training/tv/get-started/multitasking#usage-types">usage types</a>.
+        @FlaggedApi(android.app.Flags.FLAG_ENABLE_TV_IMPLICIT_ENTER_PIP_RESTRICTION)
+     -->
+    <permission android:name="android.permission.TV_IMPLICIT_ENTER_PIP"
+        android:protectionLevel="normal"
+        android:featureFlag="android.app.enable_tv_implicit_enter_pip_restriction" />
+
     <!-- ================================== -->
     <!-- Permissions affecting the system wallpaper -->
     <!-- ================================== -->
@@ -4960,6 +4981,27 @@
     <permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS"
                 android:protectionLevel="signature|privileged|role" />
 
+    <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST)
+         Allows an application to access the Settings Preference services to read settings exposed
+         by the system Settings app and system apps that contribute settings surfaced by the
+         Settings app.
+         <p>This allows the calling application to read settings values through the host
+         application, agnostic of underlying storage. -->
+    <permission android:name="android.permission.READ_SYSTEM_PREFERENCES"
+        android:protectionLevel="signature|privileged|role"
+        android:featureFlag="com.android.settingslib.flags.settings_catalyst" />
+
+    <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST)
+         Allows an application to access the Settings Preference services to write settings
+         values exposed by the system Settings app and system apps that contribute settings surfaced
+         in the Settings app.
+         <p>This allows the calling application to write settings values
+         through the host application, agnostic of underlying storage.
+         <p>Protection Level: signature|privileged|appop - appop to be added in followup -->
+    <permission android:name="android.permission.WRITE_SYSTEM_PREFERENCES"
+        android:protectionLevel="signature|privileged"
+        android:featureFlag="com.android.settingslib.flags.settings_catalyst" />
+
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
     <!-- ========================================= -->
@@ -8482,6 +8524,16 @@
                 android:protectionLevel="signature|privileged"
                 android:featureFlag="com.android.tradeinmode.flags.enable_trade_in_mode" />
 
+    <!-- @SystemApi
+        @FlaggedApi(com.android.art.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS)
+        Ability to read program metadata and attach dynamic instrumentation.
+        <p>Protection level: signature
+        @hide
+    -->
+    <permission android:name="android.permission.DYNAMIC_INSTRUMENTATION"
+                android:protectionLevel="signature"
+                android:featureFlag="com.android.art.flags.executable_method_file_offsets" />
+
     <!--
         @TestApi
         Signature permission reserved for testing. This should never be used to
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 4d73f22..41dec37 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2273,6 +2273,22 @@
         <attr name="enableOnBackInvokedCallback" format="boolean"/>
 
         <attr name="intentMatchingFlags"/>
+
+        <!-- Specifies the set of drawable resources that can be used in place
+             of an existing declared icon or banner for activities that appear
+             in the app launcher. The resource referenced must be an array of
+             drawable resources and can contain at most 500 items.
+             {@link android.content.pm.PackageManager#changeLauncherIconConfig}
+             @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
+        <attr name="alternateLauncherIcons" format="reference" />
+
+        <!-- Specifies the set of string resources that can be used in place
+             of an existing declared label for activities that appear
+             in the app launcher. The resource referenced must be an array of
+             string resources and can contain at most 500 items.
+             {@link android.content.pm.PackageManager#changeLauncherIconConfig}
+             @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
+        <attr name="alternateLauncherLabels" format="reference" />
     </declare-styleable>
 
     <!-- An attribution is a logical part of an app and is identified by a tag.
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 70cc5f1..b6436d0 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -127,6 +127,10 @@
     <public name="intentMatchingFlags"/>
     <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) -->
     <public name="layoutLabel"/>
+    <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
+    <public name="alternateLauncherIcons"/>
+    <!-- @FlaggedApi(android.content.pm.Flags.FLAG_CHANGE_LAUNCHER_BADGING) -->
+    <public name="alternateLauncherLabels"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01b60000">
diff --git a/core/res/res/values/stoppable_fgs_system_apps.xml b/core/res/res/values/stoppable_fgs_system_apps.xml
new file mode 100644
index 0000000..165ff61
--- /dev/null
+++ b/core/res/res/values/stoppable_fgs_system_apps.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (C) 2024 The Android Open 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.
+ */
+-->
+<resources>
+    <!-- A list of system apps whose FGS can be stopped in the task manager. -->
+    <string-array translatable="false" name="stoppable_fgs_system_apps">
+    </string-array>
+    <!-- stoppable_fgs_system_apps which is supposed to be overridden by vendor -->
+    <string-array translatable="false" name="vendor_stoppable_fgs_system_apps">
+    </string-array>
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index aa08d5e..db81a3b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1306,6 +1306,8 @@
   <java-symbol type="array" name="vendor_policy_exempt_apps" />
   <java-symbol type="array" name="cloneable_apps" />
   <java-symbol type="array" name="config_securityStatePackages" />
+  <java-symbol type="array" name="stoppable_fgs_system_apps" />
+  <java-symbol type="array" name="vendor_stoppable_fgs_system_apps" />
 
   <java-symbol type="drawable" name="default_wallpaper" />
   <java-symbol type="drawable" name="default_lock_wallpaper" />
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
index 31a4f16..911b7ce 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java
@@ -120,7 +120,8 @@
         doReturn(newDisplayInfo).when(mIDisplayManager).getDisplayInfo(123);
 
         mDisplayManager.registerDisplayListener(mListener, mHandler,
-                DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */);
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+                null /* packageName */);
 
         mController.onDisplayChanged(123);
         mHandler.runWithScissors(() -> { }, 0);
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
index d169ce3..7bc4abd 100644
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -37,6 +37,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
 /**
  *  Build/Install/Run:
  *   atest FrameworksCoreTests:IntentTest
@@ -57,7 +61,12 @@
     public void testReadFromParcelWithExtraIntentKeys() {
         Intent intent = new Intent("TEST_ACTION");
         intent.putExtra(TEST_EXTRA_NAME, new Intent(TEST_ACTION));
+        // Not an intent, don't count.
         intent.putExtra(TEST_EXTRA_NAME + "2", 1);
+        ArrayList<Intent> intents = new ArrayList<>();
+        intents.add(new Intent(TEST_ACTION));
+        intent.putParcelableArrayListExtra(TEST_EXTRA_NAME + "3", intents);
+        intent.setClipData(ClipData.newIntent("label", new Intent(TEST_ACTION)));
 
         intent.collectExtraIntentKeys();
         final Parcel parcel = Parcel.obtain();
@@ -68,7 +77,7 @@
 
         assertEquals(intent.getAction(), target.getAction());
         assertEquals(intent.getExtraIntentKeys(), target.getExtraIntentKeys());
-        assertThat(intent.getExtraIntentKeys()).hasSize(1);
+        assertThat(intent.getExtraIntentKeys()).hasSize(3);
     }
 
     @Test
@@ -87,13 +96,37 @@
     @RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
     public void testCollectExtraIntentKeys() {
         Intent intent = new Intent(TEST_ACTION);
-        Intent extraIntent = new Intent(TEST_ACTION, TEST_URI);
-        intent.putExtra(TEST_EXTRA_NAME, extraIntent);
+
+        Intent[] intents = new Intent[10];
+        for (int i = 0; i < intents.length; i++) {
+            intents[i] = new Intent("action" + i);
+        }
+        Intent[] intents2 = new Intent[2]; // intents[6-7]
+        System.arraycopy(intents, 6, intents2, 0, intents2.length);
+        ArrayList<Intent> intents3 = new ArrayList<>(2);
+        intents3.addAll(Arrays.asList(intents).subList(8, 10)); // intents[8-9]
+        intent.putExtra("key1", intents[0]);
+        intent.putExtra("array-key", intents2);
+        intent.setClipData(ClipData.newIntent("label2", intents[1]));
+        intent.putExtra("intkey", 1);
+        intents[0].putExtra("key3", intents[2]);
+        intents[0].setClipData(ClipData.newIntent("label4", intents[3]));
+        intents[0].putParcelableArrayListExtra("array-list-key", intents3);
+        intents[1].putExtra("key3", intents[4]);
+        intents[1].setClipData(ClipData.newIntent("label4", intents[5]));
+        intents[5].putExtra("intkey", 2);
 
         intent.collectExtraIntentKeys();
 
-        assertThat(intent.getExtraIntentKeys()).hasSize(1);
-        assertThat(intent.getExtraIntentKeys()).contains(TEST_EXTRA_NAME);
+        // collect all actions of nested intents.
+        final List<String> actions = new ArrayList<>();
+        intent.forEachNestedCreatorToken(intent1 -> {
+            actions.add(intent1.getAction());
+        });
+        assertThat(actions).hasSize(10);
+        for (int i = 0; i < intents.length; i++) {
+            assertThat(actions).contains("action" + i);
+        }
     }
 
 }
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index 5a0dacb..9552c88 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -55,9 +55,10 @@
 @RunWith(AndroidJUnit4.class)
 public class DisplayManagerGlobalTest {
 
-    private static final long ALL_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-            | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-            | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+    private static final long ALL_DISPLAY_EVENTS =
+            DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
 
     @Mock
     private IDisplayManager mDisplayManager;
@@ -127,19 +128,22 @@
 
         int displayId = 1;
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED, null);
+                ALL_DISPLAY_EVENTS
+                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mListener);
 
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null);
+                ALL_DISPLAY_EVENTS
+                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mListener);
 
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                ALL_DISPLAY_EVENTS & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null);
+                ALL_DISPLAY_EVENTS
+                        & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, null);
         callback.onDisplayEvent(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
         waitForHandler();
         Mockito.verifyZeroInteractions(mListener);
@@ -162,22 +166,25 @@
     public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
             throws RemoteException {
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS, null);
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED,
+                null);
         InOrder inOrder = Mockito.inOrder(mDisplayManager);
 
         inOrder.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(),
-                        eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                        eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED));
 
         mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
         inOrder.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(),
-                        eq(ALL_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                        eq(ALL_DISPLAY_EVENTS
+                                | DisplayManagerGlobal
+                                .INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED));
 
         mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
         inOrder.verify(mDisplayManager)
                 .registerCallbackWithEventMask(mCallbackCaptor.capture(),
-                        eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                        eq(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED));
 
         mDisplayManagerGlobal.unregisterDisplayListener(mListener);
         inOrder.verify(mDisplayManager)
@@ -196,10 +203,12 @@
 
         // One listener listens on add/remove, and the other one listens on change.
         mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
-                DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED, null /* packageName */);
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                        | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
+                null /* packageName */);
         mDisplayManagerGlobal.registerDisplayListener(mListener2, mHandler,
-                DisplayManager.EVENT_FLAG_DISPLAY_CHANGED, null /* packageName */);
+                DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED,
+                null /* packageName */);
 
         mDisplayManagerGlobal.handleDisplayChangeFromWindowManager(321);
         waitForHandler();
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
new file mode 100644
index 0000000..a6de611
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/display/DisplayTopologyTest.kt
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.display
+
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP
+import android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT
+import android.view.Display
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class DisplayTopologyTest {
+    private var topology = DisplayTopology()
+
+    @Test
+    fun addOneDisplay() {
+        val displayId = 1
+        val width = 800f
+        val height = 600f
+
+        topology.addDisplay(displayId, width, height)
+
+        assertThat(topology.primaryDisplayId).isEqualTo(displayId)
+
+        val display = topology.root!!
+        assertThat(display.displayId).isEqualTo(displayId)
+        assertThat(display.width).isEqualTo(width)
+        assertThat(display.height).isEqualTo(height)
+        assertThat(display.children).isEmpty()
+    }
+
+    @Test
+    fun addTwoDisplays() {
+        val displayId1 = 1
+        val width1 = 800f
+        val height1 = 600f
+
+        val displayId2 = 2
+        val width2 = 1000f
+        val height2 = 1500f
+
+        topology.addDisplay(displayId1, width1, height1)
+        topology.addDisplay(displayId2, width2, height2)
+
+        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+
+        val display1 = topology.root!!
+        assertThat(display1.displayId).isEqualTo(displayId1)
+        assertThat(display1.width).isEqualTo(width1)
+        assertThat(display1.height).isEqualTo(height1)
+        assertThat(display1.children).hasSize(1)
+
+        val display2 = display1.children[0]
+        assertThat(display2.displayId).isEqualTo(displayId2)
+        assertThat(display2.width).isEqualTo(width2)
+        assertThat(display2.height).isEqualTo(height2)
+        assertThat(display2.children).isEmpty()
+        assertThat(display2.position).isEqualTo(POSITION_TOP)
+        assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+    }
+
+    @Test
+    fun addManyDisplays() {
+        val displayId1 = 1
+        val width1 = 800f
+        val height1 = 600f
+
+        val displayId2 = 2
+        val width2 = 1000f
+        val height2 = 1500f
+
+        topology.addDisplay(displayId1, width1, height1)
+        topology.addDisplay(displayId2, width2, height2)
+
+        val noOfDisplays = 30
+        for (i in 3..noOfDisplays) {
+            topology.addDisplay(/* displayId= */ i, width1, height1)
+        }
+
+        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+
+        val display1 = topology.root!!
+        assertThat(display1.displayId).isEqualTo(displayId1)
+        assertThat(display1.width).isEqualTo(width1)
+        assertThat(display1.height).isEqualTo(height1)
+        assertThat(display1.children).hasSize(1)
+
+        val display2 = display1.children[0]
+        assertThat(display2.displayId).isEqualTo(displayId2)
+        assertThat(display2.width).isEqualTo(width2)
+        assertThat(display2.height).isEqualTo(height2)
+        assertThat(display2.children).hasSize(1)
+        assertThat(display2.position).isEqualTo(POSITION_TOP)
+        assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+
+        var display = display2
+        for (i in 3..noOfDisplays) {
+            display = display.children[0]
+            assertThat(display.displayId).isEqualTo(i)
+            assertThat(display.width).isEqualTo(width1)
+            assertThat(display.height).isEqualTo(height1)
+            // The last display should have no children
+            assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
+            assertThat(display.position).isEqualTo(POSITION_RIGHT)
+            assertThat(display.offset).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun removeDisplays() {
+        val displayId1 = 1
+        val width1 = 800f
+        val height1 = 600f
+
+        val displayId2 = 2
+        val width2 = 1000f
+        val height2 = 1500f
+
+        topology.addDisplay(displayId1, width1, height1)
+        topology.addDisplay(displayId2, width2, height2)
+
+        val noOfDisplays = 30
+        for (i in 3..noOfDisplays) {
+            topology.addDisplay(/* displayId= */ i, width1, height1)
+        }
+
+        var removedDisplays = arrayOf(20)
+        topology.removeDisplay(20)
+
+        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+
+        var display1 = topology.root!!
+        assertThat(display1.displayId).isEqualTo(displayId1)
+        assertThat(display1.width).isEqualTo(width1)
+        assertThat(display1.height).isEqualTo(height1)
+        assertThat(display1.children).hasSize(1)
+
+        var display2 = display1.children[0]
+        assertThat(display2.displayId).isEqualTo(displayId2)
+        assertThat(display2.width).isEqualTo(width2)
+        assertThat(display2.height).isEqualTo(height2)
+        assertThat(display2.children).hasSize(1)
+        assertThat(display2.position).isEqualTo(POSITION_TOP)
+        assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+
+        var display = display2
+        for (i in 3..noOfDisplays) {
+            if (i in removedDisplays) {
+                continue
+            }
+            display = display.children[0]
+            assertThat(display.displayId).isEqualTo(i)
+            assertThat(display.width).isEqualTo(width1)
+            assertThat(display.height).isEqualTo(height1)
+            // The last display should have no children
+            assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
+            assertThat(display.position).isEqualTo(POSITION_RIGHT)
+            assertThat(display.offset).isEqualTo(0)
+        }
+
+        topology.removeDisplay(22)
+        removedDisplays += 22
+        topology.removeDisplay(23)
+        removedDisplays += 23
+        topology.removeDisplay(25)
+        removedDisplays += 25
+
+        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+
+        display1 = topology.root!!
+        assertThat(display1.displayId).isEqualTo(displayId1)
+        assertThat(display1.width).isEqualTo(width1)
+        assertThat(display1.height).isEqualTo(height1)
+        assertThat(display1.children).hasSize(1)
+
+        display2 = display1.children[0]
+        assertThat(display2.displayId).isEqualTo(displayId2)
+        assertThat(display2.width).isEqualTo(width2)
+        assertThat(display2.height).isEqualTo(height2)
+        assertThat(display2.children).hasSize(1)
+        assertThat(display2.position).isEqualTo(POSITION_TOP)
+        assertThat(display2.offset).isEqualTo(width1 / 2 - width2 / 2)
+
+        display = display2
+        for (i in 3..noOfDisplays) {
+            if (i in removedDisplays) {
+                continue
+            }
+            display = display.children[0]
+            assertThat(display.displayId).isEqualTo(i)
+            assertThat(display.width).isEqualTo(width1)
+            assertThat(display.height).isEqualTo(height1)
+            // The last display should have no children
+            assertThat(display.children).hasSize(if (i < noOfDisplays) 1 else 0)
+            assertThat(display.position).isEqualTo(POSITION_RIGHT)
+            assertThat(display.offset).isEqualTo(0)
+        }
+    }
+
+    @Test
+    fun removeAllDisplays() {
+        val displayId = 1
+        val width = 800f
+        val height = 600f
+
+        topology.addDisplay(displayId, width, height)
+        topology.removeDisplay(displayId)
+
+        assertThat(topology.primaryDisplayId).isEqualTo(Display.INVALID_DISPLAY)
+        assertThat(topology.root).isNull()
+    }
+
+    @Test
+    fun removeDisplayThatDoesNotExist() {
+        val displayId = 1
+        val width = 800f
+        val height = 600f
+
+        topology.addDisplay(displayId, width, height)
+        topology.removeDisplay(3)
+
+        assertThat(topology.primaryDisplayId).isEqualTo(displayId)
+
+        val display = topology.root!!
+        assertThat(display.displayId).isEqualTo(displayId)
+        assertThat(display.width).isEqualTo(width)
+        assertThat(display.height).isEqualTo(height)
+        assertThat(display.children).isEmpty()
+    }
+
+    @Test
+    fun removePrimaryDisplay() {
+        val displayId1 = 1
+        val displayId2 = 2
+        val width = 800f
+        val height = 600f
+
+        topology = DisplayTopology(/* root= */ null, displayId2)
+        topology.addDisplay(displayId1, width, height)
+        topology.addDisplay(displayId2, width, height)
+        topology.removeDisplay(displayId2)
+
+        assertThat(topology.primaryDisplayId).isEqualTo(displayId1)
+        val display = topology.root!!
+        assertThat(display.displayId).isEqualTo(displayId1)
+        assertThat(display.width).isEqualTo(width)
+        assertThat(display.height).isEqualTo(height)
+        assertThat(display.children).isEmpty()
+    }
+
+    @Test
+    fun normalization_noOverlaps_leavesTopologyUnchanged() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+        display1.addChild(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+        display1.addChild(display3)
+
+        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display2.addChild(display4)
+
+        topology = DisplayTopology(display1, primaryDisplayId)
+        topology.normalize()
+
+        assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = topology.root!!
+        assertThat(actualDisplay1.displayId).isEqualTo(1)
+        assertThat(actualDisplay1.width).isEqualTo(200f)
+        assertThat(actualDisplay1.height).isEqualTo(600f)
+        assertThat(actualDisplay1.children).hasSize(2)
+
+        val actualDisplay2 = actualDisplay1.children[0]
+        assertThat(actualDisplay2.displayId).isEqualTo(2)
+        assertThat(actualDisplay2.width).isEqualTo(600f)
+        assertThat(actualDisplay2.height).isEqualTo(200f)
+        assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay2.offset).isEqualTo(0f)
+        assertThat(actualDisplay2.children).hasSize(1)
+
+        val actualDisplay3 = actualDisplay1.children[1]
+        assertThat(actualDisplay3.displayId).isEqualTo(3)
+        assertThat(actualDisplay3.width).isEqualTo(600f)
+        assertThat(actualDisplay3.height).isEqualTo(200f)
+        assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay3.offset).isEqualTo(400f)
+        assertThat(actualDisplay3.children).isEmpty()
+
+        val actualDisplay4 = actualDisplay2.children[0]
+        assertThat(actualDisplay4.displayId).isEqualTo(4)
+        assertThat(actualDisplay4.width).isEqualTo(200f)
+        assertThat(actualDisplay4.height).isEqualTo(600f)
+        assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay4.offset).isEqualTo(0f)
+        assertThat(actualDisplay4.children).isEmpty()
+    }
+
+    @Test
+    fun normalization_moveDisplayWithoutReparenting() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display1.addChild(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
+        display1.addChild(display3)
+
+        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display2.addChild(display4)
+
+        topology = DisplayTopology(display1, primaryDisplayId)
+        // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent.
+        topology.normalize()
+
+        assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = topology.root!!
+        assertThat(actualDisplay1.displayId).isEqualTo(1)
+        assertThat(actualDisplay1.width).isEqualTo(200f)
+        assertThat(actualDisplay1.height).isEqualTo(600f)
+        assertThat(actualDisplay1.children).hasSize(1)
+
+        val actualDisplay2 = actualDisplay1.children[0]
+        assertThat(actualDisplay2.displayId).isEqualTo(2)
+        assertThat(actualDisplay2.width).isEqualTo(200f)
+        assertThat(actualDisplay2.height).isEqualTo(600f)
+        assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay2.offset).isEqualTo(0f)
+        assertThat(actualDisplay2.children).hasSize(2)
+
+        val actualDisplay3 = actualDisplay2.children[1]
+        assertThat(actualDisplay3.displayId).isEqualTo(3)
+        assertThat(actualDisplay3.width).isEqualTo(600f)
+        assertThat(actualDisplay3.height).isEqualTo(200f)
+        assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay3.offset).isEqualTo(10f)
+        assertThat(actualDisplay3.children).isEmpty()
+
+        val actualDisplay4 = actualDisplay2.children[0]
+        assertThat(actualDisplay4.displayId).isEqualTo(4)
+        assertThat(actualDisplay4.width).isEqualTo(200f)
+        assertThat(actualDisplay4.height).isEqualTo(600f)
+        assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay4.offset).isEqualTo(210f)
+        assertThat(actualDisplay4.children).isEmpty()
+    }
+
+    @Test
+    fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 50f, /* position= */ 0, /* offset= */ 0f)
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
+        display1.addChild(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
+        display1.addChild(display3)
+
+        topology = DisplayTopology(display1, primaryDisplayId)
+        // Display 3 gets moved and its left side is still on the same line as the right side
+        // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2
+        // becomes its new parent.
+        topology.normalize()
+
+        assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = topology.root!!
+        assertThat(actualDisplay1.displayId).isEqualTo(1)
+        assertThat(actualDisplay1.width).isEqualTo(200f)
+        assertThat(actualDisplay1.height).isEqualTo(50f)
+        assertThat(actualDisplay1.children).hasSize(1)
+
+        val actualDisplay2 = actualDisplay1.children[0]
+        assertThat(actualDisplay2.displayId).isEqualTo(2)
+        assertThat(actualDisplay2.width).isEqualTo(600f)
+        assertThat(actualDisplay2.height).isEqualTo(200f)
+        assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay2.offset).isEqualTo(0f)
+        assertThat(actualDisplay2.children).hasSize(1)
+
+        val actualDisplay3 = actualDisplay2.children[0]
+        assertThat(actualDisplay3.displayId).isEqualTo(3)
+        assertThat(actualDisplay3.width).isEqualTo(600f)
+        assertThat(actualDisplay3.height).isEqualTo(200f)
+        assertThat(actualDisplay3.position).isEqualTo(POSITION_BOTTOM)
+        assertThat(actualDisplay3.offset).isEqualTo(0f)
+        assertThat(actualDisplay3.children).isEmpty()
+    }
+
+    @Test
+    fun normalization_moveAndReparentDisplay() {
+        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
+            /* height= */ 600f, /* position= */ 0, /* offset= */ 0f)
+
+        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display1.addChild(display2)
+
+        val primaryDisplayId = 3
+        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
+            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
+        display1.addChild(display3)
+
+        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
+            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
+        display2.addChild(display4)
+
+        topology = DisplayTopology(display1, primaryDisplayId)
+        topology.normalize()
+
+        assertThat(topology.primaryDisplayId).isEqualTo(primaryDisplayId)
+
+        val actualDisplay1 = topology.root!!
+        assertThat(actualDisplay1.displayId).isEqualTo(1)
+        assertThat(actualDisplay1.width).isEqualTo(200f)
+        assertThat(actualDisplay1.height).isEqualTo(600f)
+        assertThat(actualDisplay1.children).hasSize(1)
+
+        val actualDisplay2 = actualDisplay1.children[0]
+        assertThat(actualDisplay2.displayId).isEqualTo(2)
+        assertThat(actualDisplay2.width).isEqualTo(200f)
+        assertThat(actualDisplay2.height).isEqualTo(600f)
+        assertThat(actualDisplay2.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay2.offset).isEqualTo(0f)
+        assertThat(actualDisplay2.children).hasSize(1)
+
+        val actualDisplay3 = actualDisplay2.children[0]
+        assertThat(actualDisplay3.displayId).isEqualTo(3)
+        assertThat(actualDisplay3.width).isEqualTo(600f)
+        assertThat(actualDisplay3.height).isEqualTo(200f)
+        assertThat(actualDisplay3.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay3.offset).isEqualTo(400f)
+        assertThat(actualDisplay3.children).hasSize(1)
+
+        val actualDisplay4 = actualDisplay3.children[0]
+        assertThat(actualDisplay4.displayId).isEqualTo(4)
+        assertThat(actualDisplay4.width).isEqualTo(200f)
+        assertThat(actualDisplay4.height).isEqualTo(600f)
+        assertThat(actualDisplay4.position).isEqualTo(POSITION_RIGHT)
+        assertThat(actualDisplay4.offset).isEqualTo(-400f)
+        assertThat(actualDisplay4.children).isEmpty()
+    }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/DisplayInfoTest.java b/core/tests/coretests/src/android/view/DisplayInfoTest.java
index 4c5b7e5..8932cf1 100644
--- a/core/tests/coretests/src/android/view/DisplayInfoTest.java
+++ b/core/tests/coretests/src/android/view/DisplayInfoTest.java
@@ -78,6 +78,23 @@
     }
 
     @Test
+    public void testRefreshRateOverride_keepsDisplyInfosEqualWhenOverrideIsSame() {
+        Display.Mode mode = new Display.Mode(
+                /*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120);
+        DisplayInfo displayInfo1 = new DisplayInfo();
+        setSupportedMode(displayInfo1, mode);
+        displayInfo1.renderFrameRate = 60;
+        displayInfo1.refreshRateOverride = 30;
+
+        DisplayInfo displayInfo2 = new DisplayInfo();
+        setSupportedMode(displayInfo2, mode);
+        displayInfo2.renderFrameRate = 30;
+        displayInfo2.refreshRateOverride = 30;
+
+        assertTrue(displayInfo1.equals(displayInfo2));
+    }
+
+    @Test
     public void testRefreshRateOverride_makeDisplayInfosDifferent() {
         Display.Mode mode = new Display.Mode(
                 /*modeId=*/1, /*width=*/1000, /*height=*/1000, /*refreshRate=*/120);
diff --git a/core/tests/vibrator/src/android/os/VibratorTest.java b/core/tests/vibrator/src/android/os/VibratorTest.java
index 6210a00..09bfadb 100644
--- a/core/tests/vibrator/src/android/os/VibratorTest.java
+++ b/core/tests/vibrator/src/android/os/VibratorTest.java
@@ -110,8 +110,9 @@
 
     @Test
     public void onVibratorStateChanged_noVibrator_registersNoListenerToVibratorManager() {
+        int[] vibratorIds = new int[0];
         VibratorManager mockVibratorManager = mock(VibratorManager.class);
-        when(mockVibratorManager.getVibratorIds()).thenReturn(new int[0]);
+        when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds);
 
         Vibrator.OnVibratorStateChangedListener mockListener =
                 mock(Vibrator.OnVibratorStateChangedListener.class);
@@ -119,7 +120,7 @@
                 new SystemVibrator.MultiVibratorStateListener(
                         mTestLooper.getNewExecutor(), mockListener);
 
-        multiVibratorListener.register(mockVibratorManager);
+        multiVibratorListener.register(mockVibratorManager, vibratorIds);
 
         // Never tries to register a listener to an individual vibrator.
         assertFalse(multiVibratorListener.hasRegisteredListeners());
@@ -128,8 +129,9 @@
 
     @Test
     public void onVibratorStateChanged_singleVibrator_forwardsAllCallbacks() {
+        int[] vibratorIds = new int[] { 1 };
         VibratorManager mockVibratorManager = mock(VibratorManager.class);
-        when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1 });
+        when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds);
         when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
 
         Vibrator.OnVibratorStateChangedListener mockListener =
@@ -138,7 +140,7 @@
                 new SystemVibrator.MultiVibratorStateListener(
                         mTestLooper.getNewExecutor(), mockListener);
 
-        multiVibratorListener.register(mockVibratorManager);
+        multiVibratorListener.register(mockVibratorManager, vibratorIds);
         assertTrue(multiVibratorListener.hasRegisteredListeners());
 
         multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
@@ -156,8 +158,9 @@
 
     @Test
     public void onVibratorStateChanged_multipleVibrators_triggersOnlyWhenAllVibratorsInitialized() {
+        int[] vibratorIds = new int[] { 1, 2 };
         VibratorManager mockVibratorManager = mock(VibratorManager.class);
-        when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 });
+        when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds);
         when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
 
         Vibrator.OnVibratorStateChangedListener mockListener =
@@ -166,7 +169,7 @@
                 new SystemVibrator.MultiVibratorStateListener(
                         mTestLooper.getNewExecutor(), mockListener);
 
-        multiVibratorListener.register(mockVibratorManager);
+        multiVibratorListener.register(mockVibratorManager, vibratorIds);
         assertTrue(multiVibratorListener.hasRegisteredListeners());
 
         multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false);
@@ -181,8 +184,9 @@
 
     @Test
     public void onVibratorStateChanged_multipleVibrators_stateChangeIsDeduped() {
+        int[] vibratorIds = new int[] { 1, 2 };
         VibratorManager mockVibratorManager = mock(VibratorManager.class);
-        when(mockVibratorManager.getVibratorIds()).thenReturn(new int[] { 1, 2 });
+        when(mockVibratorManager.getVibratorIds()).thenReturn(vibratorIds);
         when(mockVibratorManager.getVibrator(anyInt())).thenReturn(NullVibrator.getInstance());
 
         Vibrator.OnVibratorStateChangedListener mockListener =
@@ -191,7 +195,7 @@
                 new SystemVibrator.MultiVibratorStateListener(
                         mTestLooper.getNewExecutor(), mockListener);
 
-        multiVibratorListener.register(mockVibratorManager);
+        multiVibratorListener.register(mockVibratorManager, vibratorIds);
         assertTrue(multiVibratorListener.hasRegisteredListeners());
 
         multiVibratorListener.onVibrating(/* vibratorIdx= */ 0, /* vibrating= */ false); // none
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 7b96699..857df10 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -213,6 +213,8 @@
     <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="gpu_service" />
     <assign-permission name="android.permission.REGISTER_STATS_PULL_ATOM" uid="keystore" />
 
+    <assign-permission name="android.permission.DYNAMIC_INSTRUMENTATION" uid="uprobestats" />
+
     <split-permission name="android.permission.ACCESS_FINE_LOCATION">
         <new-permission name="android.permission.ACCESS_COARSE_LOCATION" />
     </split-permission>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 56e55df..7ced809 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -402,6 +402,9 @@
         <permission name="android.permission.SHOW_CUSTOMIZED_RESOLVER"/>
         <!-- Permission required for access VIBRATOR_STATE. -->
         <permission name="android.permission.ACCESS_VIBRATOR_STATE"/>
+        <!-- Permission required for vendor vibration effects and sessions. -->
+        <permission name="android.permission.VIBRATE_VENDOR_EFFECTS"/>
+        <permission name="android.permission.START_VIBRATION_SESSIONS"/>
         <!-- Permission required for UsageStatsTest CTS test. -->
         <permission name="android.permission.MANAGE_NOTIFICATIONS"/>
         <!-- Permission required for CompanionDeviceManager CTS test. -->
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index f8d3bff..2b0802b 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -171,7 +171,7 @@
 # key 149 "KEY_PROG2"
 key 150   EXPLORER
 # key 151 "KEY_MSDOS"
-key 152   POWER
+key 152   LOCK
 # key 153 "KEY_DIRECTION"
 # key 154 "KEY_CYCLEWINDOWS"
 key 155   ENVELOPE
@@ -200,20 +200,20 @@
 key 178   PAGE_DOWN
 key 179   NUMPAD_LEFT_PAREN
 key 180   NUMPAD_RIGHT_PAREN
-# key 181 "KEY_NEW"
+key 181   NEW
 # key 182 "KEY_REDO"
-# key 183   F13
-# key 184   F14
-# key 185   F15
-# key 186   F16
-# key 187   F17
-# key 188   F18
-# key 189   F19
-# key 190   F20
-# key 191   F21
-# key 192   F22
-# key 193   F23
-# key 194   F24
+key 183   F13
+key 184   F14
+key 185   F15
+key 186   F16
+key 187   F17
+key 188   F18
+key 189   F19
+key 190   F20
+key 191   F21
+key 192   F22
+key 193   F23
+key 194   F24
 # key 195 (undefined)
 # key 196 (undefined)
 # key 197 (undefined)
@@ -225,11 +225,11 @@
 # key 203 "KEY_PROG4"
 key 204 NOTIFICATION
 # key 205 "KEY_SUSPEND"
-# key 206 "KEY_CLOSE"
+key 206   CLOSE
 key 207   MEDIA_PLAY
 key 208   MEDIA_FAST_FORWARD
 # key 209 "KEY_BASSBOOST"
-# key 210 "KEY_PRINT"
+key 210   PRINT
 # key 211 "KEY_HP"
 key 212   CAMERA
 key 213   MUSIC
@@ -328,7 +328,7 @@
 # key 369 "KEY_TITLE"
 key 370   CAPTIONS
 # key 371 "KEY_ANGLE"
-# key 372 "KEY_ZOOM"
+key 372   FULLSCREEN
 # key 373 "KEY_MODE"
 # key 374 "KEY_KEYBOARD"
 # key 375 "KEY_SCREEN"
@@ -425,12 +425,15 @@
 # Linux KEY_ASSISTANT
 key 583   ASSIST
 key 585   EMOJI_PICKER
+key 586   DICTATE
 key 656   MACRO_1
 key 657   MACRO_2
 key 658   MACRO_3
 key 659   MACRO_4
 
 # Keys defined by HID usages
+key usage 0x010082 LOCK                      FALLBACK_USAGE_MAPPING
+key usage 0x01009B DO_NOT_DISTURB            FALLBACK_USAGE_MAPPING
 key usage 0x0c0065 SCREENSHOT                FALLBACK_USAGE_MAPPING
 key usage 0x0c0067 WINDOW                    FALLBACK_USAGE_MAPPING
 key usage 0x0c006F BRIGHTNESS_UP             FALLBACK_USAGE_MAPPING
@@ -438,12 +441,17 @@
 key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP     FALLBACK_USAGE_MAPPING
 key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN   FALLBACK_USAGE_MAPPING
 key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING
+key usage 0x0c00D8 DICTATE                   FALLBACK_USAGE_MAPPING
 key usage 0x0c00D9 EMOJI_PICKER              FALLBACK_USAGE_MAPPING
 key usage 0x0c0173 MEDIA_AUDIO_TRACK         FALLBACK_USAGE_MAPPING
 key usage 0x0c019C PROFILE_SWITCH            FALLBACK_USAGE_MAPPING
 key usage 0x0c019F SETTINGS                  FALLBACK_USAGE_MAPPING
 key usage 0x0c01A2 ALL_APPS                  FALLBACK_USAGE_MAPPING
+key usage 0x0c0201 NEW                       FALLBACK_USAGE_MAPPING
+key usage 0x0c0203 CLOSE                     FALLBACK_USAGE_MAPPING
+key usage 0x0c0208 PRINT                     FALLBACK_USAGE_MAPPING
 key usage 0x0c0227 REFRESH                   FALLBACK_USAGE_MAPPING
+key usage 0x0c0232 FULLSCREEN                FALLBACK_USAGE_MAPPING
 key usage 0x0c029D LANGUAGE_SWITCH           FALLBACK_USAGE_MAPPING
 key usage 0x0c029F RECENT_APPS               FALLBACK_USAGE_MAPPING
 key usage 0x0c02A2 ALL_APPS                  FALLBACK_USAGE_MAPPING
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4c47de0..d55a71e 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1761,7 +1761,7 @@
 
         if (Flags.displayBt2020Colorspace()) {
             sNamedColorSpaceMap.put(Named.DISPLAY_BT2020.ordinal(), new ColorSpace.Rgb(
-                    "BT 2020",
+                    "Display BT. 2020",
                     BT2020_PRIMARIES,
                     ILLUMINANT_D65,
                     null,
diff --git a/keystore/java/android/security/keystore/KeyStoreManager.java b/keystore/java/android/security/keystore/KeyStoreManager.java
index 197aaba..e6091c1 100644
--- a/keystore/java/android/security/keystore/KeyStoreManager.java
+++ b/keystore/java/android/security/keystore/KeyStoreManager.java
@@ -49,7 +49,7 @@
  */
 @FlaggedApi(android.security.Flags.FLAG_KEYSTORE_GRANT_API)
 @SystemService(Context.KEYSTORE_SERVICE)
-public class KeyStoreManager {
+public final class KeyStoreManager {
     private static final String TAG = "KeyStoreManager";
 
     private static final Object sInstanceLock = new Object();
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
index 501bedd..c2755ef 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_expanded_view.xml
@@ -19,6 +19,7 @@
     android:layout_height="wrap_content"
     android:layout_width="wrap_content"
     android:orientation="vertical"
+    android:clipChildren="false"
     android:id="@+id/bubble_expanded_view">
 
     <com.android.wm.shell.bubbles.bar.BubbleBarHandleView
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
index f1ecde4..7aca921 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_menu_view.xml
@@ -14,20 +14,18 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.wm.shell.bubbles.bar.BubbleBarMenuView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
+<com.android.wm.shell.bubbles.bar.BubbleBarMenuView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:layout_gravity="center_horizontal"
+    android:clipToPadding="false"
     android:minWidth="@dimen/bubble_bar_manage_menu_min_width"
     android:orientation="vertical"
-    android:elevation="@dimen/bubble_manage_menu_elevation"
-    android:paddingTop="@dimen/bubble_bar_manage_menu_padding_top"
-    android:paddingHorizontal="@dimen/bubble_bar_manage_menu_padding"
-    android:paddingBottom="@dimen/bubble_bar_manage_menu_padding"
-    android:clipToPadding="false">
+    android:visibility="invisible"
+    tools:visibility="visible">
 
     <LinearLayout
         android:id="@+id/bubble_bar_manage_menu_bubble_section"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index b9a3050..c92a278 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -21,6 +21,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
+import static android.window.BackEvent.EDGE_NONE;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -533,7 +534,15 @@
 
         if (keyAction == MotionEvent.ACTION_DOWN) {
             if (!mBackGestureStarted) {
-                mShouldStartOnNextMoveEvent = true;
+                if (swipeEdge == EDGE_NONE) {
+                    // start animation immediately for non-gestural sources (without ACTION_MOVE
+                    // events)
+                    mThresholdCrossed = true;
+                    onGestureStarted(touchX, touchY, swipeEdge);
+                    mShouldStartOnNextMoveEvent = false;
+                } else {
+                    mShouldStartOnNextMoveEvent = true;
+                }
             }
         } else if (keyAction == MotionEvent.ACTION_MOVE) {
             if (!mBackGestureStarted && mShouldStartOnNextMoveEvent) {
@@ -1074,6 +1083,11 @@
             mCurrentTracker.updateStartLocation();
             BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]);
             dispatchOnBackStarted(mActiveCallback, startEvent);
+            // TODO(b/373544911): onBackStarted is dispatched here so that
+            //  WindowOnBackInvokedDispatcher knows about the back navigation and intercepts touch
+            //  events while it's active. It would be cleaner and safer to disable multitouch
+            //  altogether (same as in gesture-nav).
+            dispatchOnBackStarted(mBackNavigationInfo.getOnBackInvokedCallback(), startEvent);
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index 2a50e4d0..272dfec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -222,7 +222,7 @@
 
             mHandleView.setAccessibilityDelegate(new HandleViewAccessibilityDelegate());
         }
-        mMenuViewController = new BubbleBarMenuViewController(mContext, this);
+        mMenuViewController = new BubbleBarMenuViewController(mContext, mHandleView, this);
         mMenuViewController.setListener(new BubbleBarMenuViewController.Listener() {
             @Override
             public void onMenuVisibilityChanged(boolean visible) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
index e781c07..712e41b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java
@@ -17,17 +17,18 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.Path;
-import android.graphics.RectF;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewOutlineProvider;
 
 import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
+import androidx.core.animation.IntProperty;
 import androidx.core.content.ContextCompat;
 
 import com.android.wm.shell.R;
@@ -37,14 +38,33 @@
  */
 public class BubbleBarHandleView extends View {
     private static final long COLOR_CHANGE_DURATION = 120;
-    // Path used to draw the dots
-    private final Path mPath = new Path();
 
+    /** Custom property to set handle color. */
+    private static final IntProperty<BubbleBarHandleView> HANDLE_COLOR = new IntProperty<>(
+            "handleColor") {
+        @Override
+        public void setValue(BubbleBarHandleView bubbleBarHandleView, int color) {
+            bubbleBarHandleView.setHandleColor(color);
+        }
+
+        @Override
+        public Integer get(BubbleBarHandleView bubbleBarHandleView) {
+            return bubbleBarHandleView.getHandleColor();
+        }
+    };
+
+    @VisibleForTesting
+    final Paint mHandlePaint = new Paint();
     private final @ColorInt int mHandleLightColor;
     private final @ColorInt int mHandleDarkColor;
-    private @ColorInt int mCurrentColor;
+    private final ArgbEvaluator mArgbEvaluator = ArgbEvaluator.getInstance();
+    private final float mHandleHeight;
+    private final float mHandleWidth;
+    private float mCurrentHandleHeight;
+    private float mCurrentHandleWidth;
     @Nullable
     private ObjectAnimator mColorChangeAnim;
+    private @ColorInt int mRegionSamplerColor;
 
     public BubbleBarHandleView(Context context) {
         this(context, null /* attrs */);
@@ -61,30 +81,52 @@
     public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        final int handleHeight = getResources().getDimensionPixelSize(
+        mHandlePaint.setFlags(Paint.ANTI_ALIAS_FLAG);
+        mHandlePaint.setStyle(Paint.Style.FILL);
+        mHandlePaint.setColor(0);
+        mHandleHeight = getResources().getDimensionPixelSize(
                 R.dimen.bubble_bar_expanded_view_handle_height);
+        mHandleWidth = getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_caption_width);
         mHandleLightColor = ContextCompat.getColor(getContext(),
                 R.color.bubble_bar_expanded_view_handle_light);
         mHandleDarkColor = ContextCompat.getColor(getContext(),
                 R.color.bubble_bar_expanded_view_handle_dark);
-
-        setClipToOutline(true);
-        setOutlineProvider(new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                final int handleCenterY = view.getHeight() / 2;
-                final int handleTop = handleCenterY - handleHeight / 2;
-                final int handleBottom = handleTop + handleHeight;
-                final int radius = handleHeight / 2;
-                RectF handle = new RectF(/* left = */ 0, handleTop, view.getWidth(), handleBottom);
-                mPath.reset();
-                mPath.addRoundRect(handle, radius, radius, Path.Direction.CW);
-                outline.setPath(mPath);
-            }
-        });
+        mCurrentHandleHeight = mHandleHeight;
+        mCurrentHandleWidth = mHandleWidth;
         setContentDescription(getResources().getString(R.string.handle_text));
     }
 
+    private void setHandleColor(int color) {
+        mHandlePaint.setColor(color);
+        invalidate();
+    }
+
+    private int getHandleColor() {
+        return mHandlePaint.getColor();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        float handleLeft = (getWidth() - mCurrentHandleWidth) / 2;
+        float handleRight = handleLeft + mCurrentHandleWidth;
+        float handleCenterY = (float) getHeight() / 2;
+        float handleTop = (int) (handleCenterY - mCurrentHandleHeight / 2);
+        float handleBottom = handleTop + mCurrentHandleHeight;
+        float cornerRadius = mCurrentHandleHeight / 2;
+        canvas.drawRoundRect(handleLeft, handleTop, handleRight, handleBottom, cornerRadius,
+                cornerRadius, mHandlePaint);
+    }
+
+    /** Sets handle width, height and color. Does not change the layout properties */
+    private void setHandleProperties(float width, float height, int color) {
+        mCurrentHandleHeight = height;
+        mCurrentHandleWidth = width;
+        mHandlePaint.setColor(color);
+        invalidate();
+    }
+
     /**
      * Updates the handle color.
      *
@@ -94,15 +136,15 @@
      */
     public void updateHandleColor(boolean isRegionDark, boolean animated) {
         int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
-        if (newColor == mCurrentColor) {
+        if (newColor == mRegionSamplerColor) {
             return;
         }
+        mRegionSamplerColor = newColor;
         if (mColorChangeAnim != null) {
             mColorChangeAnim.cancel();
         }
-        mCurrentColor = newColor;
         if (animated) {
-            mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
+            mColorChangeAnim = ObjectAnimator.ofArgb(this, HANDLE_COLOR, newColor);
             mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -112,7 +154,39 @@
             mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
             mColorChangeAnim.start();
         } else {
-            setBackgroundColor(newColor);
+            setHandleColor(newColor);
         }
     }
+
+    /** Returns handle padding top. */
+    public int getHandlePaddingTop() {
+        return (getHeight() - getResources().getDimensionPixelSize(
+                R.dimen.bubble_bar_expanded_view_handle_height)) / 2;
+    }
+
+    /** Animates handle for the bubble menu. */
+    public void animateHandleForMenu(float progress, float widthDelta, float heightDelta,
+            int menuColor) {
+        float currentWidth = mHandleWidth + widthDelta * progress;
+        float currentHeight = mHandleHeight + heightDelta * progress;
+        int color = (int) mArgbEvaluator.evaluate(progress, mRegionSamplerColor, menuColor);
+        setHandleProperties(currentWidth, currentHeight, color);
+        setTranslationY(heightDelta * progress / 2);
+    }
+
+    /** Restores all the properties that were animated to the default values. */
+    public void restoreAnimationDefaults() {
+        setHandleProperties(mHandleWidth, mHandleHeight, mRegionSamplerColor);
+        setTranslationY(0);
+    }
+
+    /** Returns the handle height. */
+    public int getHandleHeight() {
+        return (int) mHandleHeight;
+    }
+
+    /** Returns the handle width. */
+    public int getHandleWidth() {
+        return (int) mHandleWidth;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
index 0ee20ef..99e2009 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java
@@ -47,6 +47,10 @@
     private ImageView mBubbleIconView;
     private ImageView mBubbleDismissIconView;
     private TextView mBubbleTitleView;
+    // The animation has three stages. Each stage transition lasts until the animation ends. In
+    // stage 1, the title item content fades in. In stage 2, the background of the option items
+    // fades in. In stage 3, the option item content fades in.
+    private static final int SHOW_MENU_STAGES_COUNT = 3;
 
     public BubbleBarMenuView(Context context) {
         this(context, null /* attrs */);
@@ -97,6 +101,35 @@
         }
     }
 
+    /** Animates the menu from the specified start scale. */
+    public void animateFromStartScale(float currentScale, float progress) {
+        int menuItemElevation = getResources().getDimensionPixelSize(
+                R.dimen.bubble_manage_menu_elevation);
+        setScaleX(currentScale);
+        setScaleY(currentScale);
+        setAlphaForTitleViews(progress);
+        mBubbleSectionView.setElevation(menuItemElevation * progress);
+        float actionsBackgroundAlpha = Math.max(0,
+                (progress - (float) 1 / SHOW_MENU_STAGES_COUNT) * (SHOW_MENU_STAGES_COUNT - 1));
+        float actionItemsAlpha = Math.max(0,
+                (progress - (float) 2 / SHOW_MENU_STAGES_COUNT) * SHOW_MENU_STAGES_COUNT);
+        mActionsSectionView.setAlpha(actionsBackgroundAlpha);
+        mActionsSectionView.setElevation(menuItemElevation * actionsBackgroundAlpha);
+        setMenuItemViewsAlpha(actionItemsAlpha);
+    }
+
+    private void setAlphaForTitleViews(float alpha) {
+        mBubbleIconView.setAlpha(alpha);
+        mBubbleTitleView.setAlpha(alpha);
+        mBubbleDismissIconView.setAlpha(alpha);
+    }
+
+    private void setMenuItemViewsAlpha(float alpha) {
+        for (int i = mActionsSectionView.getChildCount() - 1; i >= 0; i--) {
+            mActionsSectionView.getChildAt(i).setAlpha(alpha);
+        }
+    }
+
     /** Update menu details with bubble info */
     void updateInfo(Bubble bubble) {
         if (bubble.getIcon() != null) {
@@ -153,6 +186,11 @@
         return mBubbleSectionView.getAlpha();
     }
 
+    /** Return title menu item height. */
+    public float getTitleItemHeight() {
+        return mBubbleSectionView.getHeight();
+    }
+
     /**
      * Menu action details used to create menu items
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
index 5148107..9dd0cae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java
@@ -15,6 +15,9 @@
  */
 package com.android.wm.shell.bubbles.bar;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -26,13 +29,10 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
+import com.android.app.animation.Interpolators;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.shared.animation.PhysicsAnimator;
 
 import java.util.ArrayList;
 
@@ -40,22 +40,26 @@
  * Manages bubble bar expanded view menu presentation and animations
  */
 class BubbleBarMenuViewController {
-    private static final float MENU_INITIAL_SCALE = 0.5f;
+
+    private static final float WIDTH_SWAP_FRACTION = 0.4F;
+    private static final long MENU_ANIMATION_DURATION = 600;
+
     private final Context mContext;
     private final ViewGroup mRootView;
+    private final BubbleBarHandleView mHandleView;
     private @Nullable Listener mListener;
     private @Nullable Bubble mBubble;
     private @Nullable BubbleBarMenuView mMenuView;
     /** A transparent view used to intercept touches to collapse menu when presented */
     private @Nullable View mScrimView;
-    private @Nullable PhysicsAnimator<BubbleBarMenuView> mMenuAnimator;
-    private PhysicsAnimator.SpringConfig mMenuSpringConfig;
+    private @Nullable ValueAnimator mMenuAnimator;
 
-    BubbleBarMenuViewController(Context context, ViewGroup rootView) {
+
+    BubbleBarMenuViewController(Context context, BubbleBarHandleView handleView,
+            ViewGroup rootView) {
         mContext = context;
         mRootView = rootView;
-        mMenuSpringConfig = new PhysicsAnimator.SpringConfig(
-                SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+        mHandleView = handleView;
     }
 
     /** Tells if the menu is visible or being animated */
@@ -81,20 +85,21 @@
         if (mMenuView == null || mScrimView == null) {
             setupMenu();
         }
-        cancelAnimations();
-        mMenuView.setVisibility(View.VISIBLE);
-        mScrimView.setVisibility(View.VISIBLE);
-        Runnable endActions = () -> {
-            mMenuView.getChildAt(0).requestAccessibilityFocus();
-            if (mListener != null) {
-                mListener.onMenuVisibilityChanged(true /* isShown */);
+        runOnMenuIsMeasured(() -> {
+            mMenuView.setVisibility(View.VISIBLE);
+            mScrimView.setVisibility(View.VISIBLE);
+            Runnable endActions = () -> {
+                mMenuView.getChildAt(0).requestAccessibilityFocus();
+                if (mListener != null) {
+                    mListener.onMenuVisibilityChanged(true /* isShown */);
+                }
+            };
+            if (animated) {
+                animateTransition(true /* show */, endActions);
+            } else {
+                endActions.run();
             }
-        };
-        if (animated) {
-            animateTransition(true /* show */, endActions);
-        } else {
-            endActions.run();
-        }
+        });
     }
 
     /**
@@ -103,18 +108,30 @@
      */
     void hideMenu(boolean animated) {
         if (mMenuView == null || mScrimView == null) return;
-        cancelAnimations();
-        Runnable endActions = () -> {
-            mMenuView.setVisibility(View.GONE);
-            mScrimView.setVisibility(View.GONE);
-            if (mListener != null) {
-                mListener.onMenuVisibilityChanged(false /* isShown */);
+        runOnMenuIsMeasured(() -> {
+            Runnable endActions = () -> {
+                mHandleView.restoreAnimationDefaults();
+                mMenuView.setVisibility(View.GONE);
+                mScrimView.setVisibility(View.GONE);
+                mHandleView.setVisibility(View.VISIBLE);
+                if (mListener != null) {
+                    mListener.onMenuVisibilityChanged(false /* isShown */);
+                }
+            };
+            if (animated) {
+                animateTransition(false /* show */, endActions);
+            } else {
+                endActions.run();
             }
-        };
-        if (animated) {
-            animateTransition(false /* show */, endActions);
+        });
+    }
+
+    private void runOnMenuIsMeasured(Runnable action) {
+        if (mMenuView.getWidth() == 0 || mMenuView.getHeight() == 0) {
+            // the menu view is not yet measured, postpone showing the animation
+            mMenuView.post(() -> runOnMenuIsMeasured(action));
         } else {
-            endActions.run();
+            action.run();
         }
     }
 
@@ -125,24 +142,63 @@
      */
     private void animateTransition(boolean show, Runnable endActions) {
         if (mMenuView == null) return;
-        mMenuAnimator = PhysicsAnimator.getInstance(mMenuView);
-        mMenuAnimator.setDefaultSpringConfig(mMenuSpringConfig);
-        mMenuAnimator
-                .spring(DynamicAnimation.ALPHA, show ? 1f : 0f)
-                .spring(DynamicAnimation.SCALE_Y, show ? 1f : MENU_INITIAL_SCALE)
-                .withEndActions(() -> {
-                    mMenuAnimator = null;
-                    endActions.run();
-                })
-                .start();
+        float startValue = show ? 0 : 1;
+        if (mMenuAnimator != null && mMenuAnimator.isRunning()) {
+            startValue = (float) mMenuAnimator.getAnimatedValue();
+            mMenuAnimator.cancel();
+        }
+        ValueAnimator showMenuAnimation = ValueAnimator.ofFloat(startValue, show ? 1 : 0);
+        showMenuAnimation.setDuration(MENU_ANIMATION_DURATION);
+        showMenuAnimation.setInterpolator(Interpolators.EMPHASIZED);
+        showMenuAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mMenuAnimator = null;
+                endActions.run();
+            }
+        });
+        mMenuAnimator = showMenuAnimation;
+        setupAnimatorListener(showMenuAnimation);
+        showMenuAnimation.start();
     }
 
-    /** Cancel running animations */
-    private void cancelAnimations() {
-        if (mMenuAnimator != null) {
-            mMenuAnimator.cancel();
-            mMenuAnimator = null;
+    /** Setup listener that orchestrates the animation. */
+    private void setupAnimatorListener(ValueAnimator showMenuAnimation) {
+        // Getting views properties start values
+        int widthDiff = mMenuView.getWidth() - mHandleView.getHandleWidth();
+        int handleHeight = mHandleView.getHandleHeight();
+        float targetWidth = mHandleView.getHandleWidth() + widthDiff * WIDTH_SWAP_FRACTION;
+        float targetHeight = targetWidth * mMenuView.getTitleItemHeight() / mMenuView.getWidth();
+        int menuColor;
+        try (TypedArray ta = mContext.obtainStyledAttributes(new int[]{
+                com.android.internal.R.attr.materialColorSurfaceBright,
+        })) {
+            menuColor = ta.getColor(0, Color.WHITE);
         }
+        // Calculating deltas
+        float swapScale = targetWidth / mMenuView.getWidth();
+        float handleWidthDelta = targetWidth - mHandleView.getHandleWidth();
+        float handleHeightDelta = targetHeight - handleHeight;
+        // Setting update listener that will orchestrate the animation
+        showMenuAnimation.addUpdateListener(animator -> {
+            float animationProgress = (float) animator.getAnimatedValue();
+            boolean showHandle = animationProgress <= WIDTH_SWAP_FRACTION;
+            mHandleView.setVisibility(showHandle ? View.VISIBLE : View.GONE);
+            mMenuView.setVisibility(showHandle ? View.GONE : View.VISIBLE);
+            if (showHandle) {
+                float handleAnimationProgress = animationProgress / WIDTH_SWAP_FRACTION;
+                mHandleView.animateHandleForMenu(handleAnimationProgress, handleWidthDelta,
+                        handleHeightDelta, menuColor);
+            } else {
+                mMenuView.setTranslationY(mHandleView.getHandlePaddingTop());
+                mMenuView.setPivotY(0);
+                mMenuView.setPivotX((float) mMenuView.getWidth() / 2);
+                float menuAnimationProgress =
+                        (animationProgress - WIDTH_SWAP_FRACTION) / (1 - WIDTH_SWAP_FRACTION);
+                float currentMenuScale = swapScale + (1 - swapScale) * menuAnimationProgress;
+                mMenuView.animateFromStartScale(currentMenuScale, menuAnimationProgress);
+            }
+        });
     }
 
     /** Sets up and inflate menu views */
@@ -150,9 +206,6 @@
         // Menu view setup
         mMenuView = (BubbleBarMenuView) LayoutInflater.from(mContext).inflate(
                 R.layout.bubble_bar_menu_view, mRootView, false);
-        mMenuView.setAlpha(0f);
-        mMenuView.setPivotY(0f);
-        mMenuView.setScaleY(MENU_INITIAL_SCALE);
         mMenuView.setOnCloseListener(() -> hideMenu(true  /* animated */));
         if (mBubble != null) {
             mMenuView.updateInfo(mBubble);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index cefcb75..01c680d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -205,11 +205,6 @@
         finishTransaction: SurfaceControl.Transaction,
         finishCallback: TransitionFinishCallback,
     ): Boolean {
-        val launchChange = findDesktopTaskChange(info, pending.launchingTask)
-        if (launchChange == null) {
-            logV("No launch Change, returning")
-            return false
-        }
         // Check if there's also an immersive change during this launch.
         val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
             findDesktopTaskChange(info, exitingTask)
@@ -217,6 +212,13 @@
         val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
             findDesktopTaskChange(info, minimizingTask)
         }
+        val launchChange = findDesktopTaskChange(info, pending.launchingTask)
+        if (launchChange == null) {
+            check(minimizeChange == null)
+            check(immersiveExitChange == null)
+            logV("No launch Change, returning")
+            return false
+        }
 
         var subAnimationCount = -1
         var combinedWct: WindowContainerTransaction? = null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 162879c..927fd88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1770,9 +1770,13 @@
         transition: IBinder,
         taskIdToMinimize: Int,
     ) {
-        val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return
+        val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
         desktopTasksLimiter.ifPresent {
-            it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId)
+            it.addPendingMinimizeChange(
+                transition = transition,
+                displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
+                taskId = taskIdToMinimize
+            )
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index b27c428..0154d04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -45,12 +45,17 @@
     }
 
     /**
-     * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed.
+     * Set the callback when isInPip state is changed.
      *
-     * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()}
-     *                 when it's changed.
+     * @param callback The callback accepts the state of isInPip when it's changed.
      */
-    default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {}
+    default void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {}
+
+    /**
+     * Remove the callback when isInPip state is changed.
+     * @param callback The callback accepts the state of isInPip when it's changed.
+     */
+    default void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {}
 
     /**
      * Called when showing Pip menu.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 7f61186..588b887 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -104,6 +104,7 @@
 import com.android.wm.shell.transition.Transitions;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -215,7 +216,7 @@
 
     private boolean mIsKeyguardShowingOrAnimating;
 
-    private Consumer<Boolean> mOnIsInPipStateChangedListener;
+    private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
 
     @VisibleForTesting
     interface PipAnimationListener {
@@ -501,11 +502,11 @@
                     false /* saveRestoreSnapFraction */);
         });
         mPipTransitionState.addOnPipTransitionStateChangedListener((oldState, newState) -> {
-            if (mOnIsInPipStateChangedListener != null) {
-                final boolean wasInPip = PipTransitionState.isInPip(oldState);
-                final boolean nowInPip = PipTransitionState.isInPip(newState);
-                if (nowInPip != wasInPip) {
-                    mOnIsInPipStateChangedListener.accept(nowInPip);
+            final boolean wasInPip = PipTransitionState.isInPip(oldState);
+            final boolean nowInPip = PipTransitionState.isInPip(newState);
+            if (nowInPip != wasInPip) {
+                for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
+                    listener.accept(nowInPip);
                 }
             }
         });
@@ -960,13 +961,19 @@
         mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
     }
 
-    private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
-        mOnIsInPipStateChangedListener = callback;
-        if (mOnIsInPipStateChangedListener != null) {
+    private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+        if (callback != null) {
+            mOnIsInPipStateChangedListeners.add(callback);
             callback.accept(mPipTransitionState.isInPip());
         }
     }
 
+    private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+        if (callback != null) {
+            mOnIsInPipStateChangedListeners.remove(callback);
+        }
+    }
+
     private void setShelfHeightLocked(boolean visible, int height) {
         final int shelfHeight = visible ? height : 0;
         mPipBoundsState.setShelfVisibility(visible, shelfHeight);
@@ -1222,9 +1229,16 @@
         }
 
         @Override
-        public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+        public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
             mMainExecutor.execute(() -> {
-                PipController.this.setOnIsInPipStateChangedListener(callback);
+                PipController.this.addOnIsInPipStateChangedListener(callback);
+            });
+        }
+
+        @Override
+        public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+            mMainExecutor.execute(() -> {
+                PipController.this.removeOnIsInPipStateChangedListener(callback);
             });
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index d3f537b..bc09183 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -21,6 +21,7 @@
 
 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
@@ -66,6 +67,8 @@
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.function.Consumer;
 
 /**
@@ -94,7 +97,7 @@
     private final PipTouchHandler mPipTouchHandler;
     private final ShellExecutor mMainExecutor;
     private final PipImpl mImpl;
-    private Consumer<Boolean> mOnIsInPipStateChangedListener;
+    private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
 
     // Wrapper for making Binder calls into PiP animation listener hosted in launcher's Recents.
     private PipAnimationListener mPipRecentsAnimationListener;
@@ -413,13 +416,13 @@
                 if (mPipTransitionState.isInSwipePipToHomeTransition()) {
                     mPipTransitionState.resetSwipePipToHomeState();
                 }
-                if (mOnIsInPipStateChangedListener != null) {
-                    mOnIsInPipStateChangedListener.accept(true /* inPip */);
+                for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
+                    listener.accept(true /* inPip */);
                 }
                 break;
             case PipTransitionState.EXITED_PIP:
-                if (mOnIsInPipStateChangedListener != null) {
-                    mOnIsInPipStateChangedListener.accept(false /* inPip */);
+                for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
+                    listener.accept(false /* inPip */);
                 }
                 break;
         }
@@ -451,13 +454,19 @@
         mPipTransitionState.dump(pw, innerPrefix);
     }
 
-    private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
-        mOnIsInPipStateChangedListener = callback;
-        if (mOnIsInPipStateChangedListener != null) {
+    private void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+        if (callback != null) {
+            mOnIsInPipStateChangedListeners.add(callback);
             callback.accept(mPipTransitionState.isInPip());
         }
     }
 
+    private void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+        if (callback != null) {
+            mOnIsInPipStateChangedListeners.remove(callback);
+        }
+    }
+
     private void setLauncherAppIconSize(int iconSizePx) {
         mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx);
     }
@@ -473,9 +482,16 @@
         public void onSystemUiStateChanged(boolean isSysUiStateValid, long flag) {}
 
         @Override
-        public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+        public void addOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
             mMainExecutor.execute(() -> {
-                PipController.this.setOnIsInPipStateChangedListener(callback);
+                PipController.this.addOnIsInPipStateChangedListener(callback);
+            });
+        }
+
+        @Override
+        public void removeOnIsInPipStateChangedListener(@NonNull Consumer<Boolean> callback) {
+            mMainExecutor.execute(() -> {
+                PipController.this.removeOnIsInPipStateChangedListener(callback);
             });
         }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
index d38b848..329a109 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleViewTest.java
@@ -16,9 +16,8 @@
 package com.android.wm.shell.bubbles.bar;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 
-import android.graphics.drawable.ColorDrawable;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -47,10 +46,9 @@
     public void testUpdateHandleColor_lightBg() {
         mHandleView.updateHandleColor(false /* isRegionDark */, false /* animated */);
 
-        assertTrue(mHandleView.getClipToOutline());
-        assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
-        ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
-        assertEquals(bgDrawable.getColor(),
+        assertFalse(mHandleView.getClipToOutline());
+        int handleColor = mHandleView.mHandlePaint.getColor();
+        assertEquals(handleColor,
                 ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_dark));
     }
 
@@ -58,10 +56,9 @@
     public void testUpdateHandleColor_darkBg() {
         mHandleView.updateHandleColor(true /* isRegionDark */, false /* animated */);
 
-        assertTrue(mHandleView.getClipToOutline());
-        assertTrue(mHandleView.getBackground() instanceof ColorDrawable);
-        ColorDrawable bgDrawable = (ColorDrawable) mHandleView.getBackground();
-        assertEquals(bgDrawable.getColor(),
+        assertFalse(mHandleView.getClipToOutline());
+        int handleColor = mHandleView.mHandlePaint.getColor();
+        assertEquals(handleColor,
                 ContextCompat.getColor(mContext, R.color.bubble_bar_expanded_view_handle_light));
     }
 }
diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp
index c6cee07..5ab5a7a 100644
--- a/libs/appfunctions/Android.bp
+++ b/libs/appfunctions/Android.bp
@@ -18,10 +18,10 @@
 }
 
 java_sdk_library {
-    name: "com.google.android.appfunctions.sidecar",
+    name: "com.android.extensions.appfunctions",
     owner: "google",
     srcs: ["java/**/*.java"],
-    api_packages: ["com.google.android.appfunctions.sidecar"],
+    api_packages: ["com.android.extensions.appfunctions"],
     dex_preopt: {
         enabled: false,
     },
@@ -31,9 +31,9 @@
 }
 
 prebuilt_etc {
-    name: "appfunctions.sidecar.xml",
+    name: "appfunctions.extension.xml",
     system_ext_specific: true,
     sub_dir: "permissions",
-    src: "appfunctions.sidecar.xml",
+    src: "appfunctions.extension.xml",
     filename_from_src: true,
 }
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index faf84a8..f56667d 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -1,9 +1,29 @@
 // Signature format: 2.0
-package com.google.android.appfunctions.sidecar {
+package com.android.extensions.appfunctions {
+
+  public final class AppFunctionException extends java.lang.Exception {
+    ctor public AppFunctionException(int, @Nullable String);
+    ctor public AppFunctionException(int, @Nullable String, @NonNull android.os.Bundle);
+    method public int getErrorCategory();
+    method public int getErrorCode();
+    method @Nullable public String getErrorMessage();
+    method @NonNull public android.os.Bundle getExtras();
+    field public static final int ERROR_APP_UNKNOWN_ERROR = 3000; // 0xbb8
+    field public static final int ERROR_CANCELLED = 2001; // 0x7d1
+    field public static final int ERROR_CATEGORY_APP = 3; // 0x3
+    field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
+    field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
+    field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
+    field public static final int ERROR_DENIED = 1000; // 0x3e8
+    field public static final int ERROR_DISABLED = 1002; // 0x3ea
+    field public static final int ERROR_FUNCTION_NOT_FOUND = 1003; // 0x3eb
+    field public static final int ERROR_INVALID_ARGUMENT = 1001; // 0x3e9
+    field public static final int ERROR_SYSTEM_ERROR = 2000; // 0x7d0
+  }
 
   public final class AppFunctionManager {
     ctor public AppFunctionManager(android.content.Context);
-    method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+    method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void executeAppFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
     method @RequiresPermission(anyOf={android.Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, android.Manifest.permission.EXECUTE_APP_FUNCTIONS}, conditional=true) public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void isAppFunctionEnabled(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
     method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>);
@@ -15,7 +35,7 @@
   public abstract class AppFunctionService extends android.app.Service {
     ctor public AppFunctionService();
     method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>);
+    method @MainThread public abstract void onExecuteFunction(@NonNull com.android.extensions.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<com.android.extensions.appfunctions.ExecuteAppFunctionResponse,com.android.extensions.appfunctions.AppFunctionException>);
     field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
     field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService";
   }
@@ -29,33 +49,17 @@
 
   public static final class ExecuteAppFunctionRequest.Builder {
     ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String);
-    method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build();
-    method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
-    method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
+    method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest build();
+    method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
+    method @NonNull public com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
   }
 
   public final class ExecuteAppFunctionResponse {
-    method public int getErrorCategory();
-    method @Nullable public String getErrorMessage();
+    ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument);
+    ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
     method @NonNull public android.os.Bundle getExtras();
-    method public int getResultCode();
     method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
-    method public boolean isSuccess();
-    method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle);
-    method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle);
-    field public static final int ERROR_CATEGORY_APP = 3; // 0x3
-    field public static final int ERROR_CATEGORY_REQUEST_ERROR = 1; // 0x1
-    field public static final int ERROR_CATEGORY_SYSTEM = 2; // 0x2
-    field public static final int ERROR_CATEGORY_UNKNOWN = 0; // 0x0
-    field public static final String PROPERTY_RETURN_VALUE = "returnValue";
-    field public static final int RESULT_APP_UNKNOWN_ERROR = 3000; // 0xbb8
-    field public static final int RESULT_CANCELLED = 2001; // 0x7d1
-    field public static final int RESULT_DENIED = 1000; // 0x3e8
-    field public static final int RESULT_DISABLED = 1002; // 0x3ea
-    field public static final int RESULT_FUNCTION_NOT_FOUND = 1003; // 0x3eb
-    field public static final int RESULT_INVALID_ARGUMENT = 1001; // 0x3e9
-    field public static final int RESULT_OK = 0; // 0x0
-    field public static final int RESULT_SYSTEM_ERROR = 2000; // 0x7d0
+    field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
   }
 
 }
diff --git a/libs/appfunctions/appfunctions.sidecar.xml b/libs/appfunctions/appfunctions.extension.xml
similarity index 83%
rename from libs/appfunctions/appfunctions.sidecar.xml
rename to libs/appfunctions/appfunctions.extension.xml
index bef8b6e..dd09cc3 100644
--- a/libs/appfunctions/appfunctions.sidecar.xml
+++ b/libs/appfunctions/appfunctions.extension.xml
@@ -16,6 +16,6 @@
   -->
 <permissions>
     <library
-        name="com.google.android.appfunctions.sidecar"
-        file="/system_ext/framework/com.google.android.appfunctions.sidecar.jar"/>
+        name="com.android.extensions.appfunctions"
+        file="/system_ext/framework/com.android.extensions.appfunctions.jar"/>
 </permissions>
\ No newline at end of file
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
new file mode 100644
index 0000000..28c3b3d
--- /dev/null
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionException.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.extensions.appfunctions;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Represents an app function related errors. */
+public final class AppFunctionException extends Exception {
+    /**
+     * The caller does not have the permission to execute an app function.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int ERROR_DENIED = 1000;
+
+    /**
+     * The caller supplied invalid arguments to the execution request.
+     *
+     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int ERROR_INVALID_ARGUMENT = 1001;
+
+    /**
+     * The caller tried to execute a disabled app function.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int ERROR_DISABLED = 1002;
+
+    /**
+     * The caller tried to execute a function that does not exist.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
+     */
+    public static final int ERROR_FUNCTION_NOT_FOUND = 1003;
+
+    /**
+     * An internal unexpected error coming from the system.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int ERROR_SYSTEM_ERROR = 2000;
+
+    /**
+     * The operation was cancelled. Use this error code to report that a cancellation is done after
+     * receiving a cancellation signal.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
+     */
+    public static final int ERROR_CANCELLED = 2001;
+
+    /**
+     * An unknown error occurred while processing the call in the AppFunctionService.
+     *
+     * <p>This error is thrown when the service is connected in the remote application but an
+     * unexpected error is thrown from the bound application.
+     *
+     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
+     */
+    public static final int ERROR_APP_UNKNOWN_ERROR = 3000;
+
+    /**
+     * The error category is unknown.
+     *
+     * <p>This is the default value for {@link #getErrorCategory}.
+     */
+    public static final int ERROR_CATEGORY_UNKNOWN = 0;
+
+    /**
+     * The error is caused by the app requesting a function execution.
+     *
+     * <p>For example, the caller provided invalid parameters in the execution request e.g. an
+     * invalid function ID.
+     *
+     * <p>Errors in the category fall in the range 1000-1999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
+
+    /**
+     * The error is caused by an issue in the system.
+     *
+     * <p>For example, the AppFunctionService implementation is not found by the system.
+     *
+     * <p>Errors in the category fall in the range 2000-2999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_SYSTEM = 2;
+
+    /**
+     * The error is caused by the app providing the function.
+     *
+     * <p>For example, the app crashed when the system is executing the request.
+     *
+     * <p>Errors in the category fall in the range 3000-3999 inclusive.
+     */
+    public static final int ERROR_CATEGORY_APP = 3;
+
+    private final int mErrorCode;
+    @Nullable private final String mErrorMessage;
+    @NonNull private final Bundle mExtras;
+
+    public AppFunctionException(int errorCode, @Nullable String errorMessage) {
+        this(errorCode, errorMessage, Bundle.EMPTY);
+    }
+
+    public AppFunctionException(
+            int errorCode, @Nullable String errorMessage, @NonNull Bundle extras) {
+        mErrorCode = errorCode;
+        mErrorMessage = errorMessage;
+        mExtras = extras;
+    }
+
+    /** Returns one of the {@code ERROR} constants. */
+    @ErrorCode
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /** Returns the error message. */
+    @Nullable
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    /**
+     * Returns the error category.
+     *
+     * <p>This method categorizes errors based on their underlying cause, allowing developers to
+     * implement targeted error handling and provide more informative error messages to users. It
+     * maps ranges of error codes to specific error categories.
+     *
+     * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the error code does not belong to
+     * any error category.
+     *
+     * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
+     * error code ranges.
+     */
+    @ErrorCategory
+    public int getErrorCategory() {
+        if (mErrorCode >= 1000 && mErrorCode < 2000) {
+            return ERROR_CATEGORY_REQUEST_ERROR;
+        }
+        if (mErrorCode >= 2000 && mErrorCode < 3000) {
+            return ERROR_CATEGORY_SYSTEM;
+        }
+        if (mErrorCode >= 3000 && mErrorCode < 4000) {
+            return ERROR_CATEGORY_APP;
+        }
+        return ERROR_CATEGORY_UNKNOWN;
+    }
+
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Error codes.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"ERROR_"},
+            value = {
+                ERROR_DENIED,
+                ERROR_APP_UNKNOWN_ERROR,
+                ERROR_FUNCTION_NOT_FOUND,
+                ERROR_SYSTEM_ERROR,
+                ERROR_INVALID_ARGUMENT,
+                ERROR_DISABLED,
+                ERROR_CANCELLED
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCode {}
+
+    /**
+     * Error categories.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"ERROR_CATEGORY_"},
+            value = {
+                ERROR_CATEGORY_UNKNOWN,
+                ERROR_CATEGORY_REQUEST_ERROR,
+                ERROR_CATEGORY_APP,
+                ERROR_CATEGORY_SYSTEM
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ErrorCategory {}
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
similarity index 88%
rename from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
rename to libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
index 2075104..9eb66a3 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionManager.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -31,7 +31,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 
 /**
  * Provides app functions related functionalities.
@@ -115,7 +114,9 @@
             @NonNull ExecuteAppFunctionRequest sidecarRequest,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull CancellationSignal cancellationSignal,
-            @NonNull Consumer<ExecuteAppFunctionResponse> callback) {
+            @NonNull
+                    OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+                            callback) {
         Objects.requireNonNull(sidecarRequest);
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
@@ -126,10 +127,20 @@
                 platformRequest,
                 executor,
                 cancellationSignal,
-                (platformResponse) -> {
-                    callback.accept(
-                            SidecarConverter.getSidecarExecuteAppFunctionResponse(
-                                    platformResponse));
+                new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(
+                            android.app.appfunctions.ExecuteAppFunctionResponse result) {
+                        callback.onResult(
+                                SidecarConverter.getSidecarExecuteAppFunctionResponse(result));
+                    }
+
+                    @Override
+                    public void onError(
+                            android.app.appfunctions.AppFunctionException exception) {
+                        callback.onError(
+                                SidecarConverter.getSidecarAppFunctionException(exception));
+                    }
                 });
     }
 
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
similarity index 79%
rename from libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
rename to libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
index 0dc87e4..55f5791 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/AppFunctionService.java
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
 
-import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE;
+import static com.android.extensions.appfunctions.SidecarConverter.getPlatformAppFunctionException;
+import static com.android.extensions.appfunctions.SidecarConverter.getPlatformExecuteAppFunctionResponse;
 
 import android.annotation.MainThread;
 import android.annotation.NonNull;
@@ -26,9 +27,7 @@
 import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.IBinder;
-import android.util.Log;
-
-import java.util.function.Consumer;
+import android.os.OutcomeReceiver;
 
 /**
  * Abstract base class to provide app functions to the system.
@@ -80,10 +79,18 @@
                                         platformRequest),
                                 callingPackage,
                                 cancellationSignal,
-                                (sidecarResponse) -> {
-                                    callback.accept(
-                                            SidecarConverter.getPlatformExecuteAppFunctionResponse(
-                                                    sidecarResponse));
+                                new OutcomeReceiver<>() {
+                                    @Override
+                                    public void onResult(ExecuteAppFunctionResponse result) {
+                                        callback.onResult(
+                                                getPlatformExecuteAppFunctionResponse(result));
+                                    }
+
+                                    @Override
+                                    public void onError(AppFunctionException exception) {
+                                        callback.onError(
+                                                getPlatformAppFunctionException(exception));
+                                    }
                                 });
                     });
 
@@ -116,12 +123,14 @@
      * @param request The function execution request.
      * @param callingPackage The package name of the app that is requesting the execution.
      * @param cancellationSignal A signal to cancel the execution.
-     * @param callback A callback to report back the result.
+     * @param callback A callback to report back the result or error.
      */
     @MainThread
     public abstract void onExecuteFunction(
             @NonNull ExecuteAppFunctionRequest request,
             @NonNull String callingPackage,
             @NonNull CancellationSignal cancellationSignal,
-            @NonNull Consumer<ExecuteAppFunctionResponse> callback);
+            @NonNull
+                    OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException>
+                            callback);
 }
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
similarity index 96%
rename from libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
rename to libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
index 593c521..baddc24 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionRequest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.android.appfunctions.sidecar;
+package com.android.extensions.appfunctions;
 
 import android.annotation.NonNull;
 import android.app.appsearch.GenericDocument;
@@ -91,8 +91,8 @@
      * Returns the function parameters. The key is the parameter name, and the value is the
      * parameter value.
      *
-     * <p>The bundle may have missing parameters. Developers are advised to implement defensive
-     * handling measures.
+     * <p>The {@link GenericDocument} may have missing parameters. Developers are advised to
+     * implement defensive handling measures.
      *
      * <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be
      * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
new file mode 100644
index 0000000..42c3c03
--- /dev/null
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.extensions.appfunctions;
+
+import android.annotation.NonNull;
+import android.app.appfunctions.AppFunctionManager;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+
+import java.util.Objects;
+
+/** The response to an app function execution. */
+public final class ExecuteAppFunctionResponse {
+    /**
+     * The name of the property that stores the function return value within the {@code
+     * resultDocument}.
+     *
+     * <p>See {@link GenericDocument#getProperty(String)} for more information.
+     *
+     * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will
+     * be empty {@link GenericDocument}.
+     *
+     * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
+     * return {@code null}.
+     *
+     * <p>See {@link #getResultDocument} for more information on extracting the return value.
+     */
+    public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
+
+    /**
+     * Returns the return value of the executed function.
+     *
+     * <p>The return value is stored in a {@link GenericDocument} with the key {@link
+     * #PROPERTY_RETURN_VALUE}.
+     *
+     * <p>See {@link #getResultDocument} for more information on extracting the return value.
+     */
+    @NonNull private final GenericDocument mResultDocument;
+
+    /** Returns the additional metadata data relevant to this function execution response. */
+    @NonNull private final Bundle mExtras;
+
+    /**
+     * @param resultDocument The return value of the executed function.
+     */
+    public ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument) {
+        this(resultDocument, Bundle.EMPTY);
+    }
+
+    /**
+     * @param resultDocument The return value of the executed function.
+     * @param extras The additional metadata for this function execution response.
+     */
+    public ExecuteAppFunctionResponse(
+            @NonNull GenericDocument resultDocument, @NonNull Bundle extras) {
+        mResultDocument = Objects.requireNonNull(resultDocument);
+        mExtras = Objects.requireNonNull(extras);
+    }
+
+    /**
+     * Returns a generic document containing the return value of the executed function.
+     *
+     * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
+     *
+     * <p>Sample code for extracting the return value:
+     *
+     * <pre>
+     *     GenericDocument resultDocument = response.getResultDocument();
+     *     Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
+     *     if (returnValue != null) {
+     *       // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
+     *       // {@link GenericDocument#getPropertyLong} etc.
+     *       // Do something with the returnValue
+     *     }
+     * </pre>
+     *
+     * @see AppFunctionManager on how to determine the expected function return.
+     */
+    @NonNull
+    public GenericDocument getResultDocument() {
+        return mResultDocument;
+    }
+
+    /** Returns the additional metadata for this function execution response. */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+}
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
new file mode 100644
index 0000000..5e1fc7e
--- /dev/null
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/SidecarConverter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.extensions.appfunctions;
+
+import android.annotation.NonNull;
+
+/**
+ * Utility class containing methods to convert Sidecar objects of AppFunctions API into the
+ * underlying platform classes.
+ *
+ * @hide
+ */
+public final class SidecarConverter {
+    private SidecarConverter() {}
+
+    /**
+     * Converts sidecar's {@link ExecuteAppFunctionRequest} into platform's {@link
+     * android.app.appfunctions.ExecuteAppFunctionRequest}
+     *
+     * @hide
+     */
+    @NonNull
+    public static android.app.appfunctions.ExecuteAppFunctionRequest
+            getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) {
+        return new android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
+                        request.getTargetPackageName(), request.getFunctionIdentifier())
+                .setExtras(request.getExtras())
+                .setParameters(request.getParameters())
+                .build();
+    }
+
+    /**
+     * Converts sidecar's {@link ExecuteAppFunctionResponse} into platform's {@link
+     * android.app.appfunctions.ExecuteAppFunctionResponse}
+     *
+     * @hide
+     */
+    @NonNull
+    public static android.app.appfunctions.ExecuteAppFunctionResponse
+            getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) {
+        return new android.app.appfunctions.ExecuteAppFunctionResponse(
+                response.getResultDocument(), response.getExtras());
+    }
+
+    /**
+     * Converts sidecar's {@link AppFunctionException} into platform's {@link
+     * android.app.appfunctions.AppFunctionException}
+     *
+     * @hide
+     */
+    @NonNull
+    public static android.app.appfunctions.AppFunctionException
+            getPlatformAppFunctionException(@NonNull AppFunctionException exception) {
+        return new android.app.appfunctions.AppFunctionException(
+                exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras());
+    }
+
+    /**
+     * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} into sidecar's
+     * {@link ExecuteAppFunctionRequest}
+     *
+     * @hide
+     */
+    @NonNull
+    public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest(
+            @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) {
+        return new ExecuteAppFunctionRequest.Builder(
+                        request.getTargetPackageName(), request.getFunctionIdentifier())
+                .setExtras(request.getExtras())
+                .setParameters(request.getParameters())
+                .build();
+    }
+
+    /**
+     * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} into
+     * sidecar's {@link ExecuteAppFunctionResponse}
+     *
+     * @hide
+     */
+    @NonNull
+    public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse(
+            @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) {
+        return new ExecuteAppFunctionResponse(response.getResultDocument(), response.getExtras());
+    }
+
+    /**
+     * Converts platform's {@link android.app.appfunctions.AppFunctionException} into
+     * sidecar's {@link AppFunctionException}
+     *
+     * @hide
+     */
+    @NonNull
+    public static AppFunctionException getSidecarAppFunctionException(
+            @NonNull android.app.appfunctions.AppFunctionException exception) {
+        return new AppFunctionException(
+                exception.getErrorCode(), exception.getErrorMessage(), exception.getExtras());
+    }
+}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
deleted file mode 100644
index 4e88fb0..0000000
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.appfunctions.sidecar;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.appsearch.GenericDocument;
-import android.os.Bundle;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
-
-/**
- * The response to an app function execution.
- *
- * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel
- * functionality and exposes it here as a sidecar library (avoiding direct dependency on the
- * platform API).
- */
-public final class ExecuteAppFunctionResponse {
-    /**
-     * The name of the property that stores the function return value within the {@code
-     * resultDocument}.
-     *
-     * <p>See {@link GenericDocument#getProperty(String)} for more information.
-     *
-     * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will
-     * be empty {@link GenericDocument}.
-     *
-     * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
-     * return {@code null}.
-     *
-     * <p>See {@link #getResultDocument} for more information on extracting the return value.
-     */
-    public static final String PROPERTY_RETURN_VALUE = "returnValue";
-
-    /**
-     * The call was successful.
-     *
-     * <p>This result code does not belong in an error category.
-     */
-    public static final int RESULT_OK = 0;
-
-    /**
-     * The caller does not have the permission to execute an app function.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
-     */
-    public static final int RESULT_DENIED = 1000;
-
-    /**
-     * The caller supplied invalid arguments to the execution request.
-     *
-     * <p>This error may be considered similar to {@link IllegalArgumentException}.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
-     */
-    public static final int RESULT_INVALID_ARGUMENT = 1001;
-
-    /**
-     * The caller tried to execute a disabled app function.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
-     */
-    public static final int RESULT_DISABLED = 1002;
-
-    /**
-     * The caller tried to execute a function that does not exist.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_REQUEST_ERROR} category.
-     */
-    public static final int RESULT_FUNCTION_NOT_FOUND = 1003;
-
-    /**
-     * An internal unexpected error coming from the system.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
-     */
-    public static final int RESULT_SYSTEM_ERROR = 2000;
-
-    /**
-     * The operation was cancelled. Use this error code to report that a cancellation is done after
-     * receiving a cancellation signal.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_SYSTEM} category.
-     */
-    public static final int RESULT_CANCELLED = 2001;
-
-    /**
-     * An unknown error occurred while processing the call in the AppFunctionService.
-     *
-     * <p>This error is thrown when the service is connected in the remote application but an
-     * unexpected error is thrown from the bound application.
-     *
-     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
-     */
-    public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
-
-    /**
-     * The error category is unknown.
-     *
-     * <p>This is the default value for {@link #getErrorCategory}.
-     */
-    public static final int ERROR_CATEGORY_UNKNOWN = 0;
-
-    /**
-     * The error is caused by the app requesting a function execution.
-     *
-     * <p>For example, the caller provided invalid parameters in the execution request e.g. an
-     * invalid function ID.
-     *
-     * <p>Errors in the category fall in the range 1000-1999 inclusive.
-     */
-    public static final int ERROR_CATEGORY_REQUEST_ERROR = 1;
-
-    /**
-     * The error is caused by an issue in the system.
-     *
-     * <p>For example, the AppFunctionService implementation is not found by the system.
-     *
-     * <p>Errors in the category fall in the range 2000-2999 inclusive.
-     */
-    public static final int ERROR_CATEGORY_SYSTEM = 2;
-
-    /**
-     * The error is caused by the app providing the function.
-     *
-     * <p>For example, the app crashed when the system is executing the request.
-     *
-     * <p>Errors in the category fall in the range 3000-3999 inclusive.
-     */
-    public static final int ERROR_CATEGORY_APP = 3;
-
-    /** The result code of the app function execution. */
-    @ResultCode private final int mResultCode;
-
-    /**
-     * The error message associated with the result, if any. This is {@code null} if the result code
-     * is {@link #RESULT_OK}.
-     */
-    @Nullable private final String mErrorMessage;
-
-    /**
-     * Returns the return value of the executed function.
-     *
-     * <p>The return value is stored in a {@link GenericDocument} with the key {@link
-     * #PROPERTY_RETURN_VALUE}.
-     *
-     * <p>See {@link #getResultDocument} for more information on extracting the return value.
-     */
-    @NonNull private final GenericDocument mResultDocument;
-
-    /** Returns the additional metadata data relevant to this function execution response. */
-    @NonNull private final Bundle mExtras;
-
-    private ExecuteAppFunctionResponse(
-            @NonNull GenericDocument resultDocument,
-            @NonNull Bundle extras,
-            @ResultCode int resultCode,
-            @Nullable String errorMessage) {
-        mResultDocument = Objects.requireNonNull(resultDocument);
-        mExtras = Objects.requireNonNull(extras);
-        mResultCode = resultCode;
-        mErrorMessage = errorMessage;
-    }
-
-    /**
-     * Returns result codes from throwable.
-     *
-     * @hide
-     */
-    static @ResultCode int getResultCode(@NonNull Throwable t) {
-        if (t instanceof IllegalArgumentException) {
-            return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
-        }
-        return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR;
-    }
-
-    /**
-     * Returns a successful response.
-     *
-     * @param resultDocument The return value of the executed function.
-     * @param extras The additional metadata data relevant to this function execution response.
-     */
-    @NonNull
-    public static ExecuteAppFunctionResponse newSuccess(
-            @NonNull GenericDocument resultDocument, @Nullable Bundle extras) {
-        Objects.requireNonNull(resultDocument);
-        Bundle actualExtras = getActualExtras(extras);
-
-        return new ExecuteAppFunctionResponse(
-                resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null);
-    }
-
-    /**
-     * Returns a failure response.
-     *
-     * @param resultCode The result code of the app function execution.
-     * @param extras The additional metadata data relevant to this function execution response.
-     * @param errorMessage The error message associated with the result, if any.
-     */
-    @NonNull
-    public static ExecuteAppFunctionResponse newFailure(
-            @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) {
-        if (resultCode == RESULT_OK) {
-            throw new IllegalArgumentException("resultCode must not be RESULT_OK");
-        }
-        Bundle actualExtras = getActualExtras(extras);
-        GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build();
-        return new ExecuteAppFunctionResponse(
-                emptyDocument, actualExtras, resultCode, errorMessage);
-    }
-
-    private static Bundle getActualExtras(@Nullable Bundle extras) {
-        if (extras == null) {
-            return Bundle.EMPTY;
-        }
-        return extras;
-    }
-
-    /**
-     * Returns the error category of the {@link ExecuteAppFunctionResponse}.
-     *
-     * <p>This method categorizes errors based on their underlying cause, allowing developers to
-     * implement targeted error handling and provide more informative error messages to users. It
-     * maps ranges of result codes to specific error categories.
-     *
-     * <p>When constructing a {@link #newFailure} response, use the appropriate result code value to
-     * ensure correct categorization of the failed response.
-     *
-     * <p>This method returns {@code ERROR_CATEGORY_UNKNOWN} if the result code does not belong to
-     * any error category, for example, in the case of a successful result with {@link #RESULT_OK}.
-     *
-     * <p>See {@link ErrorCategory} for a complete list of error categories and their corresponding
-     * result code ranges.
-     */
-    @ErrorCategory
-    public int getErrorCategory() {
-        if (mResultCode >= 1000 && mResultCode < 2000) {
-            return ERROR_CATEGORY_REQUEST_ERROR;
-        }
-        if (mResultCode >= 2000 && mResultCode < 3000) {
-            return ERROR_CATEGORY_SYSTEM;
-        }
-        if (mResultCode >= 3000 && mResultCode < 4000) {
-            return ERROR_CATEGORY_APP;
-        }
-        return ERROR_CATEGORY_UNKNOWN;
-    }
-
-    /**
-     * Returns a generic document containing the return value of the executed function.
-     *
-     * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.
-     *
-     * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
-     * function does not produce a return value.
-     *
-     * <p>Sample code for extracting the return value:
-     *
-     * <pre>
-     *     GenericDocument resultDocument = response.getResultDocument();
-     *     Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
-     *     if (returnValue != null) {
-     *       // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
-     *       // {@link GenericDocument#getPropertyLong} etc.
-     *       // Do something with the returnValue
-     *     }
-     * </pre>
-     */
-    @NonNull
-    public GenericDocument getResultDocument() {
-        return mResultDocument;
-    }
-
-    /** Returns the extras of the app function execution. */
-    @NonNull
-    public Bundle getExtras() {
-        return mExtras;
-    }
-
-    /**
-     * Returns {@code true} if {@link #getResultCode} equals {@link
-     * ExecuteAppFunctionResponse#RESULT_OK}.
-     */
-    public boolean isSuccess() {
-        return getResultCode() == RESULT_OK;
-    }
-
-    /**
-     * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
-     */
-    @ResultCode
-    public int getResultCode() {
-        return mResultCode;
-    }
-
-    /**
-     * Returns the error message associated with this result.
-     *
-     * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
-     */
-    @Nullable
-    public String getErrorMessage() {
-        return mErrorMessage;
-    }
-
-    /**
-     * Result codes.
-     *
-     * @hide
-     */
-    @IntDef(
-            prefix = {"RESULT_"},
-            value = {
-                RESULT_OK,
-                RESULT_DENIED,
-                RESULT_APP_UNKNOWN_ERROR,
-                RESULT_SYSTEM_ERROR,
-                RESULT_FUNCTION_NOT_FOUND,
-                RESULT_INVALID_ARGUMENT,
-                RESULT_DISABLED,
-                RESULT_CANCELLED
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ResultCode {}
-
-    /**
-     * Error categories.
-     *
-     * @hide
-     */
-    @IntDef(
-            prefix = {"ERROR_CATEGORY_"},
-            value = {
-                ERROR_CATEGORY_UNKNOWN,
-                ERROR_CATEGORY_REQUEST_ERROR,
-                ERROR_CATEGORY_APP,
-                ERROR_CATEGORY_SYSTEM
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ErrorCategory {}
-}
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
deleted file mode 100644
index b1b05f7..0000000
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.appfunctions.sidecar;
-
-import android.annotation.NonNull;
-
-/**
- * Utility class containing methods to convert Sidecar objects of AppFunctions API into the
- * underlying platform classes.
- *
- * @hide
- */
-public final class SidecarConverter {
-    private SidecarConverter() {}
-
-    /**
-     * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
-     * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
-     *
-     * @hide
-     */
-    @NonNull
-    public static android.app.appfunctions.ExecuteAppFunctionRequest
-            getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) {
-        return new
-                android.app.appfunctions.ExecuteAppFunctionRequest.Builder(
-                request.getTargetPackageName(),
-                request.getFunctionIdentifier())
-                .setExtras(request.getExtras())
-                .setParameters(request.getParameters())
-                .build();
-    }
-
-    /**
-     * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
-     * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
-     *
-     * @hide
-     */
-    @NonNull
-    public static android.app.appfunctions.ExecuteAppFunctionResponse
-            getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) {
-        if (response.isSuccess()) {
-            return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess(
-                    response.getResultDocument(), response.getExtras());
-        } else {
-            return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure(
-                    response.getResultCode(),
-                    response.getErrorMessage(),
-                    response.getExtras());
-        }
-    }
-
-    /**
-     * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest}
-     * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest}
-     *
-     * @hide
-     */
-    @NonNull
-    public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest(
-            @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) {
-        return new ExecuteAppFunctionRequest.Builder(
-                request.getTargetPackageName(),
-                request.getFunctionIdentifier())
-                .setExtras(request.getExtras())
-                .setParameters(request.getParameters())
-                .build();
-    }
-
-    /**
-     * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse}
-     * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse}
-     *
-     * @hide
-     */
-    @NonNull
-    public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse(
-            @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) {
-        if (response.isSuccess()) {
-            return ExecuteAppFunctionResponse.newSuccess(
-                    response.getResultDocument(), response.getExtras());
-        } else {
-            return ExecuteAppFunctionResponse.newFailure(
-                    response.getResultCode(),
-                    response.getErrorMessage(),
-                    response.getExtras());
-        }
-    }
-}
diff --git a/libs/appfunctions/tests/Android.bp b/libs/appfunctions/tests/Android.bp
index 6f5eff3..db79675 100644
--- a/libs/appfunctions/tests/Android.bp
+++ b/libs/appfunctions/tests/Android.bp
@@ -25,7 +25,7 @@
         "androidx.test.rules",
         "androidx.test.ext.junit",
         "androidx.core_core-ktx",
-        "com.google.android.appfunctions.sidecar.impl",
+        "com.android.extensions.appfunctions.impl",
         "junit",
         "kotlin-test",
         "mockito-target-extended-minus-junit4",
diff --git a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
similarity index 62%
rename from libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
rename to libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
index 264f842..11202d5 100644
--- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
+++ b/libs/appfunctions/tests/src/com/android/extensions/appfunctions/tests/SidecarConverterTest.kt
@@ -14,14 +14,15 @@
  * limitations under the License.
  */
 
-package com.google.android.appfunctions.sidecar.tests
+package com.android.extensions.appfunctions.tests
 
+import android.app.appfunctions.AppFunctionException
 import android.app.appfunctions.ExecuteAppFunctionRequest
 import android.app.appfunctions.ExecuteAppFunctionResponse
 import android.app.appsearch.GenericDocument
 import android.os.Bundle
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.google.android.appfunctions.sidecar.SidecarConverter
+import com.android.extensions.appfunctions.SidecarConverter
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -60,7 +61,7 @@
                 .setPropertyLong("testLong", 23)
                 .build()
         val sidecarRequest =
-            com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder(
+            com.android.extensions.appfunctions.ExecuteAppFunctionRequest.Builder(
                 "targetPkg",
                 "targetFunctionId"
             )
@@ -83,44 +84,38 @@
             GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
                 .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
                 .build()
-        val platformResponse = ExecuteAppFunctionResponse.newSuccess(resultGd, null)
+        val platformResponse = ExecuteAppFunctionResponse(resultGd)
 
         val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
             platformResponse
         )
 
-        assertThat(sidecarResponse.isSuccess).isTrue()
         assertThat(
             sidecarResponse.resultDocument.getProperty(
                 ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
             )
         )
             .isEqualTo(booleanArrayOf(true))
-        assertThat(sidecarResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
-        assertThat(sidecarResponse.errorMessage).isNull()
     }
 
     @Test
-    fun getSidecarExecuteAppFunctionResponse_errorResponse_sameContents() {
-        val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
-        val platformResponse =
-            ExecuteAppFunctionResponse.newFailure(
-                ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
-                null,
-                null
+    fun getSidecarAppFunctionException_sameContents() {
+        val bundle = Bundle()
+        bundle.putString("key", "value")
+        val platformException =
+            AppFunctionException(
+                AppFunctionException.ERROR_SYSTEM_ERROR,
+                "error",
+                bundle
             )
 
-        val sidecarResponse = SidecarConverter.getSidecarExecuteAppFunctionResponse(
-            platformResponse
+        val sidecarException = SidecarConverter.getSidecarAppFunctionException(
+            platformException
         )
 
-        assertThat(sidecarResponse.isSuccess).isFalse()
-        assertThat(sidecarResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
-        assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id)
-        assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
-        assertThat(sidecarResponse.resultCode)
-            .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
-        assertThat(sidecarResponse.errorMessage).isNull()
+        assertThat(sidecarException.errorCode).isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR)
+        assertThat(sidecarException.errorMessage).isEqualTo("error")
+        assertThat(sidecarException.extras.getString("key")).isEqualTo("value")
     }
 
     @Test
@@ -129,44 +124,39 @@
             GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "")
                 .setPropertyBoolean(ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, true)
                 .build()
-        val sidecarResponse = com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse
-            .newSuccess(resultGd, null)
+        val sidecarResponse =
+            com.android.extensions.appfunctions.ExecuteAppFunctionResponse(resultGd)
 
         val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
             sidecarResponse
         )
 
-        assertThat(platformResponse.isSuccess).isTrue()
         assertThat(
             platformResponse.resultDocument.getProperty(
                 ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE
             )
         )
             .isEqualTo(booleanArrayOf(true))
-        assertThat(platformResponse.resultCode).isEqualTo(ExecuteAppFunctionResponse.RESULT_OK)
-        assertThat(platformResponse.errorMessage).isNull()
     }
 
     @Test
-    fun getPlatformExecuteAppFunctionResponse_errorResponse_sameContents() {
-        val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
-        val sidecarResponse =
-            com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure(
-                ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
-                null,
-                null
+    fun getPlatformAppFunctionException_sameContents() {
+        val bundle = Bundle()
+        bundle.putString("key", "value")
+        val sidecarException =
+            com.android.extensions.appfunctions.AppFunctionException(
+                AppFunctionException.ERROR_SYSTEM_ERROR,
+                "error",
+                bundle
             )
 
-        val platformResponse = SidecarConverter.getPlatformExecuteAppFunctionResponse(
-            sidecarResponse
+        val platformException = SidecarConverter.getPlatformAppFunctionException(
+            sidecarException
         )
 
-        assertThat(platformResponse.isSuccess).isFalse()
-        assertThat(platformResponse.resultDocument.namespace).isEqualTo(emptyGd.namespace)
-        assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id)
-        assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
-        assertThat(platformResponse.resultCode)
-            .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
-        assertThat(platformResponse.errorMessage).isNull()
+        assertThat(platformException.errorCode)
+            .isEqualTo(AppFunctionException.ERROR_SYSTEM_ERROR)
+        assertThat(platformException.errorMessage).isEqualTo("error")
+        assertThat(platformException.extras.getString("key")).isEqualTo("value")
     }
 }
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index b08a86e..bd65b2e 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
+import static android.media.codec.Flags.FLAG_NUM_INPUT_SLOTS;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
 import static android.media.codec.Flags.FLAG_APV_SUPPORT;
 
@@ -1777,6 +1778,17 @@
     public static final String KEY_SECURITY_MODEL = "security-model";
 
     /**
+     * A key describing the number of slots used in the codec. When present in input format,
+     * the associated value indicates the number of input slots. The entry is set by the codec
+     * if configured with (@link MediaCodec#CONFIGURE_FLAG_BLOCK_MODEL), and will be ignored if set
+     * by the application.
+     * <p>
+     * The associated value is an integer.
+     */
+    @FlaggedApi(FLAG_NUM_INPUT_SLOTS)
+    public static final String KEY_NUM_SLOTS = "num-slots";
+
+    /**
      * QpOffsetRect constitutes the metadata required for encoding a region of interest in an
      * image or a video frame. The region of interest is represented by a rectangle. The four
      * integer coordinates of the rectangle are stored in fields left, top, right, bottom.
diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java
index c1d73f9..8521d1c 100644
--- a/media/java/android/media/audio/common/AidlConversion.java
+++ b/media/java/android/media/audio/common/AidlConversion.java
@@ -705,6 +705,10 @@
                 aidl.type = AudioDeviceType.OUT_BROADCAST;
                 aidl.connection = AudioDeviceDescription.CONNECTION_BT_LE;
                 break;
+            case AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP:
+                aidl.type = AudioDeviceType.OUT_MULTICHANNEL_GROUP;
+                aidl.connection = AudioDeviceDescription.CONNECTION_VIRTUAL;
+                break;
             case AudioSystem.DEVICE_IN_BUILTIN_MIC:
                 aidl.type = AudioDeviceType.IN_MICROPHONE;
                 break;
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 90b4aba..52a21e2 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -79,7 +79,7 @@
 
 flag {
     name: "update_client_profile_priority"
-    namespace: "media"
+    namespace: "media_solutions"
     description : "Feature flag to add updateResourcePriority api to MediaCas"
     bug: "300565729"
 }
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
index 391eb22..bb782bf 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.java
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -26,6 +26,7 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
+ * Settings for ambient backlight.
  * @hide
  */
 public class AmbientBacklightSettings implements Parcelable {
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index e6c79dd..250d59b 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -30,20 +30,22 @@
  */
 interface IMediaQualityManager {
     PictureProfile createPictureProfile(in PictureProfile pp);
-    void updatePictureProfile(in long id, in PictureProfile pp);
-    void removePictureProfile(in long id);
-    PictureProfile getPictureProfileById(in long id);
+    void updatePictureProfile(in String id, in PictureProfile pp);
+    void removePictureProfile(in String id);
+    PictureProfile getPictureProfile(in int type, in String name);
     List<PictureProfile> getPictureProfilesByPackage(in String packageName);
     List<PictureProfile> getAvailablePictureProfiles();
-    List<PictureProfile> getAllPictureProfiles();
+    List<String> getPictureProfilePackageNames();
+    List<String> getPictureProfileAllowList();
+    void setPictureProfileAllowList(in List<String> packages);
 
     SoundProfile createSoundProfile(in SoundProfile pp);
-    void updateSoundProfile(in long id, in SoundProfile pp);
-    void removeSoundProfile(in long id);
-    SoundProfile getSoundProfileById(in long id);
+    void updateSoundProfile(in String id, in SoundProfile pp);
+    void removeSoundProfile(in String id);
+    SoundProfile getSoundProfileById(in String id);
     List<SoundProfile> getSoundProfilesByPackage(in String packageName);
     List<SoundProfile> getAvailableSoundProfiles();
-    List<SoundProfile> getAllSoundProfiles();
+    List<String> getSoundProfilePackageNames();
 
     void registerPictureProfileCallback(in IPictureProfileCallback cb);
     void registerSoundProfileCallback(in ISoundProfileCallback cb);
diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/IPictureProfileCallback.aidl
index 05441cd..34aa2b0 100644
--- a/media/java/android/media/quality/IPictureProfileCallback.aidl
+++ b/media/java/android/media/quality/IPictureProfileCallback.aidl
@@ -17,6 +17,7 @@
 
 package android.media.quality;
 
+import android.media.quality.ParamCapability;
 import android.media.quality.PictureProfile;
 
 /**
@@ -24,7 +25,9 @@
  * @hide
  */
 oneway interface IPictureProfileCallback {
-    void onPictureProfileAdded(in long id, in PictureProfile p);
-    void onPictureProfileUpdated(in long id, in PictureProfile p);
-    void onPictureProfileRemoved(in long id, in PictureProfile p);
+    void onPictureProfileAdded(in String id, in PictureProfile p);
+    void onPictureProfileUpdated(in String id, in PictureProfile p);
+    void onPictureProfileRemoved(in String id, in PictureProfile p);
+    void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps);
+    void onError(in int err);
 }
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 38a2025..26d83ac 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -19,6 +19,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.media.tv.flags.Flags;
@@ -63,7 +64,7 @@
         mService = service;
         IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
             @Override
-            public void onPictureProfileAdded(long profileId, PictureProfile profile) {
+            public void onPictureProfileAdded(String profileId, PictureProfile profile) {
                 synchronized (mLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
@@ -72,7 +73,7 @@
                 }
             }
             @Override
-            public void onPictureProfileUpdated(long profileId, PictureProfile profile) {
+            public void onPictureProfileUpdated(String profileId, PictureProfile profile) {
                 synchronized (mLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
@@ -81,7 +82,7 @@
                 }
             }
             @Override
-            public void onPictureProfileRemoved(long profileId, PictureProfile profile) {
+            public void onPictureProfileRemoved(String profileId, PictureProfile profile) {
                 synchronized (mLock) {
                     for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
                         // TODO: filter callback record
@@ -89,6 +90,24 @@
                     }
                 }
             }
+            @Override
+            public void onParamCapabilitiesChanged(String profileId, List<ParamCapability> caps) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postParamCapabilitiesChanged(profileId, caps);
+                    }
+                }
+            }
+            @Override
+            public void onError(int err) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postError(err);
+                    }
+                }
+            }
         };
         ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
             @Override
@@ -175,14 +194,17 @@
 
 
     /**
-     * Gets picture profile by given profile ID.
-     * @return the corresponding picture profile if available; {@code null} if the ID doesn't
-     *         exist or the profile is not accessible to the caller.
+     * Gets picture profile by given profile type and name.
+     *
+     * @return the corresponding picture profile if available; {@code null} if the name doesn't
+     *         exist.
      * @hide
      */
-    public PictureProfile getPictureProfileById(long profileId) {
+    @Nullable
+    public PictureProfile getPictureProfile(
+            @PictureProfile.ProfileType int type, @NonNull String name) {
         try {
-            return mService.getPictureProfileById(profileId);
+            return mService.getPictureProfile(type, name);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -190,11 +212,13 @@
 
 
     /**
-     * @SystemApi gets profiles that available to the given package
-     * @hide
+     * Gets profiles that available to the given package.
+     *
+     * @hide @SystemApi
      */
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
-    public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
+    public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) {
         try {
             return mService.getPictureProfilesByPackage(packageName);
         } catch (RemoteException e) {
@@ -215,13 +239,16 @@
     }
 
     /**
-     * @SystemApi all stored picture profiles of all packages
-     * @hide
+     * Gets all package names whose picture profiles are available.
+     *
+     * @see #getPictureProfilesByPackage(String)
+     * @hide @SystemApi
      */
+    @NonNull
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
-    public List<PictureProfile> getAllPictureProfiles() {
+    public List<String> getPictureProfilePackageNames() {
         try {
-            return mService.getAllPictureProfiles();
+            return mService.getPictureProfilePackageNames();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -231,10 +258,12 @@
     /**
      * Creates a picture profile and store it in the system.
      *
-     * @return the stored profile with an assigned profile ID.
+     * @return the stored profile with an assigned profile ID. {@code null} if it's not created
+     * successfully.
      * @hide
      */
-    public PictureProfile createPictureProfile(PictureProfile pp) {
+    @Nullable
+    public PictureProfile createPictureProfile(@NonNull PictureProfile pp) {
         try {
             return mService.createPictureProfile(pp);
         } catch (RemoteException e) {
@@ -247,7 +276,7 @@
      * Updates an existing picture profile and store it in the system.
      * @hide
      */
-    public void updatePictureProfile(long profileId, PictureProfile pp) {
+    public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) {
         try {
             mService.updatePictureProfile(profileId, pp);
         } catch (RemoteException e) {
@@ -260,7 +289,7 @@
      * Removes a picture profile from the system.
      * @hide
      */
-    public void removePictureProfile(long profileId) {
+    public void removePictureProfile(@NonNull String profileId) {
         try {
             mService.removePictureProfile(profileId);
         } catch (RemoteException e) {
@@ -307,7 +336,7 @@
      *         exist or the profile is not accessible to the caller.
      * @hide
      */
-    public SoundProfile getSoundProfileById(long profileId) {
+    public SoundProfile getSoundProfileById(String profileId) {
         try {
             return mService.getSoundProfileById(profileId);
         } catch (RemoteException e) {
@@ -346,9 +375,9 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
-    public List<SoundProfile> getAllSoundProfiles() {
+    public List<String> getSoundProfilePackageNames() {
         try {
-            return mService.getAllSoundProfiles();
+            return mService.getSoundProfilePackageNames();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -374,7 +403,7 @@
      * Updates an existing sound profile and store it in the system.
      * @hide
      */
-    public void updateSoundProfile(long profileId, SoundProfile sp) {
+    public void updateSoundProfile(String profileId, SoundProfile sp) {
         try {
             mService.updateSoundProfile(profileId, sp);
         } catch (RemoteException e) {
@@ -387,7 +416,7 @@
      * Removes a sound profile from the system.
      * @hide
      */
-    public void removeSoundProfile(long profileId) {
+    public void removeSoundProfile(String profileId) {
         try {
             mService.removeSoundProfile(profileId);
         } catch (RemoteException e) {
@@ -399,7 +428,8 @@
      * Gets capability information of the given parameters.
      * @hide
      */
-    public List<ParamCapability> getParamCapabilities(List<String> names) {
+    @NonNull
+    public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) {
         try {
             return mService.getParamCapabilities(names);
         } catch (RemoteException e) {
@@ -408,7 +438,38 @@
     }
 
     /**
+     * Gets the allowlist of packages that can create and removed picture profiles
+     *
+     * @see #createPictureProfile(PictureProfile)
+     * @see #removePictureProfile(String)
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    @NonNull
+    public List<String> getPictureProfileAllowList() {
+        try {
+            return mService.getPictureProfileAllowList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the allowlist of packages that can create and removed picture profiles
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    public void setPictureProfileAllowList(@NonNull List<String> packageNames) {
+        try {
+            mService.setPictureProfileAllowList(packageNames);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
+     * @hide
      */
     public boolean isSupported() {
         try {
@@ -581,7 +642,7 @@
             return mCallback;
         }
 
-        public void postPictureProfileAdded(final long id, PictureProfile profile) {
+        public void postPictureProfileAdded(final String id, PictureProfile profile) {
 
             mExecutor.execute(new Runnable() {
                 @Override
@@ -591,7 +652,7 @@
             });
         }
 
-        public void postPictureProfileUpdated(final long id, PictureProfile profile) {
+        public void postPictureProfileUpdated(final String id, PictureProfile profile) {
             mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
@@ -600,7 +661,7 @@
             });
         }
 
-        public void postPictureProfileRemoved(final long id, PictureProfile profile) {
+        public void postPictureProfileRemoved(final String id, PictureProfile profile) {
             mExecutor.execute(new Runnable() {
                 @Override
                 public void run() {
@@ -608,6 +669,24 @@
                 }
             });
         }
+
+        public void postParamCapabilitiesChanged(final String id, List<ParamCapability> caps) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onParamCapabilitiesChanged(id, caps);
+                }
+            });
+        }
+
+        public void postError(int error) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onError(error);
+                }
+            });
+        }
     }
 
     private static final class SoundProfileCallbackRecord {
@@ -681,24 +760,57 @@
      */
     public abstract static class PictureProfileCallback {
         /**
+         * This is invoked when a picture profile has been added.
+         *
+         * @param profileId the ID of the profile.
+         * @param profile the newly added profile.
          * @hide
          */
-        public void onPictureProfileAdded(long id, PictureProfile profile) {
+        public void onPictureProfileAdded(
+                @NonNull String profileId, @NonNull PictureProfile profile) {
         }
+
         /**
+         * This is invoked when a picture profile has been updated.
+         *
+         * @param profileId the ID of the profile.
+         * @param profile the profile with updated info.
          * @hide
          */
-        public void onPictureProfileUpdated(long id, PictureProfile profile) {
+        public void onPictureProfileUpdated(
+                @NonNull String profileId, @NonNull PictureProfile profile) {
         }
+
         /**
+         * This is invoked when a picture profile has been removed.
+         *
+         * @param profileId the ID of the profile.
+         * @param profile the removed profile.
          * @hide
          */
-        public void onPictureProfileRemoved(long id, PictureProfile profile) {
+        public void onPictureProfileRemoved(
+                @NonNull String profileId, @NonNull PictureProfile profile) {
         }
+
         /**
+         * This is invoked when an issue has occurred.
+         *
+         * @param errorCode the error code
          * @hide
          */
-        public void onError(int errorCode) {
+        public void onError(@PictureProfile.ErrorCode int errorCode) {
+        }
+
+        /**
+         * This is invoked when parameter capabilities has been changed due to status changes of the
+         * content.
+         *
+         * @param profileId the ID of the profile used by the media content.
+         * @param updatedCaps the updated capabilities.
+         * @hide
+         */
+        public void onParamCapabilitiesChanged(
+                @NonNull String profileId, @NonNull List<ParamCapability> updatedCaps) {
         }
     }
 
diff --git a/media/java/android/media/quality/ParamCapability.java b/media/java/android/media/quality/ParamCapability.java
index 70e8592..0b698a9 100644
--- a/media/java/android/media/quality/ParamCapability.java
+++ b/media/java/android/media/quality/ParamCapability.java
@@ -34,7 +34,7 @@
  * @hide
  */
 @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
-public class ParamCapability implements Parcelable {
+public final class ParamCapability implements Parcelable {
 
     /** @hide */
     @IntDef(flag = true, prefix = { "TYPE_" }, value = {
@@ -104,6 +104,7 @@
     @NonNull
     private final Bundle mCaps;
 
+    /** @hide */
     protected ParamCapability(Parcel in) {
         mName = in.readString();
         mIsSupported = in.readBoolean();
@@ -112,7 +113,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mName);
         dest.writeBoolean(mIsSupported);
         dest.writeInt(mType);
@@ -124,6 +125,7 @@
         return 0;
     }
 
+    @NonNull
     public static final Creator<ParamCapability> CREATOR = new Creator<ParamCapability>() {
         @Override
         public ParamCapability createFromParcel(Parcel in) {
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
index 8fb5712..2be47dd 100644
--- a/media/java/android/media/quality/PictureProfile.java
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -71,6 +71,53 @@
      */
     public static final int TYPE_APPLICATION = 2;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = false, prefix = "ERROR_", value = {
+            ERROR_UNKNOWN,
+            ERROR_NO_PERMISSION,
+            ERROR_DUPLICATE,
+            ERROR_INVALID_ARGUMENT,
+            ERROR_NOT_ALLOWLISTED
+    })
+    public @interface ErrorCode {}
+
+    /**
+     * Error code for unknown errors.
+     * @hide
+     */
+    public static final int ERROR_UNKNOWN = 0;
+
+    /**
+     * Error code for missing necessary permission to handle the profiles.
+     * @hide
+     */
+    public static final int ERROR_NO_PERMISSION = 1;
+
+    /**
+     * Error code for creating a profile with existing profile type and name.
+     *
+     * @see #getProfileType()
+     * @see #getName()
+     * @hide
+     */
+    public static final int ERROR_DUPLICATE = 2;
+
+    /**
+     * Error code for invalid argument.
+     * @hide
+     */
+    public static final int ERROR_INVALID_ARGUMENT = 3;
+
+    /**
+     * Error code for the case when an operation requires an allowlist but the caller is not in the
+     * list.
+     *
+     * @see MediaQualityManager#getPictureProfileAllowList()
+     * @hide
+     */
+    public static final int ERROR_NOT_ALLOWLISTED = 4;
+
 
     private PictureProfile(@NonNull Parcel in) {
         mId = in.readString();
diff --git a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java
index 0957390..d9a1221 100644
--- a/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java
+++ b/media/tests/aidltests/src/com/android/media/AidlConversionUnitTests.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -504,6 +505,27 @@
         assertEquals(AudioDeviceType.OUT_DEVICE, port.ext.getDevice().device.type.type);
     }
 
+    @Test
+    public void testAudioDeviceDescriptionConversion() {
+        for (int nativeDeviceType : AudioSystem.DEVICE_OUT_ALL_SET) {
+            assertNotEquals(
+                    AidlConversion.api2aidl_NativeType_AudioDeviceDescription(nativeDeviceType)
+                            .type,
+                    AudioDeviceType.NONE);
+        }
+
+        for (int nativeDeviceType : AudioSystem.DEVICE_IN_ALL_SET) {
+            if (nativeDeviceType == AudioSystem.DEVICE_IN_COMMUNICATION
+                    || nativeDeviceType == AudioSystem.DEVICE_IN_AMBIENT) {
+                continue;
+            }
+            assertNotEquals(
+                    AidlConversion.api2aidl_NativeType_AudioDeviceDescription(nativeDeviceType)
+                            .type,
+                    AudioDeviceType.NONE);
+        }
+    }
+
     private static AudioFormatDescription createPcm16FormatAidl() {
         final AudioFormatDescription aidl = new AudioFormatDescription();
         aidl.type = AudioFormatType.PCM;
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 3eb99c3..da29c49 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -55,6 +55,7 @@
         "surface_control_input_receiver.cpp",
         "choreographer.cpp",
         "configuration.cpp",
+        "dynamic_instrumentation_manager.cpp",
         "hardware_buffer_jni.cpp",
         "input.cpp",
         "input_transfer_token.cpp",
@@ -100,6 +101,7 @@
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
         "android.os.flags-aconfig-cc",
+        "dynamic_instrumentation_manager_aidl-cpp",
         "libnativedisplay",
         "libfmq",
     ],
diff --git a/native/android/dynamic_instrumentation_manager.cpp b/native/android/dynamic_instrumentation_manager.cpp
new file mode 100644
index 0000000..d9bacb1
--- /dev/null
+++ b/native/android/dynamic_instrumentation_manager.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "ADynamicInstrumentationManager"
+#include <android/dynamic_instrumentation_manager.h>
+#include <android/os/instrumentation/ExecutableMethodFileOffsets.h>
+#include <android/os/instrumentation/IDynamicInstrumentationManager.h>
+#include <android/os/instrumentation/MethodDescriptor.h>
+#include <android/os/instrumentation/TargetProcess.h>
+#include <binder/Binder.h>
+#include <binder/IServiceManager.h>
+#include <utils/Log.h>
+
+#include <mutex>
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace android::dynamicinstrumentationmanager {
+
+// Global instance of IDynamicInstrumentationManager, service is obtained only on first use.
+static std::mutex mLock;
+static sp<os::instrumentation::IDynamicInstrumentationManager> mService;
+
+sp<os::instrumentation::IDynamicInstrumentationManager> getService() {
+    std::lock_guard<std::mutex> scoped_lock(mLock);
+    if (mService == nullptr || !IInterface::asBinder(mService)->isBinderAlive()) {
+        sp<IBinder> binder =
+                defaultServiceManager()->waitForService(String16("dynamic_instrumentation"));
+        mService = interface_cast<os::instrumentation::IDynamicInstrumentationManager>(binder);
+    }
+    return mService;
+}
+
+} // namespace android::dynamicinstrumentationmanager
+
+using namespace android;
+using namespace dynamicinstrumentationmanager;
+
+struct ADynamicInstrumentationManager_TargetProcess {
+    uid_t uid;
+    uid_t pid;
+    std::string processName;
+
+    ADynamicInstrumentationManager_TargetProcess(uid_t uid, pid_t pid, const char* processName)
+          : uid(uid), pid(pid), processName(processName) {}
+};
+
+ADynamicInstrumentationManager_TargetProcess* ADynamicInstrumentationManager_TargetProcess_create(
+        uid_t uid, pid_t pid, const char* processName) {
+    return new ADynamicInstrumentationManager_TargetProcess(uid, pid, processName);
+}
+
+void ADynamicInstrumentationManager_TargetProcess_destroy(
+        ADynamicInstrumentationManager_TargetProcess* instance) {
+    delete instance;
+}
+
+struct ADynamicInstrumentationManager_MethodDescriptor {
+    std::string fqcn;
+    std::string methodName;
+    std::vector<std::string> fqParameters;
+
+    ADynamicInstrumentationManager_MethodDescriptor(const char* fqcn, const char* methodName,
+                                                    const char* fullyQualifiedParameters[],
+                                                    size_t numParameters)
+          : fqcn(fqcn), methodName(methodName) {
+        std::vector<std::string> fqParameters;
+        fqParameters.reserve(numParameters);
+        std::copy_n(fullyQualifiedParameters, numParameters, std::back_inserter(fqParameters));
+        this->fqParameters = std::move(fqParameters);
+    }
+};
+
+ADynamicInstrumentationManager_MethodDescriptor*
+ADynamicInstrumentationManager_MethodDescriptor_create(const char* fullyQualifiedClassName,
+                                                       const char* methodName,
+                                                       const char* fullyQualifiedParameters[],
+                                                       size_t numParameters) {
+    return new ADynamicInstrumentationManager_MethodDescriptor(fullyQualifiedClassName, methodName,
+                                                               fullyQualifiedParameters,
+                                                               numParameters);
+}
+
+void ADynamicInstrumentationManager_MethodDescriptor_destroy(
+        ADynamicInstrumentationManager_MethodDescriptor* instance) {
+    delete instance;
+}
+
+struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets {
+    std::string containerPath;
+    uint64_t containerOffset;
+    uint64_t methodOffset;
+};
+
+ADynamicInstrumentationManager_ExecutableMethodFileOffsets*
+ADynamicInstrumentationManager_ExecutableMethodFileOffsets_create() {
+    return new ADynamicInstrumentationManager_ExecutableMethodFileOffsets();
+}
+
+const char* ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath(
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+    return instance->containerPath.c_str();
+}
+
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset(
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+    return instance->containerOffset;
+}
+
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset(
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+    return instance->methodOffset;
+}
+
+void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets* instance) {
+    delete instance;
+}
+
+int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets(
+        const ADynamicInstrumentationManager_TargetProcess* targetProcess,
+        const ADynamicInstrumentationManager_MethodDescriptor* methodDescriptor,
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets** out) {
+    android::os::instrumentation::TargetProcess targetProcessParcel;
+    targetProcessParcel.uid = targetProcess->uid;
+    targetProcessParcel.pid = targetProcess->pid;
+    targetProcessParcel.processName = targetProcess->processName;
+
+    android::os::instrumentation::MethodDescriptor methodDescriptorParcel;
+    methodDescriptorParcel.fullyQualifiedClassName = methodDescriptor->fqcn;
+    methodDescriptorParcel.methodName = methodDescriptor->methodName;
+    methodDescriptorParcel.fullyQualifiedParameters = methodDescriptor->fqParameters;
+
+    sp<os::instrumentation::IDynamicInstrumentationManager> service = getService();
+    if (service == nullptr) {
+        return INVALID_OPERATION;
+    }
+
+    std::optional<android::os::instrumentation::ExecutableMethodFileOffsets> offsets;
+    binder_status_t result =
+            service->getExecutableMethodFileOffsets(targetProcessParcel, methodDescriptorParcel,
+                                                    &offsets)
+                    .exceptionCode();
+    if (result != OK) {
+        return result;
+    }
+
+    if (offsets != std::nullopt) {
+        auto* value = new ADynamicInstrumentationManager_ExecutableMethodFileOffsets();
+        value->containerPath = offsets->containerPath;
+        value->containerOffset = offsets->containerOffset;
+        value->methodOffset = offsets->methodOffset;
+        *out = value;
+    } else {
+        *out = nullptr;
+    }
+
+    return result;
+}
\ No newline at end of file
diff --git a/native/android/include_platform/android/dynamic_instrumentation_manager.h b/native/android/include_platform/android/dynamic_instrumentation_manager.h
new file mode 100644
index 0000000..6c46288
--- /dev/null
+++ b/native/android/include_platform/android/dynamic_instrumentation_manager.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ADYNAMICINSTRUMENTATIONMANAGER_H__
+#define __ADYNAMICINSTRUMENTATIONMANAGER_H__
+
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+struct ADynamicInstrumentationManager_MethodDescriptor;
+typedef struct ADynamicInstrumentationManager_MethodDescriptor
+        ADynamicInstrumentationManager_MethodDescriptor;
+
+struct ADynamicInstrumentationManager_TargetProcess;
+typedef struct ADynamicInstrumentationManager_TargetProcess
+        ADynamicInstrumentationManager_TargetProcess;
+
+struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets;
+typedef struct ADynamicInstrumentationManager_ExecutableMethodFileOffsets
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets;
+
+/**
+ * Initializes an ADynamicInstrumentationManager_TargetProcess. Caller must clean up when they are
+ * done with ADynamicInstrumentationManager_TargetProcess_destroy.
+ *
+ * @param uid of targeted process.
+ * @param pid of targeted process.
+ * @param processName to disambiguate from corner cases that may arise from pid reuse.
+ */
+ADynamicInstrumentationManager_TargetProcess* _Nonnull
+        ADynamicInstrumentationManager_TargetProcess_create(
+                uid_t uid, pid_t pid, const char* _Nonnull processName) __INTRODUCED_IN(36);
+/**
+ * Clean up an ADynamicInstrumentationManager_TargetProcess.
+ *
+ * @param instance returned from ADynamicInstrumentationManager_TargetProcess_create.
+ */
+void ADynamicInstrumentationManager_TargetProcess_destroy(
+        ADynamicInstrumentationManager_TargetProcess* _Nonnull instance) __INTRODUCED_IN(36);
+
+/**
+ * Initializes an ADynamicInstrumentationManager_MethodDescriptor. Caller must clean up when they
+ * are done with ADynamicInstrumentationManager_MethodDescriptor_Destroy.
+ *
+ * @param fullyQualifiedClassName fqcn of class containing the method.
+ * @param methodName
+ * @param fullyQualifiedParameters fqcn of parameters of the method's signature, or e.g. "int" for
+ *                                 primitives.
+ * @param numParameters length of `fullyQualifiedParameters` array.
+ */
+ADynamicInstrumentationManager_MethodDescriptor* _Nonnull
+        ADynamicInstrumentationManager_MethodDescriptor_create(
+                const char* _Nonnull fullyQualifiedClassName, const char* _Nonnull methodName,
+                const char* _Nonnull fullyQualifiedParameters[_Nonnull], size_t numParameters)
+                __INTRODUCED_IN(36);
+/**
+ * Clean up an ADynamicInstrumentationManager_MethodDescriptor.
+ *
+ * @param instance returned from ADynamicInstrumentationManager_MethodDescriptor_create.
+ */
+void ADynamicInstrumentationManager_MethodDescriptor_destroy(
+        ADynamicInstrumentationManager_MethodDescriptor* _Nonnull instance) __INTRODUCED_IN(36);
+
+/**
+ * Get the containerPath calculated by
+ * ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @return The OS path of the containing file.
+ */
+const char* _Nullable ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath(
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+        __INTRODUCED_IN(36);
+/**
+ * Get the containerOffset calculated by
+ * ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @return The offset of the containing file within the process' memory.
+ */
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset(
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+        __INTRODUCED_IN(36);
+/**
+ * Get the methodOffset calculated by ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @param instance created with ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ * @return The offset of the method within the containing file.
+ */
+uint64_t ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset(
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+        __INTRODUCED_IN(36);
+/**
+ * Clean up an ADynamicInstrumentationManager_ExecutableMethodFileOffsets.
+ *
+ * @param instance returned from ADynamicInstrumentationManager_getExecutableMethodFileOffsets.
+ */
+void ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy(
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull instance)
+        __INTRODUCED_IN(36);
+/**
+ * Provides ART metadata about the described java method within the target process.
+ *
+ * @param targetProcess describes for which process the data is requested.
+ * @param methodDescriptor describes the targeted method.
+ * @param out will be populated with the data if successful. A nullptr combined
+ *        with an OK status means that the program method is defined, but the offset
+ *        info was unavailable because it is not AOT compiled.
+ * @return status indicating success or failure. The values correspond to the `binder_exception_t`
+ *         enum values from <android/binder_status.h>.
+ */
+int32_t ADynamicInstrumentationManager_getExecutableMethodFileOffsets(
+        const ADynamicInstrumentationManager_TargetProcess* _Nonnull targetProcess,
+        const ADynamicInstrumentationManager_MethodDescriptor* _Nonnull methodDescriptor,
+        ADynamicInstrumentationManager_ExecutableMethodFileOffsets* _Nonnull* _Nullable out)
+        __INTRODUCED_IN(36);
+
+__END_DECLS
+
+#endif // __ADYNAMICINSTRUMENTATIONMANAGER_H__
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index b025cb8..a046057 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -4,6 +4,15 @@
     AActivityManager_removeUidImportanceListener; # systemapi introduced=31
     AActivityManager_isUidActive; # systemapi introduced=31
     AActivityManager_getUidImportance; # systemapi introduced=31
+    ADynamicInstrumentationManager_TargetProcess_create; # systemapi
+    ADynamicInstrumentationManager_TargetProcess_destroy; # systemapi
+    ADynamicInstrumentationManager_MethodDescriptor_create; # systemapi
+    ADynamicInstrumentationManager_MethodDescriptor_destroy; # systemapi
+    ADynamicInstrumentationManager_getExecutableMethodFileOffsets; # systemapi
+    ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerPath; # systemapi
+    ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getContainerOffset; # systemapi
+    ADynamicInstrumentationManager_ExecutableMethodFileOffsets_getMethodOffset; # systemapi
+    ADynamicInstrumentationManager_ExecutableMethodFileOffsets_destroy; # systemapi
     AAssetDir_close;
     AAssetDir_getNextFileName;
     AAssetDir_rewind;
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 1346bd34..40fd068 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -119,4 +119,5 @@
     boolean getSettingStatus();
     boolean isTagPresent();
     List<Entry> getRoutingTableEntryList();
+    void indicateDataMigration(boolean inProgress, String pkg);
 }
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index d9fd42f..c5d8191 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -2795,11 +2795,8 @@
             @IntRange(from = 0, to = 15) int gid, @IntRange(from = 0) int oid,
             @NonNull byte[] payload) {
         Objects.requireNonNull(payload, "Payload must not be null");
-        try {
-            return sService.sendVendorNciMessage(mt, gid, oid, payload);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        return callServiceReturn(() ->  sService.sendVendorNciMessage(mt, gid, oid, payload),
+                SEND_VENDOR_NCI_STATUS_FAILED);
     }
 
     /**
@@ -2873,6 +2870,18 @@
     }
 
     /**
+     * Used by data migration to indicate data migration is in progrerss or not.
+     *
+     * Note: This is @hide intentionally since the client is inside the NFC apex.
+     * @param inProgress true if migration is in progress, false once done.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+    public void indicateDataMigration(boolean inProgress) {
+        callService(() -> sService.indicateDataMigration(inProgress, mContext.getPackageName()));
+    }
+
+    /**
      * Returns an instance of {@link NfcOemExtension} associated with {@link NfcAdapter} instance.
      * @hide
      */
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index e141867..b2dcb7f 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -61,7 +61,7 @@
         "SettingsLibUtils",
         "SettingsLibZeroStatePreference",
         "settingslib_media_flags_lib",
-        "settingslib_flags_lib",
+        "aconfig_settingslib_flags_java_lib",
     ],
 
     plugins: ["androidx.room_room-compiler-plugin"],
@@ -107,20 +107,6 @@
     aconfig_declarations: "settingslib_media_flags",
 }
 
-aconfig_declarations {
-    name: "settingslib_flags",
-    package: "com.android.settingslib.flags",
-    container: "system",
-    srcs: [
-        "aconfig/settingslib.aconfig",
-    ],
-}
-
-java_aconfig_library {
-    name: "settingslib_flags_lib",
-    aconfig_declarations: "settingslib_flags",
-}
-
 soong_config_module_type {
     name: "avatar_picker_java_defaults",
     module_type: "java_defaults",
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
index 979ff96..993555e 100644
--- a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -37,7 +37,7 @@
 /**
  * A preference handled a button
  */
-public class ButtonPreference extends Preference {
+public class ButtonPreference extends Preference implements GroupSectionDividerMixin {
 
     enum ButtonStyle {
         FILLED_NORMAL(0, 0, R.layout.settingslib_expressive_button_filled),
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index d60290e..37f4754 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -43,7 +43,7 @@
  * A custom preference acting as "footer" of a page. It has a field for icon and text. It is added
  * to screen as the last preference.
  */
-public class FooterPreference extends Preference {
+public class FooterPreference extends Preference implements GroupSectionDividerMixin {
     private static final String TAG = "FooterPreference";
 
     public static final String KEY_FOOTER = "footer_preference";
diff --git a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
index c1578ef..1f8cfb5 100644
--- a/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
+++ b/packages/SettingsLib/StatusBannerPreference/src/com/android/settingslib/widget/StatusBannerPreference.kt
@@ -34,7 +34,7 @@
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
-) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
 
     enum class BannerStatus {
         GENERIC,
diff --git a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
index 5be56f8..9764e64 100644
--- a/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
+++ b/packages/SettingsLib/TopIntroPreference/src/com/android/settingslib/widget/TopIntroPreference.kt
@@ -31,7 +31,7 @@
     attrs: AttributeSet? = null,
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
-) : Preference(context, attrs, defStyleAttr, defStyleRes) {
+) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin {
 
     private var isCollapsable: Boolean = false
     private var minLines: Int = 2
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 0dc772a..ebd5a1d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -224,7 +224,7 @@
         // audio sharing is enabled.
         if (bluetoothProfile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT
                 && state == BluetoothAdapter.STATE_DISCONNECTED
-                && BluetoothUtils.isAudioSharingEnabled()) {
+                && BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
             LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager();
             if (profileManager != null
                     && profileManager.getLeAudioBroadcastProfile() != null
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 612c193..a87b815 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -64,6 +64,8 @@
 
     public static final int META_INT_ERROR = -1;
     public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
+    public static final String DEVELOPER_OPTION_PREVIEW_KEY =
+            "bluetooth_le_audio_sharing_ui_preview_enabled";
     private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
     private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
     private static final Set<Integer> SA_PROFILES =
@@ -643,6 +645,12 @@
                 && connectedGroupIds.contains(groupId);
     }
 
+    /** Returns if the le audio sharing UI is available. */
+    public static boolean isAudioSharingUIAvailable(@Nullable Context context) {
+        return isAudioSharingEnabled() || (context != null && isAudioSharingPreviewEnabled(
+                context.getContentResolver()));
+    }
+
     /** Returns if the le audio sharing is enabled. */
     public static boolean isAudioSharingEnabled() {
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -653,7 +661,23 @@
                     && adapter.isLeAudioBroadcastAssistantSupported()
                             == BluetoothStatusCodes.FEATURE_SUPPORTED;
         } catch (IllegalStateException e) {
-            Log.d(TAG, "LE state is on, but there is no bluetooth service.", e);
+            Log.d(TAG, "Fail to check isAudioSharingEnabled, e = ", e);
+            return false;
+        }
+    }
+
+    /** Returns if the le audio sharing preview is enabled in developer option. */
+    public static boolean isAudioSharingPreviewEnabled(@Nullable ContentResolver contentResolver) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        try {
+            return Flags.audioSharingDeveloperOption()
+                    && getAudioSharingPreviewValue(contentResolver)
+                    && adapter.isLeAudioBroadcastSourceSupported()
+                            == BluetoothStatusCodes.FEATURE_SUPPORTED
+                    && adapter.isLeAudioBroadcastAssistantSupported()
+                            == BluetoothStatusCodes.FEATURE_SUPPORTED;
+        } catch (IllegalStateException e) {
+            Log.d(TAG, "Fail to check isAudioSharingPreviewEnabled, e = ", e);
             return false;
         }
     }
@@ -996,6 +1020,17 @@
                 BluetoothCsipSetCoordinator.GROUP_ID_INVALID);
     }
 
+    /** Get develop option value for audio sharing preview. */
+    @WorkerThread
+    private static boolean getAudioSharingPreviewValue(@Nullable ContentResolver contentResolver) {
+        if (contentResolver == null) return false;
+        return Settings.Global.getInt(
+                contentResolver,
+                DEVELOPER_OPTION_PREVIEW_KEY,
+                0 // value off
+        ) == 1;
+    }
+
     /** Get secondary {@link CachedBluetoothDevice} in broadcast. */
     @Nullable
     @WorkerThread
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 8641f70..d0827b3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -1245,7 +1245,7 @@
      */
     public String getConnectionSummary(boolean shortSummary) {
         CharSequence summary = null;
-        if (BluetoothUtils.isAudioSharingEnabled()) {
+        if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
             if (mBluetoothManager == null) {
                 mBluetoothManager = LocalBluetoothManager.getInstance(mContext, null);
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index b9f16ed..4b7cb36 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -383,11 +383,7 @@
                 preferredMainDevice.refresh();
                 hasChanged = true;
             }
-            if (isWorkProfile()) {
-                log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
-            } else {
-                syncAudioSharingSourceIfNeeded(preferredMainDevice);
-            }
+            syncAudioSharingSourceIfNeeded(preferredMainDevice);
         }
         if (hasChanged) {
             log("addMemberDevicesIntoMainDevice: After changed, CachedBluetoothDevice list: "
@@ -402,8 +398,12 @@
     }
 
     private void syncAudioSharingSourceIfNeeded(CachedBluetoothDevice mainDevice) {
-        boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingEnabled();
+        boolean isAudioSharingEnabled = BluetoothUtils.isAudioSharingUIAvailable(mContext);
         if (isAudioSharingEnabled) {
+            if (isWorkProfile()) {
+                log("addMemberDevicesIntoMainDevice: skip sync source for work profile");
+                return;
+            }
             boolean hasBroadcastSource = BluetoothUtils.isBroadcasting(mBtManager)
                     && BluetoothUtils.hasConnectedBroadcastSource(
                     mainDevice, mBtManager);
@@ -433,6 +433,8 @@
                     }
                 }
             }
+        } else {
+            log("addMemberDevicesIntoMainDevice: skip sync source, flag disabled");
         }
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
index 4f315a2..76aa5bf 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InputRouteManager.java
@@ -75,6 +75,24 @@
                 @Override
                 public void onAudioDevicesAdded(@NonNull AudioDeviceInfo[] addedDevices) {
                     applyDefaultSelectedTypeToAllPresets();
+
+                    // Activate the last hot plugged valid input device, to match the output device
+                    // behavior.
+                    @AudioDeviceType int deviceTypeToActivate = mSelectedInputDeviceType;
+                    for (AudioDeviceInfo info : addedDevices) {
+                        if (InputMediaDevice.isSupportedInputDevice(info.getType())) {
+                            deviceTypeToActivate = info.getType();
+                        }
+                    }
+
+                    // Only activate if we find a different valid input device. e.g. if none of the
+                    // addedDevices is supported input device, we don't need to activate anything.
+                    if (mSelectedInputDeviceType != deviceTypeToActivate) {
+                        mSelectedInputDeviceType = deviceTypeToActivate;
+                        AudioDeviceAttributes deviceAttributes =
+                                createInputDeviceAttributes(mSelectedInputDeviceType);
+                        setPreferredDeviceForAllPresets(deviceAttributes);
+                    }
                 }
 
                 @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 0e060df..6d481db 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -29,11 +29,13 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -48,6 +50,7 @@
 
 import com.android.internal.R;
 import com.android.settingslib.flags.Flags;
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
 import com.android.settingslib.widget.AdaptiveIcon;
 
 import com.google.common.collect.ImmutableList;
@@ -61,6 +64,8 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -69,6 +74,7 @@
 import java.util.Set;
 
 @RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
 public class BluetoothUtilsTest {
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -88,6 +94,7 @@
     @Mock private BluetoothLeBroadcastReceiveState mLeBroadcastReceiveState;
 
     private Context mContext;
+    private ShadowBluetoothAdapter mShadowBluetoothAdapter;
     private static final String STRING_METADATA = "string_metadata";
     private static final String BOOL_METADATA = "true";
     private static final String INT_METADATA = "25";
@@ -109,6 +116,7 @@
 
         mContext = spy(RuntimeEnvironment.application);
         mSetFlagsRule.disableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA);
+        mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
         when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
         when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
@@ -1123,4 +1131,129 @@
                                 AudioDeviceInfo.TYPE_HEARING_AID,
                                 address));
     }
+
+    @Test
+    public void isAudioSharingEnabled_flagOff_returnsFalse() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+
+        assertThat(BluetoothUtils.isAudioSharingEnabled()).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingEnabled_featureNotSupported_returnsFalse() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+
+        assertThat(BluetoothUtils.isAudioSharingEnabled()).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingEnabled_featureSupported_returnsTrue() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+
+        assertThat(BluetoothUtils.isAudioSharingEnabled()).isTrue();
+    }
+
+    @Test
+    public void isAudioSharingPreviewEnabled_flagOff_returnsFalse() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingPreviewEnabled(
+                mContext.getContentResolver())).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingPreviewEnabled_featureNotSupported_returnsFalse() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+
+        assertThat(BluetoothUtils.isAudioSharingPreviewEnabled(
+                mContext.getContentResolver())).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingPreviewEnabled_developerOptionOff_returnsFalse() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 0);
+
+        assertThat(BluetoothUtils.isAudioSharingPreviewEnabled(
+                mContext.getContentResolver())).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingPreviewEnabled_developerOptionOn_returnsTrue() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 1);
+
+        assertThat(BluetoothUtils.isAudioSharingPreviewEnabled(
+                mContext.getContentResolver())).isTrue();
+    }
+
+    @Test
+    public void isAudioSharingUIAvailable_audioSharingAndPreviewFlagOff_returnsFalse() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.disableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+
+        assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingUIAvailable_audioSharingAndPreviewDisabled_returnsFalse() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_NOT_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+
+        assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isFalse();
+    }
+
+    @Test
+    public void isAudioSharingUIAvailable_audioSharingEnabled_returnsTrue() {
+        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 0);
+
+        assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue();
+    }
+
+    @Test
+    public void isAudioSharingUIAvailable_audioSharingPreviewEnabled_returnsTrue() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_LE_AUDIO_SHARING);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastSourceSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mShadowBluetoothAdapter.setIsLeAudioBroadcastAssistantSupported(
+                BluetoothStatusCodes.FEATURE_SUPPORTED);
+        mSetFlagsRule.enableFlags(Flags.FLAG_AUDIO_SHARING_DEVELOPER_OPTION);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                BluetoothUtils.DEVELOPER_OPTION_PREVIEW_KEY, 1);
+
+        assertThat(BluetoothUtils.isAudioSharingUIAvailable(mContext)).isTrue();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
index 782cee2..d808a25 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputRouteManagerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -138,6 +139,18 @@
                 /* address= */ "");
     }
 
+    private AudioDeviceAttributes getUsbHeadsetDeviceAttributes() {
+        return new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_INPUT,
+                AudioDeviceInfo.TYPE_USB_HEADSET,
+                /* address= */ "");
+    }
+
+    private AudioDeviceAttributes getHdmiDeviceAttributes() {
+        return new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_HDMI, /* address= */ "");
+    }
+
     private void onPreferredDevicesForCapturePresetChanged(InputRouteManager inputRouteManager) {
         final List<AudioDeviceAttributes> audioDeviceAttributesList =
                 new ArrayList<AudioDeviceAttributes>();
@@ -303,21 +316,47 @@
     }
 
     @Test
-    public void onAudioDevicesAdded_shouldApplyDefaultSelectedDeviceToAllPresets() {
+    public void onAudioDevicesAdded_shouldActivateAddedDevice() {
         final AudioManager audioManager = mock(AudioManager.class);
-        AudioDeviceAttributes wiredHeadsetDeviceAttributes = getWiredHeadsetDeviceAttributes();
-        when(audioManager.getDevicesForAttributes(INPUT_ATTRIBUTES))
-                .thenReturn(Collections.singletonList(wiredHeadsetDeviceAttributes));
-
         InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
         AudioDeviceInfo[] devices = {mockWiredHeadsetInfo()};
         inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
 
-        // Called twice, one after initiation, the other after onAudioDevicesAdded call.
-        verify(audioManager, atLeast(2)).getDevicesForAttributes(INPUT_ATTRIBUTES);
+        // The only added wired headset will be activated.
         for (@MediaRecorder.Source int preset : PRESETS) {
-            verify(audioManager, atLeast(2))
-                    .setPreferredDeviceForCapturePreset(preset, wiredHeadsetDeviceAttributes);
+            verify(audioManager, atLeast(1))
+                    .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+        }
+    }
+
+    @Test
+    public void onAudioDevicesAdded_shouldActivateLastAddedDevice() {
+        final AudioManager audioManager = mock(AudioManager.class);
+        InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+        AudioDeviceInfo[] devices = {mockWiredHeadsetInfo(), mockUsbHeadsetInfo()};
+        inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+        // When adding multiple valid input devices, the last added device (usb headset in this
+        // case) will be activated.
+        for (@MediaRecorder.Source int preset : PRESETS) {
+            verify(audioManager, never())
+                    .setPreferredDeviceForCapturePreset(preset, getWiredHeadsetDeviceAttributes());
+            verify(audioManager, atLeast(1))
+                    .setPreferredDeviceForCapturePreset(preset, getUsbHeadsetDeviceAttributes());
+        }
+    }
+
+    @Test
+    public void onAudioDevicesAdded_doNotActivateInvalidAddedDevice() {
+        final AudioManager audioManager = mock(AudioManager.class);
+        InputRouteManager inputRouteManager = new InputRouteManager(mContext, audioManager);
+        AudioDeviceInfo[] devices = {mockHdmiInfo()};
+        inputRouteManager.mAudioDeviceCallback.onAudioDevicesAdded(devices);
+
+        // Do not activate since HDMI is not a valid input device.
+        for (@MediaRecorder.Source int preset : PRESETS) {
+            verify(audioManager, never())
+                    .setPreferredDeviceForCapturePreset(preset, getHdmiDeviceAttributes());
         }
     }
 
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 2c8c261..0724410 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -263,6 +263,8 @@
     <uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.ACCESS_VIBRATOR_STATE" />
+    <uses-permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS" />
+    <uses-permission android:name="android.permission.START_VIBRATION_SESSIONS" />
     <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" />
     <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
     <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND" />
@@ -957,6 +959,9 @@
     <!-- Permission required for CTS test - CtsTelephonyTestCases -->
     <uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
 
+    <!-- Permission required for ExecutableMethodFileOffsetsTest -->
+    <uses-permission android:name="android.permission.DYNAMIC_INSTRUMENTATION" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
index 1820f39..1903d22 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
+++ b/packages/SystemUI/accessibility/accessibilitymenu/TEST_MAPPING
@@ -1,8 +1,7 @@
 {
-  // TODO: b/324945360 - Re-enable on presubmit after fixing failures
   "postsubmit": [
     {
       "name": "AccessibilityMenuServiceTests"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 5251246..b5eba08 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -118,3 +118,10 @@
       purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "floating_menu_hearing_device_status_icon"
+    namespace: "accessibility"
+    description: "Update hearing device icon in floating menu according to the connection status."
+    bug: "357882387"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3bf3e24..02b7667 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -414,6 +414,17 @@
 }
 
 flag {
+    name: "status_bar_auto_start_screen_record_chip"
+    namespace: "systemui"
+    description: "When screen recording, use the specified start time to update the screen record "
+        "chip state instead of waiting for an official 'recording started' signal"
+    bug: "366448907"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "status_bar_use_repos_for_call_chip"
     namespace: "systemui"
     description: "Use repositories as the source of truth for call notifications shown as a chip in"
@@ -620,9 +631,9 @@
 }
 
 flag {
-    name: "status_bar_simple_fragment"
+    name: "status_bar_root_modernization"
     namespace: "systemui"
-    description: "Feature flag for refactoring the collapsed status bar fragment"
+    description: "Feature flag for replacing the status bar fragment with a compose root"
     bug: "364360986"
 }
 
@@ -1676,6 +1687,16 @@
 }
 
 flag {
+   name: "show_toast_when_app_control_brightness"
+   namespace: "systemui"
+   description: "Showing the warning toast if the current running app window has controlled the brightness value."
+   bug: "363225340"
+   metadata {
+       purpose: PURPOSE_BUGFIX
+   }
+}
+
+flag {
   name: "home_controls_dream_hsum"
   namespace: "systemui"
   description: "Enables the home controls dream in HSUM"
@@ -1743,3 +1764,13 @@
     description: "An implementation of shortcut customizations through shortcut helper."
     bug: "365064144"
 }
+
+flag {
+    name: "stoppable_fgs_system_app"
+    namespace: "systemui"
+    description: "System app with foreground service can opt in to be stoppable."
+    bug: "376564917"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 00d9056..300bdf2 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -48,7 +48,7 @@
                 Bound.LEFT to createViewProperty(Bound.LEFT),
                 Bound.TOP to createViewProperty(Bound.TOP),
                 Bound.RIGHT to createViewProperty(Bound.RIGHT),
-                Bound.BOTTOM to createViewProperty(Bound.BOTTOM)
+                Bound.BOTTOM to createViewProperty(Bound.BOTTOM),
             )
 
         private fun createViewProperty(bound: Bound): IntProperty<View> {
@@ -89,7 +89,7 @@
             interpolator: Interpolator = DEFAULT_INTERPOLATOR,
             duration: Long = DEFAULT_DURATION,
             animateChildren: Boolean = true,
-            excludedViews: Set<View> = emptySet()
+            excludedViews: Set<View> = emptySet(),
         ): Boolean {
             return animate(
                 rootView,
@@ -97,7 +97,7 @@
                 duration,
                 ephemeral = false,
                 animateChildren = animateChildren,
-                excludedViews = excludedViews
+                excludedViews = excludedViews,
             )
         }
 
@@ -111,7 +111,7 @@
             interpolator: Interpolator = DEFAULT_INTERPOLATOR,
             duration: Long = DEFAULT_DURATION,
             animateChildren: Boolean = true,
-            excludedViews: Set<View> = emptySet()
+            excludedViews: Set<View> = emptySet(),
         ): Boolean {
             return animate(
                 rootView,
@@ -119,7 +119,7 @@
                 duration,
                 ephemeral = true,
                 animateChildren = animateChildren,
-                excludedViews = excludedViews
+                excludedViews = excludedViews,
             )
         }
 
@@ -129,7 +129,7 @@
             duration: Long,
             ephemeral: Boolean,
             animateChildren: Boolean,
-            excludedViews: Set<View> = emptySet()
+            excludedViews: Set<View> = emptySet(),
         ): Boolean {
             if (
                 !occupiesSpace(
@@ -137,7 +137,7 @@
                     rootView.left,
                     rootView.top,
                     rootView.right,
-                    rootView.bottom
+                    rootView.bottom,
                 )
             ) {
                 return false
@@ -149,7 +149,7 @@
                 listener,
                 recursive = true,
                 animateChildren = animateChildren,
-                excludedViews = excludedViews
+                excludedViews = excludedViews,
             )
             return true
         }
@@ -164,7 +164,7 @@
         private fun createUpdateListener(
             interpolator: Interpolator,
             duration: Long,
-            ephemeral: Boolean
+            ephemeral: Boolean,
         ): View.OnLayoutChangeListener {
             return createListener(interpolator, duration, ephemeral)
         }
@@ -196,9 +196,9 @@
          *
          * @param includeFadeIn true if the animator should also fade in the view and child views.
          * @param fadeInInterpolator the interpolator to use when fading in the view. Unused if
-         *     [includeFadeIn] is false.
-         * @param onAnimationEnd an optional runnable that will be run once the animation
-         *    finishes successfully. Will not be run if the animation is cancelled.
+         *   [includeFadeIn] is false.
+         * @param onAnimationEnd an optional runnable that will be run once the animation finishes,
+         *   regardless of whether the animation is cancelled or finishes successfully.
          */
         @JvmOverloads
         fun animateAddition(
@@ -217,7 +217,7 @@
                     rootView.left,
                     rootView.top,
                     rootView.right,
-                    rootView.bottom
+                    rootView.bottom,
                 )
             ) {
                 return false
@@ -241,7 +241,10 @@
                 // First, fade in the container view
                 val containerDuration = duration / 6
                 createAndStartFadeInAnimator(
-                    rootView, containerDuration, startDelay = 0, interpolator = fadeInInterpolator
+                    rootView,
+                    containerDuration,
+                    startDelay = 0,
+                    interpolator = fadeInInterpolator,
                 )
 
                 // Then, fade in the child views
@@ -253,7 +256,7 @@
                         childDuration,
                         // Wait until the container fades in before fading in the children
                         startDelay = containerDuration,
-                        interpolator = fadeInInterpolator
+                        interpolator = fadeInInterpolator,
                     )
                 }
                 // For now, we don't recursively fade in additional sub views (e.g. grandchild
@@ -264,7 +267,7 @@
                     rootView,
                     duration / 2,
                     startDelay = 0,
-                    interpolator = fadeInInterpolator
+                    interpolator = fadeInInterpolator,
                 )
             }
 
@@ -323,7 +326,7 @@
                     previousLeft: Int,
                     previousTop: Int,
                     previousRight: Int,
-                    previousBottom: Int
+                    previousBottom: Int,
                 ) {
                     if (view == null) return
 
@@ -353,14 +356,14 @@
                             startTop,
                             startRight,
                             startBottom,
-                            ignorePreviousValues
+                            ignorePreviousValues,
                         )
                     val endValues =
                         mapOf(
                             Bound.LEFT to left,
                             Bound.TOP to top,
                             Bound.RIGHT to right,
-                            Bound.BOTTOM to bottom
+                            Bound.BOTTOM to bottom,
                         )
 
                     val boundsToAnimate = mutableSetOf<Bound>()
@@ -396,8 +399,8 @@
          * added on the side(s) of the [destination], the translation of those margins can be
          * included by specifying [includeMargins].
          *
-         * @param onAnimationEnd an optional runnable that will be run once the animation finishes
-         *    successfully. Will not be run if the animation is cancelled.
+         * @param onAnimationEnd an optional runnable that will be run once the animation finishes,
+         *   regardless of whether the animation is cancelled or finishes successfully.
          */
         @JvmOverloads
         fun animateRemoval(
@@ -414,7 +417,7 @@
                     rootView.left,
                     rootView.top,
                     rootView.right,
-                    rootView.bottom
+                    rootView.bottom,
                 )
             ) {
                 return false
@@ -458,7 +461,7 @@
                     Bound.LEFT to rootView.left,
                     Bound.TOP to rootView.top,
                     Bound.RIGHT to rootView.right,
-                    Bound.BOTTOM to rootView.bottom
+                    Bound.BOTTOM to rootView.bottom,
                 )
             val endValues =
                 processEndValuesForRemoval(
@@ -550,7 +553,7 @@
             destination: Hotspot,
             endValues: Map<Bound, Int>,
             interpolator: Interpolator,
-            duration: Long
+            duration: Long,
         ) {
             for (i in 0 until rootView.childCount) {
                 val child = rootView.getChildAt(i)
@@ -559,7 +562,7 @@
                         Bound.LEFT to child.left,
                         Bound.TOP to child.top,
                         Bound.RIGHT to child.right,
-                        Bound.BOTTOM to child.bottom
+                        Bound.BOTTOM to child.bottom,
                     )
                 val childEndValues =
                     processChildEndValuesForRemoval(
@@ -569,7 +572,7 @@
                         child.right,
                         child.bottom,
                         endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT),
-                        endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP)
+                        endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP),
                     )
 
                 val boundsToAnimate = mutableSetOf<Bound>()
@@ -587,7 +590,7 @@
                     childEndValues,
                     interpolator,
                     duration,
-                    ephemeral = true
+                    ephemeral = true,
                 )
             }
         }
@@ -601,7 +604,7 @@
             left: Int,
             top: Int,
             right: Int,
-            bottom: Int
+            bottom: Int,
         ): Boolean {
             return visibility != View.GONE && left != right && top != bottom
         }
@@ -616,6 +619,7 @@
          * not newly introduced margins are included.
          *
          * Base case
+         *
          * ```
          *     1) origin=TOP
          *         x---------x    x---------x    x---------x    x---------x    x---------x
@@ -636,9 +640,11 @@
          *                                         x-----x       x-------x     |         |
          *                                                                     x---------x
          * ```
+         *
          * In case the start and end values differ in the direction of the origin, and
          * [ignorePreviousValues] is false, the previous values are used and a translation is
          * included in addition to the view expansion.
+         *
          * ```
          *     origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70)
          *         x
@@ -660,7 +666,7 @@
             previousTop: Int,
             previousRight: Int,
             previousBottom: Int,
-            ignorePreviousValues: Boolean
+            ignorePreviousValues: Boolean,
         ): Map<Bound, Int> {
             val startLeft = if (ignorePreviousValues) newLeft else previousLeft
             val startTop = if (ignorePreviousValues) newTop else previousTop
@@ -727,7 +733,7 @@
                 Bound.LEFT to left,
                 Bound.TOP to top,
                 Bound.RIGHT to right,
-                Bound.BOTTOM to bottom
+                Bound.BOTTOM to bottom,
             )
         }
 
@@ -777,18 +783,17 @@
             includeMargins: Boolean = false,
         ): Map<Bound, Int> {
             val marginAdjustment =
-                if (includeMargins &&
-                    (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
+                if (includeMargins && (rootView.layoutParams is ViewGroup.MarginLayoutParams)) {
                     val marginLp = rootView.layoutParams as ViewGroup.MarginLayoutParams
                     DimenHolder(
                         left = marginLp.leftMargin,
                         top = marginLp.topMargin,
                         right = marginLp.rightMargin,
-                        bottom = marginLp.bottomMargin
+                        bottom = marginLp.bottomMargin,
                     )
-            } else {
-                DimenHolder(0, 0, 0, 0)
-            }
+                } else {
+                    DimenHolder(0, 0, 0, 0)
+                }
 
             // These are the end values to use *if* this bound is part of the destination.
             val endLeft = left - marginAdjustment.left
@@ -805,60 +810,69 @@
             //  - If destination=BOTTOM_LEFT, then endBottom == endTop AND endLeft == endRight.
 
             return when (destination) {
-                Hotspot.TOP -> mapOf(
-                    Bound.TOP to endTop,
-                    Bound.BOTTOM to endTop,
-                    Bound.LEFT to left,
-                    Bound.RIGHT to right,
-                )
-                Hotspot.TOP_RIGHT -> mapOf(
-                    Bound.TOP to endTop,
-                    Bound.BOTTOM to endTop,
-                    Bound.RIGHT to endRight,
-                    Bound.LEFT to endRight,
-                )
-                Hotspot.RIGHT -> mapOf(
-                    Bound.RIGHT to endRight,
-                    Bound.LEFT to endRight,
-                    Bound.TOP to top,
-                    Bound.BOTTOM to bottom,
-                )
-                Hotspot.BOTTOM_RIGHT -> mapOf(
-                    Bound.BOTTOM to endBottom,
-                    Bound.TOP to endBottom,
-                    Bound.RIGHT to endRight,
-                    Bound.LEFT to endRight,
-                )
-                Hotspot.BOTTOM -> mapOf(
-                    Bound.BOTTOM to endBottom,
-                    Bound.TOP to endBottom,
-                    Bound.LEFT to left,
-                    Bound.RIGHT to right,
-                )
-                Hotspot.BOTTOM_LEFT -> mapOf(
-                    Bound.BOTTOM to endBottom,
-                    Bound.TOP to endBottom,
-                    Bound.LEFT to endLeft,
-                    Bound.RIGHT to endLeft,
-                )
-                Hotspot.LEFT -> mapOf(
-                    Bound.LEFT to endLeft,
-                    Bound.RIGHT to endLeft,
-                    Bound.TOP to top,
-                    Bound.BOTTOM to bottom,
-                )
-                Hotspot.TOP_LEFT -> mapOf(
-                    Bound.TOP to endTop,
-                    Bound.BOTTOM to endTop,
-                    Bound.LEFT to endLeft,
-                    Bound.RIGHT to endLeft,
-                )
-                Hotspot.CENTER -> mapOf(
-                    Bound.LEFT to (endLeft + endRight) / 2,
-                    Bound.RIGHT to (endLeft + endRight) / 2,
-                    Bound.TOP to (endTop + endBottom) / 2,
-                    Bound.BOTTOM to (endTop + endBottom) / 2,
-                )
+                Hotspot.TOP ->
+                    mapOf(
+                        Bound.TOP to endTop,
+                        Bound.BOTTOM to endTop,
+                        Bound.LEFT to left,
+                        Bound.RIGHT to right,
+                    )
+                Hotspot.TOP_RIGHT ->
+                    mapOf(
+                        Bound.TOP to endTop,
+                        Bound.BOTTOM to endTop,
+                        Bound.RIGHT to endRight,
+                        Bound.LEFT to endRight,
+                    )
+                Hotspot.RIGHT ->
+                    mapOf(
+                        Bound.RIGHT to endRight,
+                        Bound.LEFT to endRight,
+                        Bound.TOP to top,
+                        Bound.BOTTOM to bottom,
+                    )
+                Hotspot.BOTTOM_RIGHT ->
+                    mapOf(
+                        Bound.BOTTOM to endBottom,
+                        Bound.TOP to endBottom,
+                        Bound.RIGHT to endRight,
+                        Bound.LEFT to endRight,
+                    )
+                Hotspot.BOTTOM ->
+                    mapOf(
+                        Bound.BOTTOM to endBottom,
+                        Bound.TOP to endBottom,
+                        Bound.LEFT to left,
+                        Bound.RIGHT to right,
+                    )
+                Hotspot.BOTTOM_LEFT ->
+                    mapOf(
+                        Bound.BOTTOM to endBottom,
+                        Bound.TOP to endBottom,
+                        Bound.LEFT to endLeft,
+                        Bound.RIGHT to endLeft,
+                    )
+                Hotspot.LEFT ->
+                    mapOf(
+                        Bound.LEFT to endLeft,
+                        Bound.RIGHT to endLeft,
+                        Bound.TOP to top,
+                        Bound.BOTTOM to bottom,
+                    )
+                Hotspot.TOP_LEFT ->
+                    mapOf(
+                        Bound.TOP to endTop,
+                        Bound.BOTTOM to endTop,
+                        Bound.LEFT to endLeft,
+                        Bound.RIGHT to endLeft,
+                    )
+                Hotspot.CENTER ->
+                    mapOf(
+                        Bound.LEFT to (endLeft + endRight) / 2,
+                        Bound.RIGHT to (endLeft + endRight) / 2,
+                        Bound.TOP to (endTop + endBottom) / 2,
+                        Bound.BOTTOM to (endTop + endBottom) / 2,
+                    )
             }
         }
 
@@ -887,7 +901,7 @@
             right: Int,
             bottom: Int,
             parentWidth: Int,
-            parentHeight: Int
+            parentHeight: Int,
         ): Map<Bound, Int> {
             val halfWidth = (right - left) / 2
             val halfHeight = (bottom - top) / 2
@@ -945,7 +959,7 @@
                 Bound.LEFT to endLeft,
                 Bound.TOP to endTop,
                 Bound.RIGHT to endRight,
-                Bound.BOTTOM to endBottom
+                Bound.BOTTOM to endBottom,
             )
         }
 
@@ -954,7 +968,7 @@
             listener: View.OnLayoutChangeListener,
             recursive: Boolean = false,
             animateChildren: Boolean = true,
-            excludedViews: Set<View> = emptySet()
+            excludedViews: Set<View> = emptySet(),
         ) {
             if (excludedViews.contains(view)) return
 
@@ -973,7 +987,7 @@
                         listener,
                         recursive = true,
                         animateChildren = animateChildren,
-                        excludedViews = excludedViews
+                        excludedViews = excludedViews,
                     )
                 }
             }
@@ -1027,7 +1041,7 @@
                                 PropertyValuesHolder.ofInt(
                                     PROPERTIES[bound],
                                     startValues.getValue(bound),
-                                    endValues.getValue(bound)
+                                    endValues.getValue(bound),
                                 )
                             )
                         }
@@ -1056,9 +1070,10 @@
                             // listener.
                             recursivelyRemoveListener(view)
                         }
-                        if (!cancelled) {
-                            onAnimationEnd?.run()
-                        }
+                        // Run the end runnable regardless of whether the animation was cancelled or
+                        // not - this ensures critical actions (like removing a window) always occur
+                        // (see b/344049884).
+                        onAnimationEnd?.run()
                     }
 
                     override fun onAnimationCancel(animation: Animator) {
@@ -1077,17 +1092,19 @@
             view: View,
             duration: Long,
             startDelay: Long,
-            interpolator: Interpolator
+            interpolator: Interpolator,
         ) {
             val animator = ObjectAnimator.ofFloat(view, "alpha", 1f)
             animator.startDelay = startDelay
             animator.duration = duration
             animator.interpolator = interpolator
-            animator.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator) {
-                    view.setTag(R.id.tag_alpha_animator, null /* tag */)
+            animator.addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        view.setTag(R.id.tag_alpha_animator, null /* tag */)
+                    }
                 }
-            })
+            )
 
             (view.getTag(R.id.tag_alpha_animator) as? ObjectAnimator)?.cancel()
             view.setTag(R.id.tag_alpha_animator, animator)
@@ -1105,7 +1122,7 @@
         RIGHT,
         BOTTOM_RIGHT,
         BOTTOM,
-        BOTTOM_LEFT
+        BOTTOM_LEFT,
     }
 
     private enum class Bound(val label: String, val overrideTag: Int) {
@@ -1147,14 +1164,10 @@
         };
 
         abstract fun setValue(view: View, value: Int)
+
         abstract fun getValue(view: View): Int
     }
 
     /** Simple data class to hold a set of dimens for left, top, right, bottom. */
-    private data class DimenHolder(
-        val left: Int,
-        val top: Int,
-        val right: Int,
-        val bottom: Int,
-    )
+    private data class DimenHolder(val left: Int, val top: Int, val right: Int, val bottom: Int)
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index e9b7335..eeab232 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -98,19 +98,20 @@
     theme: Int = SystemUIDialog.DEFAULT_THEME,
     dismissOnDeviceLock: Boolean = SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK,
     @GravityInt dialogGravity: Int? = null,
+    dialogDelegate: DialogDelegate<SystemUIDialog> =
+        object : DialogDelegate<SystemUIDialog> {
+            override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+                super.onCreate(dialog, savedInstanceState)
+                dialogGravity?.let { dialog.window?.setGravity(it) }
+            }
+        },
     content: @Composable (SystemUIDialog) -> Unit,
 ): ComponentSystemUIDialog {
     return create(
         context = context,
         theme = theme,
         dismissOnDeviceLock = dismissOnDeviceLock,
-        delegate =
-            object : DialogDelegate<SystemUIDialog> {
-                override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
-                    super.onCreate(dialog, savedInstanceState)
-                    dialogGravity?.let { dialog.window?.setGravity(it) }
-                }
-            },
+        delegate = dialogDelegate,
         content = content,
     )
 }
@@ -287,7 +288,7 @@
             Modifier.padding(top = 16.dp, bottom = 6.dp)
                 .semantics { contentDescription = dragHandleContentDescription }
                 .clickable { dialog.dismiss() },
-        color = MaterialTheme.colorScheme.outlineVariant,
+        color = MaterialTheme.colorScheme.onSurfaceVariant,
         shape = MaterialTheme.shapes.extraLarge,
     ) {
         Box(Modifier.size(width = 32.dp, height = 4.dp))
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index ae18aac..052e60e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -25,6 +25,9 @@
     /** Returns a [Flow] tracking the value of a setting as an [Int]. */
     fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
 
+    /** Returns a [Flow] tracking the value of a setting as a [Boolean]. */
+    fun boolSetting(name: String, defaultValue: Boolean = false): Flow<Boolean>
+
     /** Updates the value of the setting with the given name. */
     suspend fun setInt(name: String, value: Int)
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
index 8b9fcb4..9f37959 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
@@ -63,6 +63,10 @@
             .flowOn(backgroundDispatcher)
     }
 
+    override fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> {
+        return intSetting(name, if (defaultValue) 1 else 0).map { it != 0 }
+    }
+
     override suspend fun setInt(name: String, value: Int) {
         withContext(backgroundDispatcher) { Settings.Secure.putInt(contentResolver, name, value) }
     }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
index 8cda9b3..b5f991c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
@@ -25,6 +25,9 @@
     /** Returns a [Flow] tracking the value of a setting as an [Int]. */
     fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
 
+    /** Returns a [Flow] tracking the value of a setting as a [Boolean]. */
+    fun boolSetting(name: String, defaultValue: Boolean = false): Flow<Boolean>
+
     /** Updates the value of the setting with the given name. */
     suspend fun setInt(name: String, value: Int)
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
index b039a32..8485d4d 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
@@ -63,6 +63,10 @@
             .flowOn(backgroundDispatcher)
     }
 
+    override fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> {
+        return intSetting(name, if (defaultValue) 1 else 0).map { it != 0 }
+    }
+
     override suspend fun setInt(name: String, value: Int) {
         withContext(backgroundDispatcher) { Settings.System.putInt(contentResolver, name, value) }
     }
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
index 37b9792..21d8d46 100644
--- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSecureSettingsRepository.kt
@@ -28,6 +28,10 @@
         return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
     }
 
+    override fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> {
+        return intSetting(name, if (defaultValue) 1 else 0).map { it != 0 }
+    }
+
     override suspend fun setInt(name: String, value: Int) {
         settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
     }
diff --git a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt
index 7da2b40..f6c053f 100644
--- a/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt
+++ b/packages/SystemUI/customization/tests/utils/src/com/android/systemui/shared/settings/data/repository/FakeSystemSettingsRepository.kt
@@ -28,6 +28,10 @@
         return settings.map { it.getOrDefault(name, defaultValue.toString()) }.map { it.toInt() }
     }
 
+    override fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> {
+        return intSetting(name, if (defaultValue) 1 else 0).map { it != 0 }
+    }
+
     override suspend fun setInt(name: String, value: Int) {
         settings.value = settings.value.toMutableMap().apply { this[name] = value.toString() }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index a8c3af9..8db82d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -24,8 +24,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
-class
-ViewHierarchyAnimatorTest : SysuiTestCase() {
+class ViewHierarchyAnimatorTest : SysuiTestCase() {
     companion object {
         private const val TEST_DURATION = 1000L
         private val TEST_INTERPOLATOR = Interpolators.LINEAR
@@ -49,9 +48,12 @@
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
         // animate()
-        var success = ViewHierarchyAnimator.animate(
-            rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
-        )
+        var success =
+            ViewHierarchyAnimator.animate(
+                rootView,
+                interpolator = TEST_INTERPOLATOR,
+                duration = TEST_DURATION,
+            )
         rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -64,9 +66,12 @@
         ViewHierarchyAnimator.stopAnimating(rootView)
 
         // animateNextUpdate()
-        success = ViewHierarchyAnimator.animateNextUpdate(
-            rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
-        )
+        success =
+            ViewHierarchyAnimator.animateNextUpdate(
+                rootView,
+                interpolator = TEST_INTERPOLATOR,
+                duration = TEST_DURATION,
+            )
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
         assertTrue(success)
@@ -79,9 +84,12 @@
 
         // animateAddition()
         rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                interpolator = TEST_INTERPOLATOR,
+                duration = TEST_DURATION,
+            )
         rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
         assertTrue(success)
@@ -93,9 +101,12 @@
         // animateRemoval()
         setUpRootWithChildren()
         val child = rootView.getChildAt(0)
-        success = ViewHierarchyAnimator.animateRemoval(
-            child, interpolator = TEST_INTERPOLATOR, duration = TEST_DURATION
-        )
+        success =
+            ViewHierarchyAnimator.animateRemoval(
+                child,
+                interpolator = TEST_INTERPOLATOR,
+                duration = TEST_DURATION,
+            )
 
         assertTrue(success)
         assertNotNull(child.getTag(R.id.tag_animator))
@@ -185,7 +196,7 @@
         // Change all bounds.
         rootView.measure(
             View.MeasureSpec.makeMeasureSpec(190, View.MeasureSpec.EXACTLY),
-            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
         )
         rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
 
@@ -211,14 +222,12 @@
     fun animatesRootAndChildren_withExcludedViews() {
         setUpRootWithChildren()
 
-        val success = ViewHierarchyAnimator.animate(
-            rootView,
-            excludedViews = setOf(rootView.getChildAt(0))
-        )
+        val success =
+            ViewHierarchyAnimator.animate(rootView, excludedViews = setOf(rootView.getChildAt(0)))
         // Change all bounds.
         rootView.measure(
-                View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+            View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
+            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
         )
         rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
 
@@ -245,14 +254,11 @@
     fun animatesRootOnly() {
         setUpRootWithChildren()
 
-        val success = ViewHierarchyAnimator.animate(
-                rootView,
-                animateChildren = false
-        )
+        val success = ViewHierarchyAnimator.animate(rootView, animateChildren = false)
         // Change all bounds.
         rootView.measure(
-                View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+            View.MeasureSpec.makeMeasureSpec(180, View.MeasureSpec.EXACTLY),
+            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
         )
         rootView.layout(10 /* l */, 20 /* t */, 200 /* r */, 120 /* b */)
 
@@ -351,10 +357,11 @@
     fun animatesAppearingViewsRespectingOrigin() {
         // CENTER.
         rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
-        var success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.CENTER
-        )
+        var success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.CENTER,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -364,10 +371,11 @@
 
         // LEFT.
         rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.LEFT
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.LEFT,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -377,10 +385,11 @@
 
         // TOP_LEFT.
         rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -390,10 +399,11 @@
 
         // TOP.
         rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.TOP
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.TOP,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -403,10 +413,11 @@
 
         // TOP_RIGHT.
         rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -416,10 +427,11 @@
 
         // RIGHT.
         rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.RIGHT
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.RIGHT,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -429,10 +441,11 @@
 
         // BOTTOM_RIGHT.
         rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -442,10 +455,11 @@
 
         // BOTTOM.
         rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.BOTTOM
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.BOTTOM,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -455,10 +469,11 @@
 
         // BOTTOM_LEFT.
         rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -471,11 +486,12 @@
     fun animatesAppearingViewsRespectingMargins() {
         // CENTER.
         rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
-        var success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.CENTER,
-            includeMargins = true
-        )
+        var success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.CENTER,
+                includeMargins = true,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -485,10 +501,12 @@
 
         // LEFT.
         rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView, origin = ViewHierarchyAnimator.Hotspot.LEFT,
-            includeMargins = true
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.LEFT,
+                includeMargins = true,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -498,11 +516,12 @@
 
         // TOP_LEFT.
         rootView.layout(0 /* l */, 0 /* t */, 0 /* r */, 0 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
-            includeMargins = true
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+                includeMargins = true,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -512,10 +531,12 @@
 
         // TOP.
         rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView, origin = ViewHierarchyAnimator.Hotspot.TOP,
-            includeMargins = true
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.TOP,
+                includeMargins = true,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -525,11 +546,12 @@
 
         // TOP_RIGHT.
         rootView.layout(150 /* l */, 0 /* t */, 150 /* r */, 0 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
-            includeMargins = true
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+                includeMargins = true,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -539,11 +561,12 @@
 
         // RIGHT.
         rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.RIGHT,
-            includeMargins = true
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.RIGHT,
+                includeMargins = true,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -553,11 +576,12 @@
 
         // BOTTOM_RIGHT.
         rootView.layout(150 /* l */, 150 /* t */, 150 /* r */, 150 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
-            includeMargins = true
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+                includeMargins = true,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -567,11 +591,12 @@
 
         // BOTTOM.
         rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.BOTTOM,
-            includeMargins = true
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.BOTTOM,
+                includeMargins = true,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -581,11 +606,12 @@
 
         // BOTTOM_LEFT.
         rootView.layout(0 /* l */, 150 /* t */, 0 /* r */, 150 /* b */)
-        success = ViewHierarchyAnimator.animateAddition(
-            rootView,
-            origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
-            includeMargins = true
-        )
+        success =
+            ViewHierarchyAnimator.animateAddition(
+                rootView,
+                origin = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+                includeMargins = true,
+            )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         assertTrue(success)
@@ -626,7 +652,7 @@
         ViewHierarchyAnimator.animateAddition(
             rootView,
             includeFadeIn = true,
-            fadeInInterpolator = Interpolators.LINEAR
+            fadeInInterpolator = Interpolators.LINEAR,
         )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
@@ -641,7 +667,7 @@
         ViewHierarchyAnimator.animateAddition(
             rootView,
             includeFadeIn = true,
-            fadeInInterpolator = Interpolators.LINEAR
+            fadeInInterpolator = Interpolators.LINEAR,
         )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
@@ -663,7 +689,7 @@
         ViewHierarchyAnimator.animateAddition(
             rootView,
             includeFadeIn = true,
-            fadeInInterpolator = Interpolators.LINEAR
+            fadeInInterpolator = Interpolators.LINEAR,
         )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
@@ -680,7 +706,7 @@
         ViewHierarchyAnimator.animateAddition(
             rootView,
             includeFadeIn = true,
-            fadeInInterpolator = Interpolators.LINEAR
+            fadeInInterpolator = Interpolators.LINEAR,
         )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
@@ -692,7 +718,7 @@
         ViewHierarchyAnimator.animateAddition(
             rootView,
             includeFadeIn = true,
-            fadeInInterpolator = Interpolators.LINEAR
+            fadeInInterpolator = Interpolators.LINEAR,
         )
 
         // THEN the alpha remains at its current value (it doesn't get reset to 0)
@@ -721,7 +747,7 @@
         ViewHierarchyAnimator.animateAddition(
             rootView,
             includeFadeIn = false,
-            fadeInInterpolator = Interpolators.LINEAR
+            fadeInInterpolator = Interpolators.LINEAR,
         )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
@@ -738,10 +764,10 @@
         val onAnimationEndRunnable = { runnableRun = true }
 
         ViewHierarchyAnimator.animateAddition(
-                rootView,
-                origin = ViewHierarchyAnimator.Hotspot.CENTER,
-                includeMargins = true,
-                onAnimationEnd = onAnimationEndRunnable
+            rootView,
+            origin = ViewHierarchyAnimator.Hotspot.CENTER,
+            includeMargins = true,
+            onAnimationEnd = onAnimationEndRunnable,
         )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
@@ -751,7 +777,7 @@
     }
 
     @Test
-    fun animateAddition_runnableDoesNotRunWhenAnimationCancelled() {
+    fun animateAddition_runnableRunsWhenAnimationCancelled() {
         var runnableRun = false
         val onAnimationEndRunnable = { runnableRun = true }
 
@@ -759,13 +785,13 @@
             rootView,
             origin = ViewHierarchyAnimator.Hotspot.CENTER,
             includeMargins = true,
-            onAnimationEnd = onAnimationEndRunnable
+            onAnimationEnd = onAnimationEndRunnable,
         )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
         cancelAnimation(rootView)
 
-        assertEquals(false, runnableRun)
+        assertEquals(true, runnableRun)
     }
 
     @Test
@@ -777,7 +803,7 @@
             rootView,
             origin = ViewHierarchyAnimator.Hotspot.CENTER,
             includeMargins = true,
-            onAnimationEnd = onAnimationEndRunnable
+            onAnimationEnd = onAnimationEndRunnable,
         )
         rootView.layout(50 /* l */, 50 /* t */, 100 /* r */, 100 /* b */)
 
@@ -791,11 +817,12 @@
         setUpRootWithChildren()
 
         val child = rootView.getChildAt(0)
-        val success = ViewHierarchyAnimator.animateRemoval(
-            child,
-            destination = ViewHierarchyAnimator.Hotspot.LEFT,
-            interpolator = Interpolators.LINEAR
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                child,
+                destination = ViewHierarchyAnimator.Hotspot.LEFT,
+                interpolator = Interpolators.LINEAR,
+            )
 
         assertTrue(success)
         assertNotNull(child.getTag(R.id.tag_animator))
@@ -820,11 +847,12 @@
         rootView.addView(onlyChild)
         forceLayout()
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            onlyChild,
-            destination = ViewHierarchyAnimator.Hotspot.LEFT,
-            interpolator = Interpolators.LINEAR
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                onlyChild,
+                destination = ViewHierarchyAnimator.Hotspot.LEFT,
+                interpolator = Interpolators.LINEAR,
+            )
 
         assertTrue(success)
         assertNotNull(onlyChild.getTag(R.id.tag_animator))
@@ -845,9 +873,11 @@
         setUpRootWithChildren()
         var removedChild = rootView.getChildAt(0)
         var remainingChild = rootView.getChildAt(1)
-        var success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, destination = ViewHierarchyAnimator.Hotspot.CENTER
-        )
+        var success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.CENTER,
+            )
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -863,9 +893,11 @@
         setUpRootWithChildren()
         removedChild = rootView.getChildAt(0)
         remainingChild = rootView.getChildAt(1)
-        success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, destination = ViewHierarchyAnimator.Hotspot.LEFT
-        )
+        success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.LEFT,
+            )
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -881,9 +913,11 @@
         setUpRootWithChildren()
         removedChild = rootView.getChildAt(0)
         remainingChild = rootView.getChildAt(1)
-        success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT
-        )
+        success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+            )
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -899,9 +933,11 @@
         setUpRootWithChildren()
         removedChild = rootView.getChildAt(0)
         remainingChild = rootView.getChildAt(1)
-        success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP
-        )
+        success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.TOP,
+            )
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -917,9 +953,11 @@
         setUpRootWithChildren()
         removedChild = rootView.getChildAt(0)
         remainingChild = rootView.getChildAt(1)
-        success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT
-        )
+        success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+            )
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -935,9 +973,11 @@
         setUpRootWithChildren()
         removedChild = rootView.getChildAt(0)
         remainingChild = rootView.getChildAt(1)
-        success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, destination = ViewHierarchyAnimator.Hotspot.RIGHT
-        )
+        success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.RIGHT,
+            )
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -953,9 +993,11 @@
         setUpRootWithChildren()
         removedChild = rootView.getChildAt(0)
         remainingChild = rootView.getChildAt(1)
-        success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT
-        )
+        success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+            )
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -971,9 +1013,11 @@
         setUpRootWithChildren()
         removedChild = rootView.getChildAt(0)
         remainingChild = rootView.getChildAt(1)
-        success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM
-        )
+        success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
+            )
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -989,9 +1033,11 @@
         setUpRootWithChildren()
         removedChild = rootView.getChildAt(0)
         remainingChild = rootView.getChildAt(1)
-        success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT
-        )
+        success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+            )
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -1014,11 +1060,12 @@
         val originalRight = removedChild.right
         val originalBottom = removedChild.bottom
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild,
-            destination = ViewHierarchyAnimator.Hotspot.CENTER,
-            includeMargins = true,
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.CENTER,
+                includeMargins = true,
+            )
         forceLayout()
 
         assertTrue(success)
@@ -1027,13 +1074,7 @@
         val expectedX = ((originalLeft - M_LEFT) + (originalRight + M_RIGHT)) / 2
         val expectedY = ((originalTop - M_TOP) + (originalBottom + M_BOTTOM)) / 2
 
-        checkBounds(
-            removedChild,
-            l = expectedX,
-            t = expectedY,
-            r = expectedX,
-            b = expectedY
-        )
+        checkBounds(removedChild, l = expectedX, t = expectedY, r = expectedX, b = expectedY)
     }
 
     @Test
@@ -1044,11 +1085,12 @@
         val originalTop = removedChild.top
         val originalBottom = removedChild.bottom
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild,
-            destination = ViewHierarchyAnimator.Hotspot.LEFT,
-            includeMargins = true,
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.LEFT,
+                includeMargins = true,
+            )
         forceLayout()
 
         assertTrue(success)
@@ -1059,7 +1101,7 @@
             l = originalLeft - M_LEFT,
             t = originalTop,
             r = originalLeft - M_LEFT,
-            b = originalBottom
+            b = originalBottom,
         )
     }
 
@@ -1070,11 +1112,12 @@
         val originalLeft = removedChild.left
         val originalTop = removedChild.top
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild,
-            destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
-            includeMargins = true,
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.TOP_LEFT,
+                includeMargins = true,
+            )
         forceLayout()
 
         assertTrue(success)
@@ -1085,7 +1128,7 @@
             l = originalLeft - M_LEFT,
             t = originalTop - M_TOP,
             r = originalLeft - M_LEFT,
-            b = originalTop - M_TOP
+            b = originalTop - M_TOP,
         )
     }
 
@@ -1097,11 +1140,12 @@
         val originalTop = removedChild.top
         val originalRight = removedChild.right
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild,
-            destination = ViewHierarchyAnimator.Hotspot.TOP,
-            includeMargins = true,
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.TOP,
+                includeMargins = true,
+            )
         forceLayout()
 
         assertTrue(success)
@@ -1112,7 +1156,7 @@
             l = originalLeft,
             t = originalTop - M_TOP,
             r = originalRight,
-            b = originalTop - M_TOP
+            b = originalTop - M_TOP,
         )
     }
 
@@ -1123,11 +1167,12 @@
         val originalTop = removedChild.top
         val originalRight = removedChild.right
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild,
-            destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
-            includeMargins = true,
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.TOP_RIGHT,
+                includeMargins = true,
+            )
         forceLayout()
 
         assertTrue(success)
@@ -1138,7 +1183,7 @@
             l = originalRight + M_RIGHT,
             t = originalTop - M_TOP,
             r = originalRight + M_RIGHT,
-            b = originalTop - M_TOP
+            b = originalTop - M_TOP,
         )
     }
 
@@ -1150,11 +1195,12 @@
         val originalRight = removedChild.right
         val originalBottom = removedChild.bottom
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild,
-            destination = ViewHierarchyAnimator.Hotspot.RIGHT,
-            includeMargins = true,
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.RIGHT,
+                includeMargins = true,
+            )
         forceLayout()
 
         assertTrue(success)
@@ -1165,7 +1211,7 @@
             l = originalRight + M_RIGHT,
             t = originalTop,
             r = originalRight + M_RIGHT,
-            b = originalBottom
+            b = originalBottom,
         )
     }
 
@@ -1176,11 +1222,12 @@
         val originalRight = removedChild.right
         val originalBottom = removedChild.bottom
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild,
-            destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
-            includeMargins = true,
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.BOTTOM_RIGHT,
+                includeMargins = true,
+            )
         forceLayout()
 
         assertTrue(success)
@@ -1191,7 +1238,7 @@
             l = originalRight + M_RIGHT,
             t = originalBottom + M_BOTTOM,
             r = originalRight + M_RIGHT,
-            b = originalBottom + M_BOTTOM
+            b = originalBottom + M_BOTTOM,
         )
     }
 
@@ -1203,11 +1250,12 @@
         val originalRight = removedChild.right
         val originalBottom = removedChild.bottom
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild,
-            destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
-            includeMargins = true,
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.BOTTOM,
+                includeMargins = true,
+            )
         forceLayout()
 
         assertTrue(success)
@@ -1218,7 +1266,7 @@
             l = originalLeft,
             t = originalBottom + M_BOTTOM,
             r = originalRight,
-            b = originalBottom + M_BOTTOM
+            b = originalBottom + M_BOTTOM,
         )
     }
 
@@ -1229,11 +1277,12 @@
         val originalLeft = removedChild.left
         val originalBottom = removedChild.bottom
 
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild,
-            destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
-            includeMargins = true,
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(
+                removedChild,
+                destination = ViewHierarchyAnimator.Hotspot.BOTTOM_LEFT,
+                includeMargins = true,
+            )
         forceLayout()
 
         assertTrue(success)
@@ -1244,9 +1293,10 @@
             l = originalLeft - M_LEFT,
             t = originalBottom + M_BOTTOM,
             r = originalLeft - M_LEFT,
-            b = originalBottom + M_BOTTOM
+            b = originalBottom + M_BOTTOM,
         )
     }
+
     /* ******** end of animatesViewRemoval_includeMarginsTrue tests ******** */
 
     @Test
@@ -1256,9 +1306,8 @@
         val child = rootView.getChildAt(0) as ViewGroup
         val firstGrandChild = child.getChildAt(0)
         val secondGrandChild = child.getChildAt(1)
-        val success = ViewHierarchyAnimator.animateRemoval(
-            child, interpolator = Interpolators.LINEAR
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(child, interpolator = Interpolators.LINEAR)
 
         assertTrue(success)
         assertNotNull(child.getTag(R.id.tag_animator))
@@ -1288,9 +1337,8 @@
 
         val removedChild = rootView.getChildAt(0)
         val remainingChild = rootView.getChildAt(1)
-        val success = ViewHierarchyAnimator.animateRemoval(
-            removedChild, interpolator = Interpolators.LINEAR
-        )
+        val success =
+            ViewHierarchyAnimator.animateRemoval(removedChild, interpolator = Interpolators.LINEAR)
         // Ensure that the layout happens before the checks.
         forceLayout()
 
@@ -1315,17 +1363,14 @@
         forceLayout()
         val removedView = rootView.getChildAt(0)
 
-        ViewHierarchyAnimator.animateRemoval(
-            removedView,
-            onAnimationEnd = onAnimationEndRunnable
-        )
+        ViewHierarchyAnimator.animateRemoval(removedView, onAnimationEnd = onAnimationEndRunnable)
         endAnimation(removedView)
 
         assertEquals(true, runnableRun)
     }
 
     @Test
-    fun animateRemoval_runnableDoesNotRunWhenAnimationCancelled() {
+    fun animateRemoval_runnableRunsWhenAnimationCancelled() {
         var runnableRun = false
         val onAnimationEndRunnable = { runnableRun = true }
 
@@ -1333,13 +1378,10 @@
         forceLayout()
         val removedView = rootView.getChildAt(0)
 
-        ViewHierarchyAnimator.animateRemoval(
-            removedView,
-            onAnimationEnd = onAnimationEndRunnable
-        )
+        ViewHierarchyAnimator.animateRemoval(removedView, onAnimationEnd = onAnimationEndRunnable)
         cancelAnimation(removedView)
 
-        assertEquals(false, runnableRun)
+        assertEquals(true, runnableRun)
     }
 
     @Test
@@ -1351,10 +1393,7 @@
         forceLayout()
         val removedView = rootView.getChildAt(0)
 
-        ViewHierarchyAnimator.animateRemoval(
-            removedView,
-            onAnimationEnd = onAnimationEndRunnable
-        )
+        ViewHierarchyAnimator.animateRemoval(removedView, onAnimationEnd = onAnimationEndRunnable)
         advanceAnimation(removedView, 0.5f)
 
         assertEquals(false, runnableRun)
@@ -1370,7 +1409,7 @@
         rootView.addView(secondChild)
         rootView.measure(
             View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
-            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
         )
         rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */)
 
@@ -1378,7 +1417,7 @@
         // Change all bounds.
         rootView.measure(
             View.MeasureSpec.makeMeasureSpec(150, View.MeasureSpec.EXACTLY),
-            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY)
+            View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY),
         )
         rootView.layout(0 /* l */, 0 /* t */, 150 /* r */, 100 /* b */)
 
@@ -1501,7 +1540,7 @@
         checkBounds(rootView, l = 0, t = 15, r = 70, b = 80)
 
         // Change all bounds again.
-        rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */)
+        rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
         assertNull(rootView.getTag(R.id.tag_animator))
         checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
@@ -1523,7 +1562,7 @@
 
         ViewHierarchyAnimator.stopAnimating(rootView)
         // Change all bounds again.
-        rootView.layout(10 /* l */, 10 /* t */, 50/* r */, 50 /* b */)
+        rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
 
         assertNull(rootView.getTag(R.id.tag_animator))
         checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
@@ -1543,10 +1582,8 @@
         val secondChild = View(mContext)
         rootView.addView(secondChild)
 
-        val firstChildParams = LinearLayout.LayoutParams(
-            0 /* width */,
-            LinearLayout.LayoutParams.MATCH_PARENT
-        )
+        val firstChildParams =
+            LinearLayout.LayoutParams(0 /* width */, LinearLayout.LayoutParams.MATCH_PARENT)
         firstChildParams.weight = 0.5f
         if (includeMarginsOnFirstChild) {
             firstChildParams.leftMargin = M_LEFT
@@ -1556,23 +1593,25 @@
         }
         firstChild.layoutParams = firstChildParams
 
-        val secondChildParams = LinearLayout.LayoutParams(
-            0 /* width */,
-            LinearLayout.LayoutParams.MATCH_PARENT
-        )
+        val secondChildParams =
+            LinearLayout.LayoutParams(0 /* width */, LinearLayout.LayoutParams.MATCH_PARENT)
         secondChildParams.weight = 0.5f
         secondChild.layoutParams = secondChildParams
 
         firstGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */)
-        (firstGrandChild.layoutParams as RelativeLayout.LayoutParams)
-            .addRule(RelativeLayout.ALIGN_PARENT_START)
-        (firstGrandChild.layoutParams as RelativeLayout.LayoutParams)
-            .addRule(RelativeLayout.ALIGN_PARENT_TOP)
+        (firstGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule(
+            RelativeLayout.ALIGN_PARENT_START
+        )
+        (firstGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule(
+            RelativeLayout.ALIGN_PARENT_TOP
+        )
         secondGrandChild.layoutParams = RelativeLayout.LayoutParams(40 /* width */, 40 /* height */)
-        (secondGrandChild.layoutParams as RelativeLayout.LayoutParams)
-            .addRule(RelativeLayout.ALIGN_PARENT_END)
-        (secondGrandChild.layoutParams as RelativeLayout.LayoutParams)
-            .addRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
+        (secondGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule(
+            RelativeLayout.ALIGN_PARENT_END
+        )
+        (secondGrandChild.layoutParams as RelativeLayout.LayoutParams).addRule(
+            RelativeLayout.ALIGN_PARENT_BOTTOM
+        )
 
         forceLayout()
     }
@@ -1580,7 +1619,7 @@
     private fun forceLayout() {
         rootView.measure(
             View.MeasureSpec.makeMeasureSpec(200 /* width */, View.MeasureSpec.AT_MOST),
-            View.MeasureSpec.makeMeasureSpec(100 /* height */, View.MeasureSpec.AT_MOST)
+            View.MeasureSpec.makeMeasureSpec(100 /* height */, View.MeasureSpec.AT_MOST),
         )
         rootView.layout(0 /* l */, 0 /* t */, 200 /* r */, 100 /* b */)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
index 0983105..f4cffc5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
@@ -20,7 +20,7 @@
 import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE
 import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
 import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.hardware.display.DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS
 import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -119,7 +119,8 @@
                     .registerDisplayListener(
                         capture(listenerCaptor),
                         eq(null),
-                        eq(EVENT_FLAG_DISPLAY_BRIGHTNESS),
+                        eq(0),
+                        eq(PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS),
                     )
 
                 val newBrightness = BrightnessInfo(0.6f, 0.3f, 0.9f)
@@ -157,7 +158,8 @@
                     .registerDisplayListener(
                         capture(listenerCaptor),
                         eq(null),
-                        eq(EVENT_FLAG_DISPLAY_BRIGHTNESS),
+                        eq(0),
+                        eq(PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS),
                     )
 
                 changeBrightnessInfoAndNotify(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
index 116b705..7cdfb0e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
+import com.android.systemui.kosmos.brightnessWarningToast
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
@@ -61,6 +62,7 @@
                 sliderHapticsViewModelFactory,
                 brightnessMirrorShowingInteractor,
                 supportsMirroring = true,
+                brightnessWarningToast,
             )
         }
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
index cd8b2e1..e6e5665 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/DisplayRepositoryTest.kt
@@ -540,7 +540,8 @@
             .registerDisplayListener(
                 connectedDisplayListener.capture(),
                 eq(testHandler),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED),
+                eq(0),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED),
             )
         return flowValue
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
index d1431ee..1580ea5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarterTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts
+import com.android.systemui.keyboard.shortcut.shortcutCustomizationDialogStarterFactory
 import com.android.systemui.keyboard.shortcut.shortcutHelperAppCategoriesShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperCurrentAppShortcutsSource
 import com.android.systemui.keyboard.shortcut.shortcutHelperInputShortcutsSource
@@ -71,7 +72,13 @@
 
     private val starter: ShortcutHelperDialogStarter =
         with(kosmos) {
-            ShortcutHelperDialogStarter(coroutineScope, viewModel, dialogFactory, activityStarter)
+            ShortcutHelperDialogStarter(
+                coroutineScope,
+                viewModel,
+                shortcutCustomizationDialogStarterFactory,
+                dialogFactory,
+                activityStarter,
+            )
         }
 
     @Before
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 8ccaf6b..0f63150 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -300,7 +300,7 @@
         mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
                 UserHandle.USER_CURRENT);
         verifyActivityDetails("abc/.def");
-        assertThat(mController.isEnabledForLockScreenButton()).isFalse();
+        assertThat(mController.isEnabledForLockScreenButton()).isTrue();
         assertThat(mController.isAllowedOnLockScreen()).isTrue();
         assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 16ae466..0356422 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -42,6 +42,7 @@
 import android.os.Binder;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.DeviceConfig;
 import android.testing.TestableLooper;
 
@@ -49,6 +50,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogTransitionAnimator;
 import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -315,13 +317,36 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_STOPPABLE_FGS_SYSTEM_APP)
+    public void testButtonVisibilityOfStoppableApps() throws Exception {
+        setUserProfiles(0);
+        setBackgroundRestrictionExemptionReason("pkg", 12345, REASON_ALLOWLISTED_PACKAGE);
+        setBackgroundRestrictionExemptionReason("vendor_pkg", 67890, REASON_ALLOWLISTED_PACKAGE);
+
+        // Same as above, but apps are opt-in to be stoppable
+        setStoppableApps(new String[] {"pkg"}, /* vendor */ false);
+        setStoppableApps(new String[] {"vendor_pkg"}, /* vendor */ true);
+
+        final Binder binder = new Binder();
+        setShowStopButtonForUserAllowlistedApps(true);
+        // Both are foreground.
+        mIForegroundServiceObserver.onForegroundStateChanged(binder, "pkg", 0, true);
+        mIForegroundServiceObserver.onForegroundStateChanged(binder, "vendor_pkg", 0, true);
+        Assert.assertEquals(2, mFmc.visibleButtonsCount());
+
+        // The vendor package is no longer foreground. Only `pkg` remains.
+        mIForegroundServiceObserver.onForegroundStateChanged(binder, "vendor_pkg", 0, false);
+        Assert.assertEquals(1, mFmc.visibleButtonsCount());
+    }
+
+    @Test
     public void testShowUserVisibleJobsOnCreation() {
         // Test when the default is on.
         mDeviceConfigProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
                 "true", false);
         FgsManagerController fmc = new FgsManagerControllerImpl(
-                mContext,
+                mContext.getResources(),
                 mMainExecutor,
                 mBackgroundExecutor,
                 mSystemClock,
@@ -348,7 +373,7 @@
                 SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS,
                 "false", false);
         fmc = new FgsManagerControllerImpl(
-                mContext,
+                mContext.getResources(),
                 mMainExecutor,
                 mBackgroundExecutor,
                 mSystemClock,
@@ -446,6 +471,11 @@
                 .getBackgroundRestrictionExemptionReason(uid);
     }
 
+    private void setStoppableApps(String[] packageNames, boolean vendor) throws Exception {
+        overrideResource(vendor ? com.android.internal.R.array.vendor_stoppable_fgs_system_apps
+                    : com.android.internal.R.array.stoppable_fgs_system_apps, packageNames);
+    }
+
     FgsManagerController createFgsManagerController() throws RemoteException {
         ArgumentCaptor<IForegroundServiceObserver> iForegroundServiceObserverArgumentCaptor =
                 ArgumentCaptor.forClass(IForegroundServiceObserver.class);
@@ -455,7 +485,7 @@
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
         FgsManagerController result = new FgsManagerControllerImpl(
-                mContext,
+                mContext.getResources(),
                 mMainExecutor,
                 mBackgroundExecutor,
                 mSystemClock,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
index 5cba325..03feceb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -318,6 +318,63 @@
             }
         }
 
+    @Test
+    fun qqsMediaExpansion_collapsedMediaInLandscape() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                setCollapsedMediaInLandscape(true)
+                setMediaState(ACTIVE_MEDIA)
+
+                setConfigurationForMediaInRow(mediaInRow = false)
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+
+                setConfigurationForMediaInRow(mediaInRow = true)
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED)
+            }
+        }
+
+    @Test
+    fun qqsMediaExpansion_notCollapsedMediaInLandscape_alwaysExpanded() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                setCollapsedMediaInLandscape(false)
+                setMediaState(ACTIVE_MEDIA)
+
+                setConfigurationForMediaInRow(mediaInRow = false)
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+
+                setConfigurationForMediaInRow(mediaInRow = true)
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+            }
+        }
+
+    @Test
+    fun qqsMediaExpansion_reactsToChangesInCollapsedMediaInLandscape() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                setConfigurationForMediaInRow(mediaInRow = true)
+                setMediaState(ACTIVE_MEDIA)
+
+                setCollapsedMediaInLandscape(false)
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+
+                setCollapsedMediaInLandscape(true)
+                Snapshot.sendApplyNotifications()
+                runCurrent()
+                assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.COLLAPSED)
+            }
+        }
+
     private fun TestScope.setMediaState(state: MediaState) {
         with(kosmos) {
             val activeMedia = state == ACTIVE_MEDIA
@@ -331,6 +388,14 @@
         runCurrent()
     }
 
+    private fun TestScope.setCollapsedMediaInLandscape(collapsed: Boolean) {
+        with(kosmos) {
+            overrideResource(R.bool.config_quickSettingsMediaLandscapeCollapsed, collapsed)
+            fakeConfigurationRepository.onAnyConfigurationChange()
+        }
+        runCurrent()
+    }
+
     companion object {
         private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
index 41e2467..ae7719b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessControllerTest.kt
@@ -16,13 +16,19 @@
 
 package com.android.systemui.settings.brightness
 
+import android.hardware.display.BrightnessInfo
 import android.hardware.display.DisplayManager
 import android.os.Handler
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.service.vr.IVrManager
 import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.settings.DisplayTracker
@@ -30,13 +36,16 @@
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -44,7 +53,8 @@
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper
 class BrightnessControllerTest : SysuiTestCase() {
-
+    @get:Rule
+    public val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
     private val executor = FakeExecutor(FakeSystemClock())
     private val secureSettings = FakeSettings()
     @Mock private lateinit var toggleSlider: ToggleSlider
@@ -53,6 +63,7 @@
     @Mock private lateinit var displayManager: DisplayManager
     @Mock private lateinit var iVrManager: IVrManager
     @Mock private lateinit var logger: LogBuffer
+    @Mock private lateinit var display: Display
 
     private lateinit var testableLooper: TestableLooper
 
@@ -63,9 +74,11 @@
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
 
+        val contextSpy = spy(context)
+        whenever(contextSpy.getDisplay()).thenReturn(display)
         underTest =
             BrightnessController(
-                context,
+                contextSpy,
                 toggleSlider,
                 userTracker,
                 displayTracker,
@@ -105,4 +118,21 @@
 
         assertThat(messagesProcessed).isEqualTo(1)
     }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_SHOW_TOAST_WHEN_APP_CONTROL_BRIGHTNESS)
+    fun testOnChange_showToastWhenAppOverridesBrightness() {
+        val brightnessInfo = BrightnessInfo(
+            0.45f, 0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+            1.0f /* highBrightnessTransitionPoint */,
+            BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
+            true /* isBrightnessOverrideByWindow */
+        )
+        whenever(display.brightnessInfo).thenReturn(brightnessInfo)
+        underTest.registerCallbacks()
+        testableLooper.processAllMessages()
+
+        underTest.onChanged(true /* tracking */, 100 /* value */, false /* stopTracking */)
+        verify(toggleSlider).showToast(any())
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
index 637a12c..3697c31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.haptics.slider.HapticSlider
 import com.android.systemui.haptics.slider.HapticSliderPlugin
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.brightness.ui.BrightnessWarningToast
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.BrightnessMirrorController
 import com.android.systemui.util.mockito.any
@@ -64,6 +65,7 @@
     @Mock private lateinit var vibratorHelper: VibratorHelper
     @Mock private lateinit var msdlPlayer: MSDLPlayer
     @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var brightnessWarningToast: BrightnessWarningToast
 
     @Captor
     private lateinit var seekBarChangeCaptor: ArgumentCaptor<SeekBar.OnSeekBarChangeListener>
@@ -94,6 +96,7 @@
                     HapticSlider.SeekBar(seekBar),
                 ),
                 activityStarter,
+                brightnessWarningToast,
             )
         mController.init()
         mController.setOnChangedListener(listener)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
index 4478252..33a0803 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerBaseTest.java
@@ -86,6 +86,7 @@
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.util.wakelock.WakeLockFake;
@@ -160,6 +161,8 @@
     @Mock
     protected DeviceEntryFingerprintAuthInteractor mDeviceEntryFingerprintAuthInteractor;
     @Mock
+    protected UserLogoutInteractor mUserLogoutInteractor;
+    @Mock
     protected ScreenLifecycle mScreenLifecycle;
     @Mock
     protected AuthController mAuthController;
@@ -248,6 +251,9 @@
 
         when(mFaceHelpMessageDeferralFactory.create()).thenReturn(mFaceHelpMessageDeferral);
         when(mDeviceEntryFingerprintAuthInteractor.isEngaged()).thenReturn(mock(StateFlow.class));
+        StateFlow mockLogoutEnabledFlow = mock(StateFlow.class);
+        when(mockLogoutEnabledFlow.getValue()).thenReturn(false);
+        when(mUserLogoutInteractor.isLogoutEnabled()).thenReturn(mockLogoutEnabledFlow);
 
         mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor);
 
@@ -291,7 +297,8 @@
                 KeyguardInteractorFactory.create(mFlags).getKeyguardInteractor(),
                 mBiometricMessageInteractor,
                 mDeviceEntryFingerprintAuthInteractor,
-                mDeviceEntryFaceAuthInteractor
+                mDeviceEntryFaceAuthInteractor,
+                mUserLogoutInteractor
         );
         mController.init();
         mController.setIndicationArea(mIndicationArea);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
index 0efd591..11a125a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractorTest.kt
@@ -16,29 +16,35 @@
 
 package com.android.systemui.statusbar.chips.screenrecord.domain.interactor
 
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
 import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager.Companion.createTask
 import com.android.systemui.screenrecord.data.model.ScreenRecordModel
 import com.android.systemui.screenrecord.data.repository.screenRecordRepository
 import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
 class ScreenRecordChipInteractorTest : SysuiTestCase() {
-    private val kosmos = Kosmos().also { it.testCase = this }
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
     private val screenRecordRepo = kosmos.screenRecordRepository
     private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
@@ -116,6 +122,137 @@
         }
 
     @Test
+    @DisableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP)
+    fun screenRecordState_flagOff_doesNotAutomaticallySwitchToRecordingBasedOnTime() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.screenRecordState)
+
+            // WHEN screen record should start in 900ms
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900))
+
+            // WHEN 900ms has elapsed
+            advanceTimeBy(901)
+
+            // THEN we don't automatically update to the recording state if the flag is off
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900))
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP)
+    fun screenRecordState_flagOn_automaticallySwitchesToRecordingBasedOnTime() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.screenRecordState)
+
+            // WHEN screen record should start in 900ms
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900))
+
+            // WHEN 900ms has elapsed
+            advanceTimeBy(901)
+
+            // THEN we automatically update to the recording state
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = null))
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP)
+    fun screenRecordState_recordingBeginsEarly_switchesToRecording() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.screenRecordState)
+
+            // WHEN screen record should start in 900ms
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(900))
+
+            // WHEN we update to the Recording state earlier than 900ms
+            advanceTimeBy(800)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            val task = createTask(taskId = 1)
+            mediaProjectionRepo.mediaProjectionState.value =
+                MediaProjectionState.Projecting.SingleTask(
+                    "host.package",
+                    hostDeviceName = null,
+                    task,
+                )
+
+            // THEN we immediately switch to Recording, and we have the task
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task))
+
+            // WHEN more than 900ms has elapsed
+            advanceTimeBy(200)
+
+            // THEN we still stay in the Recording state and we have the task
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = task))
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP)
+    fun screenRecordState_secondRecording_doesNotAutomaticallyStart() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.screenRecordState)
+
+            // First recording starts, records, and stops
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(900)
+            advanceTimeBy(900)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Recording
+            advanceTimeBy(5000)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing
+            advanceTimeBy(10000)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.DoingNothing)
+
+            // WHEN a second recording is starting
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2900)
+
+            // THEN we stay as starting and do not switch to Recording (verifying the auto-start
+            // timer is reset)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(2900))
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP)
+    fun screenRecordState_startingButThenDoingNothing_doesNotAutomaticallyStart() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.screenRecordState)
+
+            // WHEN a screen recording is starting in 500ms
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(500)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(500))
+
+            // But it's cancelled after 300ms
+            advanceTimeBy(300)
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.DoingNothing
+
+            // THEN we don't automatically start the recording 200ms later
+            advanceTimeBy(201)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.DoingNothing)
+        }
+
+    @Test
+    @EnableFlags(FLAG_STATUS_BAR_AUTO_START_SCREEN_RECORD_CHIP)
+    fun screenRecordState_multipleStartingValues_autoStartResets() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.screenRecordState)
+
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(2900)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(2900))
+
+            advanceTimeBy(2800)
+
+            // WHEN there's 100ms left to go before auto-start, but then we get a new start time
+            // that's in 500ms
+            screenRecordRepo.screenRecordState.value = ScreenRecordModel.Starting(500)
+
+            // THEN we don't auto-start in 100ms
+            advanceTimeBy(101)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Starting(500))
+
+            // THEN we *do* auto-start 400ms later
+            advanceTimeBy(401)
+            assertThat(latest).isEqualTo(ScreenRecordChipModel.Recording(recordedTask = null))
+        }
+
+    @Test
     fun stopRecording_sendsToRepo() =
         testScope.runTest {
             assertThat(screenRecordRepo.stopRecordingInvoked).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
index bfebe18..48d8add6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/screenrecord/ui/viewmodel/ScreenRecordChipViewModelTest.kt
@@ -26,9 +26,8 @@
 import com.android.systemui.animation.mockDialogTransitionAnimator
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
 import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager
@@ -44,6 +43,7 @@
 import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
+import com.android.systemui.testKosmos
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlin.test.Test
@@ -61,7 +61,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ScreenRecordChipViewModelTest : SysuiTestCase() {
-    private val kosmos = Kosmos().also { it.testCase = this }
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
     private val testScope = kosmos.testScope
     private val screenRecordRepo = kosmos.screenRecordRepository
     private val mediaProjectionRepo = kosmos.fakeMediaProjectionRepository
@@ -254,7 +254,7 @@
                 MediaProjectionState.Projecting.SingleTask(
                     "host.package",
                     hostDeviceName = null,
-                    FakeActivityTaskManager.createTask(taskId = 1)
+                    FakeActivityTaskManager.createTask(taskId = 1),
                 )
 
             // THEN the start time is still the old start time
@@ -275,12 +275,7 @@
             clickListener!!.onClick(chipView)
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
             verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    any(),
-                    anyBoolean(),
-                )
+                .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean())
         }
 
     @Test
@@ -297,12 +292,7 @@
             clickListener!!.onClick(chipView)
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
             verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    any(),
-                    anyBoolean(),
-                )
+                .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean())
         }
 
     @Test
@@ -314,7 +304,7 @@
                 MediaProjectionState.Projecting.SingleTask(
                     "host.package",
                     hostDeviceName = null,
-                    FakeActivityTaskManager.createTask(taskId = 1)
+                    FakeActivityTaskManager.createTask(taskId = 1),
                 )
 
             val clickListener = ((latest as OngoingActivityChipModel.Shown).onClickListener)
@@ -323,12 +313,7 @@
             clickListener!!.onClick(chipView)
             // EndScreenRecordingDialogDelegate will test that the dialog has the right message
             verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    eq(mockSystemUIDialog),
-                    eq(chipBackgroundView),
-                    any(),
-                    anyBoolean(),
-                )
+                .showFromView(eq(mockSystemUIDialog), eq(chipBackgroundView), any(), anyBoolean())
         }
 
     @Test
@@ -344,12 +329,7 @@
 
             val cujCaptor = argumentCaptor<DialogCuj>()
             verify(kosmos.mockDialogTransitionAnimator)
-                .showFromView(
-                    any(),
-                    any(),
-                    cujCaptor.capture(),
-                    anyBoolean(),
-                )
+                .showFromView(any(), any(), cujCaptor.capture(), anyBoolean())
 
             assertThat(cujCaptor.firstValue.cujType)
                 .isEqualTo(Cuj.CUJ_STATUS_BAR_LAUNCH_DIALOG_FROM_CHIP)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index e96def6..c5c2a94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -29,7 +29,6 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.mediaprojection.data.model.MediaProjectionState
 import com.android.systemui.mediaprojection.data.repository.fakeMediaProjectionRepository
@@ -48,6 +47,7 @@
 import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
 import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.testKosmos
 import com.android.systemui.util.time.fakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -72,7 +72,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @DisableFlags(StatusBarNotifChips.FLAG_NAME)
 class OngoingActivityChipsViewModelTest : SysuiTestCase() {
-    private val kosmos = Kosmos().also { it.testCase = this }
+    private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val systemClock = kosmos.fakeSystemClock
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index 938da88..009b33b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -23,7 +23,6 @@
 import android.view.ViewGroup
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.fragments.FragmentHostManager
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
@@ -81,21 +80,21 @@
         )
 
     @Test
-    @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
-    fun simpleFragment_startsFromCoreStartable() {
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME)
+    fun flagOn_startsFromCoreStartable() {
         underTest.start()
         assertThat(underTest.initialized).isTrue()
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
-    fun simpleFragment_throwsIfInitializeIsCalled() {
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME)
+    fun flagOn_throwsIfInitializeIsCalled() {
         assertThrows(IllegalStateException::class.java) { underTest.initializeStatusBar() }
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
-    fun simpleFragment_flagEnabled_doesNotCreateFragment() {
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME)
+    fun flagOn_flagEnabled_doesNotCreateFragment() {
         underTest.start()
 
         verify(fragmentManager, never()).beginTransaction()
@@ -103,14 +102,14 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     fun flagOff_startCalled_stillInitializes() {
         underTest.start()
         assertThat(underTest.initialized).isTrue()
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     fun flagOff_doesNotThrowIfInitializeIsCalled() {
         underTest.initializeStatusBar()
         assertThat(underTest.initialized).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index 3b5d358..c4b1b84 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -16,8 +16,8 @@
 
 package com.android.systemui.statusbar.notification
 
+import android.platform.test.flag.junit.FlagsParameterization
 import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
@@ -26,11 +26,17 @@
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.keyguard.domain.interactor.pulseExpansionInteractor
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ShadeViewController.Companion.WAKEUP_ANIMATION_DELAY_MS
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
@@ -41,9 +47,6 @@
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
 import com.android.systemui.statusbar.policy.HeadsUpManager
 import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.mockito.withArgCaptor
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,16 +58,21 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.verifyNoMoreInteractions
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidJUnit4::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
-class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
+class NotificationWakeUpCoordinatorTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     @get:Rule val animatorTestRule = AnimatorTestRule(this)
 
@@ -105,6 +113,18 @@
         statusBarStateCallback.onDozeAmountChanged(dozeAmount, dozeAmount)
     }
 
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     @Before
     fun setup() {
         whenever(bypassController.bypassEnabled).then { bypassEnabled }
@@ -178,6 +198,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun setDozeToZeroWhenCommunalShowingWillFullyHideNotifications() =
         testScope.runTest {
             val transitionState =
@@ -192,6 +213,17 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun setDozeToZeroWhenCommunalShowingWillFullyHideNotifications_withSceneContainer() =
+        testScope.runTest {
+            kosmos.setSceneTransition(Idle(Scenes.Communal))
+            setDozeAmount(0f)
+            verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+            assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+        }
+
+    @Test
+    @DisableSceneContainer
     fun closingCommunalWillShowNotifications() =
         testScope.runTest {
             val transitionState =
@@ -211,6 +243,20 @@
         }
 
     @Test
+    @EnableSceneContainer
+    fun closingCommunalWillShowNotifications_withSceneContainer() =
+        testScope.runTest {
+            kosmos.setSceneTransition(Idle(Scenes.Communal))
+            setDozeAmount(0f)
+            verifyStackScrollerDozeAndHideAmount(dozeAmount = 1f, hideAmount = 1f)
+            assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isTrue()
+
+            kosmos.setSceneTransition(Idle(CommunalScenes.Blank))
+            verifyStackScrollerDozeAndHideAmount(dozeAmount = 0f, hideAmount = 0f)
+            assertThat(notificationWakeUpCoordinator.notificationsFullyHidden).isFalse()
+        }
+
+    @Test
     fun switchingToShadeWithBypassEnabledWillShowNotifications() {
         setDozeToZeroWithBypassWillFullyHideNotifications()
         clearInvocations(stackScrollerController)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
index 740abf3..76390fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerAlwaysOnDisplayViewModelTest.kt
@@ -18,12 +18,13 @@
 
 import android.content.res.mainResources
 import android.platform.test.annotations.DisableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -51,17 +52,20 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class NotificationIconContainerAlwaysOnDisplayViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationIconContainerAlwaysOnDisplayViewModelTest(flags: FlagsParameterization) :
+    SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
             fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, value = false) }
         }
 
-    val underTest =
+    val underTest by lazy {
         NotificationIconContainerAlwaysOnDisplayViewModel(
             kosmos.testDispatcher,
             kosmos.alwaysOnDisplayNotificationIconsInteractor,
@@ -70,11 +74,24 @@
             kosmos.mainResources,
             kosmos.shadeInteractor,
         )
+    }
     val testScope = kosmos.testScope
     val keyguardRepository = kosmos.fakeKeyguardRepository
     val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
     val powerRepository = kosmos.fakePowerRepository
 
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     @Before
     fun setup() {
         keyguardRepository.setKeyguardShowing(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 9dcbe1b..ff0321b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -99,6 +99,7 @@
         whenever(mainLooper.isCurrentThread).thenReturn(true)
         whenever(mainLooper.thread).thenReturn(thread)
         whenever(thread.name).thenReturn("backgroundThread")
+        whenever(context.applicationContext).thenReturn(context)
         whenever(context.resources).thenReturn(resources)
         whenever(context.mainExecutor).thenReturn(mContext.mainExecutor)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index b03c679..9b47ead 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,13 +17,16 @@
 
 package com.android.systemui.user.data.repository
 
+import android.app.admin.devicePolicyManager
 import android.content.pm.UserInfo
+import android.internal.statusbar.fakeStatusBarService
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.kosmos.useUnconfinedTestDispatcher
@@ -57,6 +60,9 @@
     private val testDispatcher = kosmos.testDispatcher
     private val testScope = kosmos.testScope
     private val globalSettings = kosmos.fakeGlobalSettings
+    private val broadcastDispatcher = kosmos.broadcastDispatcher
+    private val devicePolicyManager = kosmos.devicePolicyManager
+    private val statusBarService = kosmos.fakeStatusBarService
 
     @Mock private lateinit var manager: UserManager
 
@@ -317,6 +323,10 @@
             backgroundDispatcher = testDispatcher,
             globalSettings = globalSettings,
             tracker = tracker,
+            broadcastDispatcher = broadcastDispatcher,
+            devicePolicyManager = devicePolicyManager,
+            resources = context.resources,
+            statusBarService = statusBarService,
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
new file mode 100644
index 0000000..f70b426
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.user.domain.interactor
+
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserLogoutInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+    private val userRepository = kosmos.fakeUserRepository
+    private val testScope = kosmos.testScope
+
+    private val underTest = kosmos.userLogoutInteractor
+
+    @Before
+    fun setUp() {
+        userRepository.setUserInfos(USER_INFOS)
+        runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[2]) }
+        userRepository.setLogoutToSystemUserEnabled(false)
+        userRepository.setSecondaryUserLogoutEnabled(false)
+    }
+
+    @Test
+    fun logOut_doesNothing_whenBothLogoutOptionsAreDisabled() {
+        testScope.runTest {
+            val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+            val secondaryUserLogoutCount = userRepository.logOutSecondaryUserCallCount
+            val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
+            assertThat(isLogoutEnabled).isFalse()
+            underTest.logOut()
+            assertThat(userRepository.logOutSecondaryUserCallCount)
+                .isEqualTo(secondaryUserLogoutCount)
+            assertThat(userRepository.logOutToSystemUserCallCount)
+                .isEqualTo(logoutToSystemUserCount)
+        }
+    }
+
+    @Test
+    fun logOut_logsOutSecondaryUser_whenAdminEnabledSecondaryLogout() {
+        testScope.runTest {
+            val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+            val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+            val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
+            userRepository.setSecondaryUserLogoutEnabled(true)
+            assertThat(isLogoutEnabled).isTrue()
+            underTest.logOut()
+            assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1)
+            assertThat(userRepository.logOutToSystemUserCallCount)
+                .isEqualTo(logoutToSystemUserCount)
+        }
+    }
+
+    @Test
+    fun logOut_logsOutToSystemUser_whenLogoutToSystemUserIsEnabled() {
+        testScope.runTest {
+            val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+            val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+            val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
+            userRepository.setLogoutToSystemUserEnabled(true)
+            assertThat(isLogoutEnabled).isTrue()
+            underTest.logOut()
+            assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount)
+            assertThat(userRepository.logOutToSystemUserCallCount)
+                .isEqualTo(logoutToSystemUserCount + 1)
+        }
+    }
+
+    @Test
+    fun logOut_secondaryUserTakesPrecedence() {
+        testScope.runTest {
+            val isLogoutEnabled by collectLastValue(underTest.isLogoutEnabled)
+            val lastLogoutCount = userRepository.logOutSecondaryUserCallCount
+            val logoutToSystemUserCount = userRepository.logOutToSystemUserCallCount
+            userRepository.setLogoutToSystemUserEnabled(true)
+            userRepository.setSecondaryUserLogoutEnabled(true)
+            assertThat(isLogoutEnabled).isTrue()
+            underTest.logOut()
+            assertThat(userRepository.logOutSecondaryUserCallCount).isEqualTo(lastLogoutCount + 1)
+            assertThat(userRepository.logOutToSystemUserCallCount)
+                .isEqualTo(logoutToSystemUserCount)
+        }
+    }
+
+    companion object {
+        private val USER_INFOS =
+            listOf(
+                UserInfo(0, "System user", 0),
+                UserInfo(10, "Regular user", 0),
+                UserInfo(11, "Secondary user", 0),
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
index e88dbd2..ad473c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -16,152 +16,16 @@
 
 package com.android.systemui.util.settings.repository
 
-import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.coroutines.collectValues
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
-import com.android.systemui.util.settings.fakeSettings
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
+class UserAwareSecureSettingsRepositoryTest : UserAwareSettingsRepositoryTestBase() {
 
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val secureSettings = kosmos.fakeSettings
-    private val userRepository = kosmos.fakeUserRepository
-    private lateinit var underTest: UserAwareSecureSettingsRepository
-
-    @Before
-    fun setup() {
-        underTest = kosmos.userAwareSecureSettingsRepository
-
-        userRepository.setUserInfos(USER_INFOS)
-
-        secureSettings.putBoolForUser(BOOL_SETTING_NAME, true, USER_1.id)
-        secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_2.id)
-        secureSettings.putIntForUser(INT_SETTING_NAME, 1337, USER_1.id)
-        secureSettings.putIntForUser(INT_SETTING_NAME, 818, USER_2.id)
-    }
-
-    @Test
-    fun boolSetting_emitsInitialValue() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(USER_1)
-
-            val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
-
-            assertThat(enabled).isTrue()
-        }
-    }
-
-    @Test
-    fun boolSetting_whenSettingChanges_emitsNewValue() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(USER_1)
-            val enabled by collectValues(underTest.boolSetting(BOOL_SETTING_NAME, false))
-            runCurrent()
-
-            secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_1.id)
-
-            assertThat(enabled).containsExactly(true, false).inOrder()
-        }
-    }
-
-    @Test
-    fun boolSetting_whenWhenUserChanges_emitsNewValue() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(USER_1)
-            val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
-            runCurrent()
-
-            userRepository.setSelectedUserInfo(USER_2)
-
-            assertThat(enabled).isFalse()
-        }
-    }
-
-    @Test
-    fun intSetting_emitsInitialValue() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(USER_1)
-
-            val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
-
-            assertThat(number).isEqualTo(1337)
-        }
-    }
-
-    @Test
-    fun intSetting_whenSettingChanges_emitsNewValue() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(USER_1)
-            val number by collectValues(underTest.intSetting(INT_SETTING_NAME, 0))
-            runCurrent()
-
-            secureSettings.putIntForUser(INT_SETTING_NAME, 1338, USER_1.id)
-
-            assertThat(number).containsExactly(1337, 1338).inOrder()
-        }
-    }
-
-    @Test
-    fun intSetting_whenWhenUserChanges_emitsNewValue() {
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(USER_1)
-            val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
-            runCurrent()
-
-            userRepository.setSelectedUserInfo(USER_2)
-
-            assertThat(number).isEqualTo(818)
-        }
-    }
-
-    @Test
-    fun getInt_returnsInitialValue() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(USER_1)
-
-            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(1337)
-        }
-
-    @Test
-    fun getInt_whenSettingChanges_returnsNewValue() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(USER_1)
-            secureSettings.putIntForUser(INT_SETTING_NAME, 999, USER_1.id)
-
-            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(999)
-        }
-
-    @Test
-    fun getInt_whenUserChanges_returnsThatUserValue() =
-        testScope.runTest {
-            userRepository.setSelectedUserInfo(USER_2)
-
-            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(818)
-        }
-
-    private companion object {
-        const val BOOL_SETTING_NAME = "BOOL_SETTING_NAME"
-        const val INT_SETTING_NAME = "INT_SETTING_NAME"
-        val USER_1 = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
-        val USER_2 = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
-        val USER_INFOS = listOf(USER_1, USER_2)
+    override fun getKosmosUserAwareSettingsRepository(): UserAwareSettingsRepository {
+        return kosmos.userAwareSecureSettingsRepository
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepositoryTestBase.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepositoryTestBase.kt
new file mode 100644
index 0000000..09db96f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepositoryTestBase.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.util.settings.repository
+
+import android.content.pm.UserInfo
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class UserAwareSettingsRepositoryTestBase : SysuiTestCase() {
+
+    protected val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    protected val secureSettings = kosmos.fakeSettings
+    protected val userRepository = kosmos.fakeUserRepository
+    private lateinit var underTest: UserAwareSettingsRepository
+
+    @Before
+    fun setup() {
+        underTest = getKosmosUserAwareSettingsRepository()
+
+        userRepository.setUserInfos(USER_INFOS)
+
+        secureSettings.putBoolForUser(BOOL_SETTING_NAME, true, USER_1.id)
+        secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_2.id)
+        secureSettings.putIntForUser(INT_SETTING_NAME, 1337, USER_1.id)
+        secureSettings.putIntForUser(INT_SETTING_NAME, 818, USER_2.id)
+    }
+
+    abstract fun getKosmosUserAwareSettingsRepository(): UserAwareSettingsRepository
+
+    @Test
+    fun boolSetting_emitsInitialValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+
+            val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
+
+            assertThat(enabled).isTrue()
+        }
+    }
+
+    @Test
+    fun boolSetting_whenSettingChanges_emitsNewValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            val enabled by collectValues(underTest.boolSetting(BOOL_SETTING_NAME, false))
+            runCurrent()
+
+            secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_1.id)
+
+            assertThat(enabled).containsExactly(true, false).inOrder()
+        }
+    }
+
+    @Test
+    fun boolSetting_whenWhenUserChanges_emitsNewValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
+            runCurrent()
+
+            userRepository.setSelectedUserInfo(USER_2)
+
+            assertThat(enabled).isFalse()
+        }
+    }
+
+    @Test
+    fun intSetting_emitsInitialValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+
+            val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+
+            assertThat(number).isEqualTo(1337)
+        }
+    }
+
+    @Test
+    fun intSetting_whenSettingChanges_emitsNewValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            val number by collectValues(underTest.intSetting(INT_SETTING_NAME, 0))
+            runCurrent()
+
+            secureSettings.putIntForUser(INT_SETTING_NAME, 1338, USER_1.id)
+
+            assertThat(number).containsExactly(1337, 1338).inOrder()
+        }
+    }
+
+    @Test
+    fun intSetting_whenWhenUserChanges_emitsNewValue() {
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+            runCurrent()
+
+            userRepository.setSelectedUserInfo(USER_2)
+
+            assertThat(number).isEqualTo(818)
+        }
+    }
+
+    @Test
+    fun getInt_returnsInitialValue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+
+            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(1337)
+        }
+
+    @Test
+    fun getInt_whenSettingChanges_returnsNewValue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_1)
+            secureSettings.putIntForUser(INT_SETTING_NAME, 999, USER_1.id)
+
+            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(999)
+        }
+
+    @Test
+    fun getInt_whenUserChanges_returnsThatUserValue() =
+        testScope.runTest {
+            userRepository.setSelectedUserInfo(USER_2)
+
+            assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(818)
+        }
+
+    private companion object {
+        const val BOOL_SETTING_NAME = "BOOL_SETTING_NAME"
+        const val INT_SETTING_NAME = "INT_SETTING_NAME"
+        val USER_1 = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+        val USER_2 = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+        val USER_INFOS = listOf(USER_1, USER_2)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepositoryTest.kt
new file mode 100644
index 0000000..586da8e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepositoryTest.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.util.settings.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.util.settings.data.repository.userAwareSystemSettingsRepository
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserAwareSystemSettingsRepositoryTest : UserAwareSettingsRepositoryTestBase() {
+
+    override fun getKosmosUserAwareSettingsRepository(): UserAwareSettingsRepository {
+        return kosmos.userAwareSystemSettingsRepository
+    }
+}
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 82c8c44..0854eb4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1086,4 +1086,9 @@
     enable the desktop specific features.
     -->
     <bool name="config_enableDesktopFeatureSet">false</bool>
+
+    <!--
+    Whether the user switching can only happen by logging out and going through the system user (login screen).
+    -->
+    <bool name="config_userSwitchingMustGoThroughLoginScreen">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 5a8417d..53ab686 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -794,6 +794,7 @@
     <!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] -->
     <string name="quick_settings_bluetooth_secondary_label_transient">Turning on&#8230;</string>
     <!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_brightness_unable_adjust_msg">Can\'t adjust brightness because it\'s being\n controlled by the top app</string>
     <!-- QuickSettings: Rotation Unlocked [CHAR LIMIT=NONE] -->
     <string name="quick_settings_rotation_unlocked_label">Auto-rotate</string>
     <!-- Accessibility label for Auto-ratate QuickSettings tile [CHAR LIMIT=NONE] -->
@@ -3743,6 +3744,11 @@
          is a component that shows the user which keyboard shortcuts they can use.
          [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_customize_mode_title">Customize keyboard shortcuts</string>
+    <!-- Sub title at the top of the keyboard shortcut helper customization dialog. Explains to the
+         user what action they need to take in the customization dialog to assign a new custom shortcut.
+         The helper is a component that shows the user which keyboard shortcuts they can use.
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_customize_mode_sub_title">Press key to assign shortcut</string>
     <!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
          hasn't typed in anything in the search box yet. The helper is a  component that shows the
          user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3754,6 +3760,16 @@
          use. The helper shows shortcuts in categories, which can be collapsed or expanded.
          [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_content_description_collapse_icon">Collapse icon</string>
+    <!-- Content description of the Meta key (also called Action Key) icon that prompts users to
+         press some key combination starting with meta key to assign new key combination to shortcut
+         in shortcut helper customization dialog. The helper is a  component that shows the  user
+         which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_content_description_meta_key">Action or Meta key icon</string>
+    <!-- Content description of the plus icon after the meta key icon prompts users to
+         press some key combination starting with meta key to assign new key combination to shortcut
+         in shortcut helper customization dialog. The helper is a  component that shows the  user
+         which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_content_description_plus_icon">Plus icon</string>
     <!-- Description text of the button that allows user to customize shortcuts in keyboard
          shortcut helper The helper is a  component that shows the  user which keyboard shortcuts
          they can use. [CHAR LIMIT=NONE] -->
@@ -3784,6 +3800,24 @@
          open keyboard settings while in shortcut helper. The helper is a  component that shows the
          user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
     <string name="shortcut_helper_keyboard_settings_buttons_label">Keyboard Settings</string>
+    <!-- Label on the set shortcut button in keyboard shortcut helper customize dialog, that allows user to
+         confirm and assign key combination to selected shortcut. The helper is a  component that
+         shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_customize_dialog_set_shortcut_button_label">Set shortcut</string>
+    <!-- Label on the cancel button in keyboard shortcut helper customize dialog, that allows user to
+         cancel and exit shortcut customization dialog, returning to the main shortcut helper page.
+         The helper is a  component that shows the user which keyboard shortcuts they can use.
+         [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_customize_dialog_cancel_button_label">Cancel</string>
+    <!-- Placeholder text, prompting user to Press key combination assign to shortcut. This is shown
+         in shortcut helper's "Add Custom Shortcut" Dialog text field when user hasn't pressed
+         any key  yet. The helper is a component that shows the user which keyboard shortcuts
+         they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_add_shortcut_dialog_placeholder">Press key</string>
+    <!-- Error message displayed when the user select a key combination that is already in use while
+         assigning a new custom key combination to a shortcut in shortcut helper. The helper is a
+         component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+    <string name="shortcut_helper_customize_dialog_error_message">Key combination already in use. Try another key.</string>
 
 
     <!-- Keyboard touchpad tutorial scheduler-->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 6209ed8..e332280 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -21,6 +21,7 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import com.android.internal.util.ScreenshotRequest;
 
@@ -102,9 +103,9 @@
     oneway void expandNotificationPanel() = 29;
 
     /**
-     * Notifies SystemUI to invoke Back.
+     * Notifies SystemUI of a back KeyEvent.
      */
-    oneway void onBackPressed() = 44;
+    oneway void onBackEvent(in KeyEvent keyEvent) = 44;
 
     /** Sets home rotation enabled. */
     oneway void setHomeRotationEnabled(boolean enabled) = 45;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f98890e..8ca0e80 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -219,7 +219,6 @@
     private static final int MSG_USER_UNLOCKED = 334;
     private static final int MSG_ASSISTANT_STACK_CHANGED = 335;
     private static final int MSG_BIOMETRIC_AUTHENTICATION_CONTINUE = 336;
-    private static final int MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED = 337;
     private static final int MSG_TELEPHONY_CAPABLE = 338;
     private static final int MSG_TIMEZONE_UPDATE = 339;
     private static final int MSG_USER_STOPPED = 340;
@@ -402,7 +401,6 @@
     protected int mFingerprintRunningState = BIOMETRIC_STATE_STOPPED;
     private boolean mFingerprintDetectRunning;
     private boolean mIsDreaming;
-    private boolean mLogoutEnabled;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     private final FingerprintInteractiveToAuthProvider mFingerprintInteractiveToAuthProvider;
 
@@ -1739,9 +1737,6 @@
                         mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
             } else if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
                 mHandler.sendEmptyMessage(MSG_SIM_SUBSCRIPTION_INFO_CHANGED);
-            } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
-                    action)) {
-                mHandler.sendEmptyMessage(MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED);
             }
         }
     };
@@ -2328,9 +2323,6 @@
                     case MSG_BIOMETRIC_AUTHENTICATION_CONTINUE:
                         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
                         break;
-                    case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
-                        updateLogoutEnabled();
-                        break;
                     case MSG_TELEPHONY_CAPABLE:
                         updateTelephonyCapable((boolean) msg.obj);
                         break;
@@ -2496,7 +2488,6 @@
         boolean isUserUnlocked = mUserManager.isUserUnlocked(user);
         mLogger.logUserUnlockedInitialState(user, isUserUnlocked);
         mUserIsUnlocked.put(user, isUserUnlocked);
-        mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
         updateSecondaryLockscreenRequirement(user);
         List<UserInfo> allUsers = mUserManager.getUsers();
         for (UserInfo userInfo : allUsers) {
@@ -4060,28 +4051,6 @@
         return null; // not found
     }
 
-    /**
-     * @return a cached version of DevicePolicyManager.isLogoutEnabled()
-     */
-    public boolean isLogoutEnabled() {
-        return mLogoutEnabled;
-    }
-
-    private void updateLogoutEnabled() {
-        Assert.isMainThread();
-        boolean logoutEnabled = mDevicePolicyManager.isLogoutEnabled();
-        if (mLogoutEnabled != logoutEnabled) {
-            mLogoutEnabled = logoutEnabled;
-
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
-                if (cb != null) {
-                    cb.onLogoutEnabledChanged();
-                }
-            }
-        }
-    }
-
     protected int getBiometricLockoutDelay() {
         return BIOMETRIC_LOCKOUT_RESET_DELAY_MS;
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 7ac5ac2..fdee21b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -286,11 +286,6 @@
     public void onTrustAgentErrorMessage(CharSequence message) { }
 
     /**
-     * Called when a value of logout enabled is change.
-     */
-    public void onLogoutEnabledChanged() { }
-
-    /**
      * Called when authenticated fingerprint biometrics are cleared.
      */
     public void onFingerprintsCleared() { }
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
index 06d3917..6c78b8b 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
@@ -19,6 +19,7 @@
 import android.annotation.SuppressLint
 import android.hardware.display.BrightnessInfo
 import android.hardware.display.DisplayManager
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.brightness.shared.model.BrightnessLog
 import com.android.systemui.brightness.shared.model.LinearBrightness
 import com.android.systemui.brightness.shared.model.formatBrightness
@@ -46,7 +47,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 /**
@@ -64,6 +64,9 @@
     /** Current maximum value for the brightness */
     val maxLinearBrightness: Flow<LinearBrightness>
 
+    /** Whether the current brightness value is overridden by the application window */
+    val isBrightnessOverriddenByWindow: StateFlow<Boolean>
+
     /** Gets the current values for min and max brightness */
     suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness>
 
@@ -90,10 +93,7 @@
     @Background private val backgroundContext: CoroutineContext,
 ) : ScreenBrightnessRepository {
 
-    private val apiQueue =
-        Channel<SetBrightnessMethod>(
-            capacity = UNLIMITED,
-        )
+    private val apiQueue = Channel<SetBrightnessMethod>(capacity = UNLIMITED)
 
     init {
         applicationScope.launch(context = backgroundContext) {
@@ -132,7 +132,8 @@
                 displayManager.registerDisplayListener(
                     listener,
                     null,
-                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS,
+                    /* eventFlags */ 0,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
                 )
 
                 awaitClose { displayManager.unregisterDisplayListener(listener) }
@@ -180,6 +181,11 @@
             .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_BRIGHTNESS, null)
             .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f))
 
+    override val isBrightnessOverriddenByWindow = brightnessInfo
+        .filterNotNull()
+        .map { it.isBrightnessOverrideByWindow }
+        .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+
     override fun setTemporaryBrightness(value: LinearBrightness) {
         apiQueue.trySend(SetBrightnessMethod.Temporary(value))
     }
@@ -190,8 +196,10 @@
 
     private sealed interface SetBrightnessMethod {
         val value: LinearBrightness
+
         @JvmInline
         value class Temporary(override val value: LinearBrightness) : SetBrightnessMethod
+
         @JvmInline
         value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod
     }
@@ -201,7 +209,7 @@
             LOG_BUFFER_BRIGHTNESS_CHANGE_TAG,
             if (permanent) LogLevel.DEBUG else LogLevel.VERBOSE,
             { str1 = value.formatBrightness() },
-            { "Change requested: $str1" }
+            { "Change requested: $str1" },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
index 5647f521..b794c5c 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
@@ -25,12 +25,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.table.TableLogBuffer
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
 
 /**
  * Converts between [GammaBrightness] and [LinearBrightness].
@@ -68,6 +68,8 @@
                 .stateIn(applicationScope, SharingStarted.WhileSubscribed(), GammaBrightness(0))
         }
 
+    val brightnessOverriddenByWindow = screenBrightnessRepository.isBrightnessOverriddenByWindow
+
     /** Sets the brightness temporarily, while the user is changing it. */
     suspend fun setTemporaryBrightness(gammaBrightness: GammaBrightness) {
         screenBrightnessRepository.setTemporaryBrightness(
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index 02161d2..917a4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -19,6 +19,7 @@
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.interaction.DragInteraction
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -27,8 +28,10 @@
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
@@ -38,6 +41,7 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.colorResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
@@ -62,6 +66,7 @@
 
 @Composable
 private fun BrightnessSlider(
+    viewModel: BrightnessSliderViewModel,
     gammaValue: Int,
     valueRange: IntRange,
     label: Text.Resource,
@@ -97,21 +102,31 @@
             null
         }
 
+    val overriddenByAppState by if (Flags.showToastWhenAppControlBrightness()) {
+        viewModel.brightnessOverriddenByWindow.collectAsStateWithLifecycle()
+    } else {
+        mutableStateOf(false)
+    }
+
     PlatformSlider(
         value = animatedValue,
         valueRange = floatValueRange,
         enabled = !isRestricted,
         onValueChange = {
             if (!isRestricted) {
-                hapticsViewModel?.onValueChange(it)
-                value = it.toInt()
-                onDrag(value)
+                if (!overriddenByAppState) {
+                    hapticsViewModel?.onValueChange(it)
+                    value = it.toInt()
+                    onDrag(value)
+                }
             }
         },
         onValueChangeFinished = {
             if (!isRestricted) {
-                hapticsViewModel?.onValueChangeEnded()
-                onStop(value)
+                if (!overriddenByAppState) {
+                    hapticsViewModel?.onValueChangeEnded()
+                    onStop(value)
+                }
             }
         },
         modifier =
@@ -136,6 +151,21 @@
         },
         interactionSource = interactionSource,
     )
+    // Showing the warning toast if the current running app window has controlled the
+    // brightness value.
+    if (Flags.showToastWhenAppControlBrightness()) {
+        val context = LocalContext.current
+        LaunchedEffect(interactionSource) {
+            interactionSource.interactions.collect { interaction ->
+                if (interaction is DragInteraction.Start && overriddenByAppState) {
+                    viewModel.showToast(
+                        context,
+                        R.string.quick_settings_brightness_unable_adjust_msg
+                    )
+                }
+            }
+        }
+    }
 }
 
 private val sliderBackgroundFrameSize = 8.dp
@@ -167,6 +197,7 @@
 
     Box(modifier = modifier.fillMaxWidth().sysuiResTag("brightness_slider")) {
         BrightnessSlider(
+            viewModel = viewModel,
             gammaValue = gamma,
             valueRange = viewModel.minBrightness.value..viewModel.maxBrightness.value,
             label = viewModel.label,
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
index a61ce8f..1630ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
@@ -17,7 +17,8 @@
 package com.android.systemui.brightness.ui.viewmodel
 
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.setValue
+import android.content.Context
+import androidx.annotation.StringRes
 import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor
 import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor
 import com.android.systemui.brightness.shared.model.GammaBrightness
@@ -29,6 +30,7 @@
 import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.res.R
 import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
+import com.android.systemui.settings.brightness.ui.BrightnessWarningToast
 import com.android.systemui.utils.PolicyRestriction
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -51,6 +53,7 @@
     val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
     private val brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor,
     @Assisted private val supportsMirroring: Boolean,
+    private val brightnessWarningToast: BrightnessWarningToast,
 ) : ExclusiveActivatable() {
 
     private val hydrator = Hydrator("BrightnessSliderViewModel.hydrator")
@@ -75,6 +78,15 @@
         brightnessPolicyEnforcementInteractor.startAdminSupportDetailsDialog(restriction)
     }
 
+    val brightnessOverriddenByWindow = screenBrightnessInteractor.brightnessOverriddenByWindow
+
+    fun showToast(viewContext: Context, @StringRes resId: Int) {
+        if (brightnessWarningToast.isToastActive()) {
+            return
+        }
+        brightnessWarningToast.show(viewContext, resId)
+    }
+
     /**
      * As a brightness slider is dragged, the corresponding events should be sent using this method.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 034cb31..1fa829a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -264,7 +264,8 @@
                 displayManager.registerDisplayListener(
                     callback,
                     backgroundHandler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
+                    /* eventFlags */ 0,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED,
                 )
                 awaitClose { displayManager.unregisterDisplayListener(callback) }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 162047b..91b44e7 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -36,7 +36,6 @@
 import android.app.IActivityManager;
 import android.app.StatusBarManager;
 import android.app.WallpaperManager;
-import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -138,6 +137,7 @@
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor;
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.settings.GlobalSettings;
@@ -197,7 +197,6 @@
     private final Context mContext;
     private final GlobalActionsManager mWindowManagerFuncs;
     private final AudioManager mAudioManager;
-    private final DevicePolicyManager mDevicePolicyManager;
     private final LockPatternUtils mLockPatternUtils;
     private final SelectedUserInteractor mSelectedUserInteractor;
     private final TelephonyListenerManager mTelephonyListenerManager;
@@ -260,6 +259,7 @@
     private final ShadeController mShadeController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final DialogTransitionAnimator mDialogTransitionAnimator;
+    private final UserLogoutInteractor mLogoutInteractor;
     private final GlobalActionsInteractor mInteractor;
 
     @VisibleForTesting
@@ -344,7 +344,6 @@
             Context context,
             GlobalActionsManager windowManagerFuncs,
             AudioManager audioManager,
-            DevicePolicyManager devicePolicyManager,
             LockPatternUtils lockPatternUtils,
             BroadcastDispatcher broadcastDispatcher,
             TelephonyListenerManager telephonyListenerManager,
@@ -376,11 +375,11 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             DialogTransitionAnimator dialogTransitionAnimator,
             SelectedUserInteractor selectedUserInteractor,
+            UserLogoutInteractor logoutInteractor,
             GlobalActionsInteractor interactor) {
         mContext = context;
         mWindowManagerFuncs = windowManagerFuncs;
         mAudioManager = audioManager;
-        mDevicePolicyManager = devicePolicyManager;
         mLockPatternUtils = lockPatternUtils;
         mTelephonyListenerManager = telephonyListenerManager;
         mKeyguardStateController = keyguardStateController;
@@ -412,6 +411,7 @@
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mDialogTransitionAnimator = dialogTransitionAnimator;
         mSelectedUserInteractor = selectedUserInteractor;
+        mLogoutInteractor = logoutInteractor;
         mInteractor = interactor;
 
         // receive broadcasts
@@ -639,12 +639,7 @@
             } else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
                 addIfShouldShowAction(tempActions, new ScreenshotAction());
             } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
-                // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of
-                // hardcode it to USER_SYSTEM so it properly supports headless system user mode
-                // (and then call mDevicePolicyManager.clearLogoutUser() after switched)
-                if (mDevicePolicyManager.isLogoutEnabled()
-                        && currentUser.get() != null
-                        && currentUser.get().id != UserHandle.USER_SYSTEM) {
+                if (mLogoutInteractor.isLogoutEnabled().getValue()) {
                     addIfShouldShowAction(tempActions, new LogoutAction());
                 }
             } else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
@@ -1134,7 +1129,7 @@
             // Add a little delay before executing, to give the dialog a chance to go away before
             // switching user
             mHandler.postDelayed(() -> {
-                mDevicePolicyManager.logoutUser();
+                mLogoutInteractor.logOut();
             }, mDialogPressDelay);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
new file mode 100644
index 0000000..85d2214
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutCustomizationInteractor.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.keyboard.shortcut.domain.interactor
+
+import android.view.KeyEvent.META_META_ON
+import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperKeys
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import javax.inject.Inject
+
+class ShortcutCustomizationInteractor @Inject constructor() {
+    fun getDefaultCustomShortcutModifierKey(): ShortcutKey.Icon.ResIdIcon {
+        return ShortcutKey.Icon.ResIdIcon(ShortcutHelperKeys.keyIcons[META_META_ON]!!)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
new file mode 100644
index 0000000..e4ccc2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutInfo.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.keyboard.shortcut.shared.model
+
+data class ShortcutInfo(
+    val label: String,
+    val categoryType: ShortcutCategoryType,
+    val subCategoryLabel: String,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogDelegate.kt
new file mode 100644
index 0000000..c98472e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogDelegate.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.keyboard.shortcut.ui
+
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.statusbar.phone.DialogDelegate
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+class ShortcutCustomizationDialogDelegate : DialogDelegate<SystemUIDialog> {
+
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        super.onCreate(dialog, savedInstanceState)
+        dialog.window?.apply { setGravity(Gravity.CENTER) }
+    }
+
+    override fun getWidth(dialog: SystemUIDialog): Int {
+        return WindowManager.LayoutParams.WRAP_CONTENT
+    }
+
+    override fun getHeight(dialog: SystemUIDialog): Int {
+        return WindowManager.LayoutParams.WRAP_CONTENT
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
new file mode 100644
index 0000000..02e206e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.keyboard.shortcut.ui
+
+import android.app.Dialog
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
+import com.android.systemui.keyboard.shortcut.ui.composable.AssignNewShortcutDialog
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.statusbar.phone.SystemUIDialogFactory
+import com.android.systemui.statusbar.phone.create
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+class ShortcutCustomizationDialogStarter
+@AssistedInject
+constructor(
+    viewModelFactory: ShortcutCustomizationViewModel.Factory,
+    private val dialogFactory: SystemUIDialogFactory,
+) : ExclusiveActivatable() {
+
+    private var dialog: Dialog? = null
+    private val viewModel = viewModelFactory.create()
+
+    override suspend fun onActivated(): Nothing {
+        viewModel.shortcutCustomizationUiState.collect { uiState ->
+            if (
+                uiState is ShortcutCustomizationUiState.AddShortcutDialog &&
+                    !uiState.isDialogShowing
+            ) {
+                dialog = createAddShortcutDialog().also { it.show() }
+                viewModel.onAddShortcutDialogShown()
+            } else if (uiState is ShortcutCustomizationUiState.Inactive) {
+                dialog?.dismiss()
+                dialog = null
+            }
+        }
+    }
+
+    fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) {
+        viewModel.onAddShortcutDialogRequested(shortcutBeingCustomized)
+    }
+
+    private fun createAddShortcutDialog(): Dialog {
+        return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog
+            ->
+            val uiState by viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle()
+            AssignNewShortcutDialog(
+                uiState = uiState,
+                modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp),
+                onKeyPress = { viewModel.onKeyPressed(it) },
+                onCancel = { dialog.dismiss() },
+            )
+            dialog.setOnDismissListener { viewModel.onAddShortcutDialogDismissed() }
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): ShortcutCustomizationDialogStarter
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
index d33ab2a..807c70b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperDialogStarter.kt
@@ -24,6 +24,7 @@
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
@@ -47,15 +48,18 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val viewModel: ShortcutHelperViewModel,
+    private val shortcutHelperViewModel: ShortcutHelperViewModel,
+    shortcutCustomizationDialogStarterFactory: ShortcutCustomizationDialogStarter.Factory,
     private val dialogFactory: SystemUIDialogFactory,
     private val activityStarter: ActivityStarter,
 ) : CoreStartable {
 
     @VisibleForTesting var dialog: Dialog? = null
+    private val shortcutCustomizationDialogStarter =
+        shortcutCustomizationDialogStarterFactory.create()
 
     override fun start() {
-        viewModel.shouldShow
+        shortcutHelperViewModel.shouldShow
             .map { shouldShow ->
                 if (shouldShow) {
                     dialog = createShortcutHelperDialog().also { it.show() }
@@ -69,16 +73,21 @@
     private fun createShortcutHelperDialog(): Dialog {
         return dialogFactory.createBottomSheet(
             content = { dialog ->
-                val shortcutsUiState by viewModel.shortcutsUiState.collectAsStateWithLifecycle()
+                val shortcutsUiState by
+                    shortcutHelperViewModel.shortcutsUiState.collectAsStateWithLifecycle()
+                LaunchedEffect(Unit) { shortcutCustomizationDialogStarter.activate() }
                 ShortcutHelper(
                     modifier = Modifier.width(getWidth()),
                     shortcutsUiState = shortcutsUiState,
                     onKeyboardSettingsClicked = { onKeyboardSettingsClicked(dialog) },
-                    onSearchQueryChanged = { viewModel.onSearchQueryChanged(it) },
+                    onSearchQueryChanged = { shortcutHelperViewModel.onSearchQueryChanged(it) },
+                    onCustomizationRequested = {
+                        shortcutCustomizationDialogStarter.onAddShortcutDialogRequested(it)
+                    },
                 )
-                dialog.setOnDismissListener { viewModel.onViewClosed() }
+                dialog.setOnDismissListener { shortcutHelperViewModel.onViewClosed() }
             },
-            maxWidth = ShortcutHelperBottomSheet.LargeScreenWidthLandscape
+            maxWidth = ShortcutHelperBottomSheet.LargeScreenWidthLandscape,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
new file mode 100644
index 0000000..43f0f20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.keyboard.shortcut.ui.composable
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.collectIsFocusedAsState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
+import androidx.compose.material.icons.filled.ErrorOutline
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.onPreviewKeyEvent
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import com.android.systemui.res.R
+
+@Composable
+fun AssignNewShortcutDialog(
+    uiState: ShortcutCustomizationUiState,
+    modifier: Modifier = Modifier,
+    onKeyPress: (KeyEvent) -> Boolean,
+    onCancel: () -> Unit,
+) {
+    if (uiState is ShortcutCustomizationUiState.AddShortcutDialog) {
+        Column(modifier = modifier) {
+            Title(
+                uiState.shortcutLabel,
+                modifier = Modifier.padding(horizontal = 24.dp).width(316.dp),
+            )
+            Description(
+                modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp).width(316.dp)
+            )
+            PromptShortcutModifier(
+                modifier =
+                    Modifier.padding(top = 24.dp, start = 116.5.dp, end = 116.5.dp)
+                        .width(131.dp)
+                        .height(48.dp),
+                defaultModifierKey = uiState.defaultCustomShortcutModifierKey,
+            )
+            SelectedKeyCombinationContainer(
+                shouldShowErrorMessage = uiState.shouldShowErrorMessage,
+                onKeyPress = onKeyPress,
+            )
+            KeyCombinationAlreadyInUseErrorMessage(uiState.shouldShowErrorMessage)
+            DialogButtons(onCancel, isValidKeyCombination = uiState.isValidKeyCombination)
+        }
+    }
+}
+
+@Composable
+fun DialogButtons(onCancel: () -> Unit, isValidKeyCombination: Boolean) {
+    Row(
+        modifier =
+            Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp)
+                .sizeIn(minWidth = 316.dp, minHeight = 48.dp),
+        verticalAlignment = Alignment.Bottom,
+        horizontalArrangement = Arrangement.End,
+    ) {
+        ShortcutHelperButton(
+            shape = RoundedCornerShape(50.dp),
+            onClick = onCancel,
+            color = Color.Transparent,
+            width = 80.dp,
+            contentColor = MaterialTheme.colorScheme.primary,
+            text = stringResource(R.string.shortcut_helper_customize_dialog_cancel_button_label),
+        )
+        Spacer(modifier = Modifier.width(8.dp))
+        ShortcutHelperButton(
+            onClick = {},
+            color = MaterialTheme.colorScheme.primary,
+            width = 116.dp,
+            contentColor = MaterialTheme.colorScheme.onPrimary,
+            text =
+                stringResource(R.string.shortcut_helper_customize_dialog_set_shortcut_button_label),
+            enabled = isValidKeyCombination,
+        )
+    }
+}
+
+@Composable
+fun KeyCombinationAlreadyInUseErrorMessage(shouldShowErrorMessage: Boolean) {
+    if (shouldShowErrorMessage) {
+        Box(modifier = Modifier.padding(horizontal = 16.dp).width(332.dp).height(40.dp)) {
+            Text(
+                text = stringResource(R.string.shortcut_helper_customize_dialog_error_message),
+                style = MaterialTheme.typography.bodyMedium,
+                fontSize = 14.sp,
+                lineHeight = 20.sp,
+                fontWeight = FontWeight.W500,
+                color = MaterialTheme.colorScheme.error,
+                modifier = Modifier.padding(start = 24.dp).width(252.dp),
+            )
+        }
+    }
+}
+
+@Composable
+fun SelectedKeyCombinationContainer(
+    keyCombination: String =
+        stringResource(R.string.shortcut_helper_add_shortcut_dialog_placeholder),
+    shouldShowErrorMessage: Boolean,
+    onKeyPress: (KeyEvent) -> Boolean,
+) {
+    val interactionSource = remember { MutableInteractionSource() }
+    val isFocused by interactionSource.collectIsFocusedAsState()
+    val outlineColor =
+        if (!isFocused) MaterialTheme.colorScheme.outline
+        else if (shouldShowErrorMessage) MaterialTheme.colorScheme.error
+        else MaterialTheme.colorScheme.primary
+
+    ClickableShortcutSurface(
+        onClick = {},
+        color = Color.Transparent,
+        shape = RoundedCornerShape(50.dp),
+        modifier =
+            Modifier.padding(all = 16.dp)
+                .sizeIn(minWidth = 332.dp, minHeight = 56.dp)
+                .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp))
+                .onPreviewKeyEvent { onKeyPress(it) },
+        interactionSource = interactionSource,
+    ) {
+        Row(
+            modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp),
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            Text(
+                text = keyCombination,
+                style = MaterialTheme.typography.headlineSmall,
+                fontSize = 16.sp,
+                lineHeight = 24.sp,
+                fontWeight = FontWeight.W500,
+                color = MaterialTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.width(252.dp),
+            )
+            Spacer(modifier = Modifier.weight(1f))
+            if (shouldShowErrorMessage) {
+                Icon(
+                    imageVector = Icons.Default.ErrorOutline,
+                    contentDescription = null,
+                    modifier = Modifier.size(20.dp),
+                    tint = MaterialTheme.colorScheme.error,
+                )
+            }
+        }
+    }
+}
+
+@Composable
+private fun Title(title: String, modifier: Modifier = Modifier) {
+    Text(
+        text = title,
+        style = MaterialTheme.typography.headlineSmall,
+        fontSize = 24.sp,
+        modifier = modifier.wrapContentSize(Alignment.Center),
+        color = MaterialTheme.colorScheme.onSurface,
+        lineHeight = 32.sp,
+    )
+}
+
+@Composable
+private fun Description(modifier: Modifier = Modifier) {
+    Text(
+        text = stringResource(id = R.string.shortcut_helper_customize_mode_sub_title),
+        style = MaterialTheme.typography.bodyMedium,
+        fontSize = 14.sp,
+        lineHeight = 20.sp,
+        modifier = modifier.wrapContentSize(Alignment.Center),
+        color = MaterialTheme.colorScheme.onSurfaceVariant,
+    )
+}
+
+@Composable
+private fun PromptShortcutModifier(
+    modifier: Modifier,
+    defaultModifierKey: ShortcutKey.Icon.ResIdIcon,
+) {
+    Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(2.dp)) {
+        ActionKeyContainer(defaultModifierKey)
+        PlusIconContainer()
+    }
+}
+
+@Composable
+private fun ActionKeyContainer(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
+    Row(
+        modifier =
+            Modifier.height(48.dp)
+                .width(105.dp)
+                .background(
+                    color = MaterialTheme.colorScheme.surface,
+                    shape = RoundedCornerShape(16.dp),
+                )
+                .padding(all = 12.dp),
+        horizontalArrangement = Arrangement.spacedBy(8.dp),
+    ) {
+        ActionKeyIcon(defaultModifierKey)
+        ActionKeyText()
+    }
+}
+
+@Composable
+fun ActionKeyText() {
+    Text(
+        text = "Action",
+        style = MaterialTheme.typography.titleMedium,
+        fontSize = 16.sp,
+        lineHeight = 24.sp,
+        modifier = Modifier.wrapContentSize(Alignment.Center),
+        color = MaterialTheme.colorScheme.onSurface,
+    )
+}
+
+@Composable
+private fun ActionKeyIcon(defaultModifierKey: ShortcutKey.Icon.ResIdIcon) {
+    Icon(
+        painter = painterResource(id = defaultModifierKey.drawableResId),
+        contentDescription = stringResource(R.string.shortcut_helper_content_description_meta_key),
+        modifier = Modifier.size(24.dp).wrapContentSize(Alignment.Center),
+    )
+}
+
+@Composable
+private fun PlusIconContainer() {
+    Icon(
+        tint = MaterialTheme.colorScheme.onSurface,
+        imageVector = Icons.Default.Add,
+        contentDescription =
+            stringResource(id = R.string.shortcut_helper_content_description_plus_icon),
+        modifier = Modifier.padding(vertical = 12.dp).size(24.dp).wrapContentSize(Alignment.Center),
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index abddc70..13934ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -110,6 +110,7 @@
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutIcon
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
 import com.android.systemui.keyboard.shortcut.ui.model.IconSource
@@ -124,6 +125,7 @@
     modifier: Modifier = Modifier,
     shortcutsUiState: ShortcutsUiState,
     useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() },
+    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
 ) {
     when (shortcutsUiState) {
         is ShortcutsUiState.Active -> {
@@ -133,6 +135,7 @@
                 onSearchQueryChanged,
                 modifier,
                 onKeyboardSettingsClicked,
+                onCustomizationRequested,
             )
         }
         else -> {
@@ -148,6 +151,7 @@
     onSearchQueryChanged: (String) -> Unit,
     modifier: Modifier,
     onKeyboardSettingsClicked: () -> Unit,
+    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
 ) {
     var selectedCategoryType by
         remember(shortcutsUiState.defaultSelectedCategory) {
@@ -173,6 +177,7 @@
             onCategorySelected = { selectedCategoryType = it },
             onKeyboardSettingsClicked,
             shortcutsUiState.isShortcutCustomizerFlagEnabled,
+            onCustomizationRequested,
         )
     }
 }
@@ -362,6 +367,7 @@
     onCategorySelected: (ShortcutCategoryType?) -> Unit,
     onKeyboardSettingsClicked: () -> Unit,
     isShortcutCustomizerFlagEnabled: Boolean,
+    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
 ) {
     val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
     var isCustomizeModeEntered by remember { mutableStateOf(false) }
@@ -400,6 +406,7 @@
                 Modifier.fillMaxSize().padding(top = 8.dp),
                 selectedCategory,
                 isCustomizing = isCustomizing,
+                onCustomizationRequested = onCustomizationRequested,
             )
         }
     }
@@ -434,6 +441,7 @@
     modifier: Modifier,
     category: ShortcutCategoryUi?,
     isCustomizing: Boolean,
+    onCustomizationRequested: (ShortcutInfo) -> Unit = {},
 ) {
     val listState = rememberLazyListState()
     LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
@@ -447,6 +455,15 @@
                 searchQuery = searchQuery,
                 subCategory = subcategory,
                 isCustomizing = isCustomizing,
+                onCustomizationRequested = { label, subCategoryLabel ->
+                    onCustomizationRequested(
+                        ShortcutInfo(
+                            label = label,
+                            subCategoryLabel = subCategoryLabel,
+                            categoryType = category.type,
+                        )
+                    )
+                },
             )
             Spacer(modifier = Modifier.height(8.dp))
         }
@@ -476,6 +493,7 @@
     searchQuery: String,
     subCategory: ShortcutSubCategory,
     isCustomizing: Boolean,
+    onCustomizationRequested: (String, String) -> Unit = { _: String, _: String -> },
 ) {
     Surface(
         modifier = Modifier.fillMaxWidth(),
@@ -497,6 +515,7 @@
                     searchQuery = searchQuery,
                     shortcut = shortcut,
                     isCustomizing = isCustomizing,
+                    onCustomizationRequested = { onCustomizationRequested(it, subCategory.label) },
                 )
             }
         }
@@ -518,6 +537,7 @@
     searchQuery: String,
     shortcut: ShortcutModel,
     isCustomizing: Boolean = false,
+    onCustomizationRequested: (String) -> Unit = {},
 ) {
     val interactionSource = remember { MutableInteractionSource() }
     val isFocused by interactionSource.collectIsFocusedAsState()
@@ -541,7 +561,12 @@
             ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
         }
         Spacer(modifier = Modifier.width(24.dp))
-        ShortcutKeyCombinations(modifier = Modifier.weight(1f), shortcut = shortcut, isCustomizing)
+        ShortcutKeyCombinations(
+            modifier = Modifier.weight(1f),
+            shortcut = shortcut,
+            isCustomizing = isCustomizing,
+            onAddShortcutClicked = { onCustomizationRequested(shortcut.label) },
+        )
     }
 }
 
@@ -569,6 +594,7 @@
     modifier: Modifier = Modifier,
     shortcut: ShortcutModel,
     isCustomizing: Boolean = false,
+    onAddShortcutClicked: () -> Unit = {},
 ) {
     FlowRow(
         modifier = modifier,
@@ -590,7 +616,7 @@
                         color = MaterialTheme.colorScheme.outline,
                         shape = CircleShape,
                     ),
-                onClick = {},
+                onClick = { onAddShortcutClicked() },
                 color = Color.Transparent,
                 width = 32.dp,
                 height = 32.dp,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index 435968e..e761c73 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -44,6 +44,7 @@
 import androidx.compose.material3.LocalContentColor
 import androidx.compose.material3.LocalTonalElevationEnabled
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.contentColorFor
 import androidx.compose.material3.minimumInteractiveComponentSize
@@ -283,6 +284,108 @@
 }
 
 @Composable
+fun ShortcutHelperButton(
+    modifier: Modifier = Modifier,
+    onClick: () -> Unit,
+    shape: Shape = RoundedCornerShape(360.dp),
+    color: Color,
+    width: Dp,
+    height: Dp = 40.dp,
+    iconSource: IconSource = IconSource(),
+    text: String? = null,
+    contentColor: Color,
+    contentPaddingHorizontal: Dp = 16.dp,
+    contentPaddingVertical: Dp = 10.dp,
+    enabled: Boolean = true,
+) {
+    ShortcutHelperButtonSurface(
+        onClick = onClick,
+        shape = shape,
+        color = color,
+        modifier = modifier,
+        enabled = enabled,
+        width = width,
+        height = height,
+    ) {
+        Row(
+            modifier =
+                Modifier.padding(
+                    horizontal = contentPaddingHorizontal,
+                    vertical = contentPaddingVertical,
+                ),
+            verticalAlignment = Alignment.CenterVertically,
+            horizontalArrangement = Arrangement.Center,
+        ) {
+            if (iconSource.imageVector != null) {
+                Icon(
+                    tint = contentColor,
+                    imageVector = iconSource.imageVector,
+                    contentDescription =
+                        null, // TODO this probably should not be null for accessibility.
+                    modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
+                )
+            }
+
+            if (iconSource.imageVector != null && text != null)
+                Spacer(modifier = Modifier.weight(1f))
+
+            if (text != null) {
+                Text(
+                    text,
+                    color = contentColor,
+                    fontSize = 14.sp,
+                    style = MaterialTheme.typography.labelLarge,
+                    modifier = Modifier.wrapContentSize(Alignment.Center),
+                )
+            }
+        }
+    }
+}
+
+@Composable
+fun ShortcutHelperButtonSurface(
+    onClick: () -> Unit,
+    shape: Shape,
+    color: Color,
+    modifier: Modifier = Modifier,
+    enabled: Boolean,
+    width: Dp,
+    height: Dp,
+    content: @Composable () -> Unit,
+) {
+    if (enabled) {
+        ClickableShortcutSurface(
+            onClick = onClick,
+            shape = shape,
+            color = color,
+            modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
+            interactionsConfig =
+                InteractionsConfig(
+                    hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+                    hoverOverlayAlpha = 0.11f,
+                    pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
+                    pressedOverlayAlpha = 0.15f,
+                    focusOutlineColor = MaterialTheme.colorScheme.secondary,
+                    focusOutlineStrokeWidth = 3.dp,
+                    focusOutlinePadding = 2.dp,
+                    surfaceCornerRadius = 28.dp,
+                    focusOutlineCornerRadius = 33.dp,
+                ),
+        ) {
+            content()
+        }
+    } else {
+        Surface(
+            shape = shape,
+            color = color.copy(0.38f),
+            modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
+        ) {
+            content()
+        }
+    }
+}
+
+@Composable
 private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color {
     return MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
new file mode 100644
index 0000000..e9f2a3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.keyboard.shortcut.ui.model
+
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey
+
+sealed interface ShortcutCustomizationUiState {
+    data class AddShortcutDialog(
+        val shortcutLabel: String,
+        val shouldShowErrorMessage: Boolean,
+        val isValidKeyCombination: Boolean,
+        val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon,
+        val isDialogShowing: Boolean,
+    ) : ShortcutCustomizationUiState
+
+    data object Inactive : ShortcutCustomizationUiState
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
new file mode 100644
index 0000000..b925387
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.keyboard.shortcut.ui.viewmodel
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.input.key.KeyEvent
+import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutInfo
+import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.update
+
+class ShortcutCustomizationViewModel
+@AssistedInject
+constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationInteractor) {
+    private val _shortcutBeingCustomized = mutableStateOf<ShortcutInfo?>(null)
+
+    private val _shortcutCustomizationUiState =
+        MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive)
+
+    val shortcutCustomizationUiState = _shortcutCustomizationUiState.asStateFlow()
+
+    fun onAddShortcutDialogRequested(shortcutBeingCustomized: ShortcutInfo) {
+        _shortcutCustomizationUiState.value =
+            ShortcutCustomizationUiState.AddShortcutDialog(
+                shortcutLabel = shortcutBeingCustomized.label,
+                shouldShowErrorMessage = false,
+                isValidKeyCombination = false,
+                defaultCustomShortcutModifierKey =
+                    shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(),
+                isDialogShowing = false,
+            )
+
+        _shortcutBeingCustomized.value = shortcutBeingCustomized
+    }
+
+    fun onAddShortcutDialogShown() {
+        _shortcutCustomizationUiState.update { uiState ->
+            (uiState as? ShortcutCustomizationUiState.AddShortcutDialog)
+                ?.let { it.copy(isDialogShowing = true) }
+                ?: uiState
+        }
+    }
+
+    fun onAddShortcutDialogDismissed() {
+        _shortcutBeingCustomized.value = null
+        _shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive
+    }
+
+    fun onKeyPressed(keyEvent: KeyEvent): Boolean {
+        // TODO Not yet implemented b/373638584
+        return false
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): ShortcutCustomizationViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index 585f7ed..922bc15 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -52,6 +52,7 @@
 constructor(
     private val inputManager: InputManager,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    // TODO: b/377244768 - Change to inject SecureSettingsRepository
     secureSettingsRepository: UserAwareSecureSettingsRepository,
     private val stickyKeysLogger: StickyKeysLogger,
 ) : StickyKeysRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 53177de..80ac2fc 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -696,7 +696,8 @@
                 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(
                         mTaskStackListener);
                 DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
-                mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null));
+                mPipOptional.ifPresent(pip -> pip.removeOnIsInPipStateChangedListener(
+                        mOnIsInPipStateChangedListener));
 
                 try {
                     mWindowManagerService.unregisterSystemGestureExclusionListener(
@@ -720,7 +721,7 @@
                         mTaskStackListener);
                 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
                         mUiThreadContext.getExecutor()::execute, mOnPropertiesChangedListener);
-                mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(
+                mPipOptional.ifPresent(pip -> pip.addOnIsInPipStateChangedListener(
                         mOnIsInPipStateChangedListener));
                 mDesktopModeOptional.ifPresent(
                         dm -> dm.addDesktopGestureExclusionRegionListener(
@@ -1191,11 +1192,13 @@
     }
 
     private void pilferPointers() {
-        // Capture inputs
-        mInputMonitor.pilferPointers();
-        // Notify FalsingManager that an intentional gesture has occurred.
-        mFalsingManager.isFalseTouch(BACK_GESTURE);
-        mInputEventReceiver.setBatchingEnabled(true);
+        if (mInputMonitor != null) {
+            // Capture inputs
+            mInputMonitor.pilferPointers();
+            // Notify FalsingManager that an intentional gesture has occurred.
+            mFalsingManager.isFalseTouch(BACK_GESTURE);
+            mInputEventReceiver.setBatchingEnabled(true);
+        }
     }
 
     private boolean isButtonPressFromTrackpad(MotionEvent ev) {
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index 765b45b..bab88c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -159,7 +159,7 @@
      * Returns true if lock screen entry point for QR Code Scanner is to be enabled.
      */
     public boolean isEnabledForLockScreenButton() {
-        return mQRCodeScannerEnabled && isAbleToLaunchScannerActivity() && isAllowedOnLockScreen();
+        return isAbleToLaunchScannerActivity() && isAllowedOnLockScreen();
     }
 
     /** Returns whether the QR scanner button is allowed on lockscreen. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index a1071907..2a5ffc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -27,6 +27,7 @@
 import android.content.IntentFilter
 import android.content.pm.PackageManager
 import android.content.pm.UserInfo
+import android.content.res.Resources
 import android.graphics.drawable.Drawable
 import android.os.IBinder
 import android.os.PowerExemptionManager
@@ -54,6 +55,7 @@
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_USER_VISIBLE_JOBS
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.systemui.Dumpable
+import com.android.systemui.Flags;
 import com.android.systemui.res.R
 import com.android.systemui.animation.DialogCuj
 import com.android.systemui.animation.DialogTransitionAnimator
@@ -137,7 +139,7 @@
 
 @SysUISingleton
 class FgsManagerControllerImpl @Inject constructor(
-    private val context: Context,
+    @Main private val resources: Resources,
     @Main private val mainExecutor: Executor,
     @Background private val backgroundExecutor: Executor,
     private val systemClock: SystemClock,
@@ -223,6 +225,14 @@
 
     private val userVisibleJobObserver = UserVisibleJobObserver()
 
+    private val stoppableApps by lazy { resources
+        .getStringArray(com.android.internal.R.array.stoppable_fgs_system_apps)
+    }
+
+    private val vendorStoppableApps by lazy { resources
+        .getStringArray(com.android.internal.R.array.vendor_stoppable_fgs_system_apps)
+    }
+
     override fun init() {
         synchronized(lock) {
             if (initialized) {
@@ -725,9 +735,22 @@
                     }
                 else -> UIControl.NORMAL
             }
+            // If the app wants to be a good citizen by being stoppable, even if the category it
+            // belongs to is exempted for background restriction, let it be stoppable by user.
+            if (Flags.stoppableFgsSystemApp()) {
+                if (isStoppableApp(packageName)) {
+                    uiControl = UIControl.NORMAL
+                }
+            }
+
             uiControlInitialized = true
         }
 
+        fun isStoppableApp(packageName: String): Boolean {
+            return stoppableApps.contains(packageName) ||
+                vendorStoppableApps.contains(packageName)
+        }
+
         override fun equals(other: Any?): Boolean {
             if (other !is UserPackage) {
                 return false
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index 624cf30..e912a0c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -63,6 +63,7 @@
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.util.LargeScreenUtils
 import com.android.systemui.util.asIndenting
+import com.android.systemui.util.kotlin.emitOnStart
 import com.android.systemui.util.printSection
 import com.android.systemui.util.println
 import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
@@ -278,6 +279,24 @@
     private val mediaSuddenlyAppearingInLandscape: Boolean
         get() = !qqsMediaInRow && qsMediaInRow
 
+    private val collapsedLandscapeMedia by
+        hydrator.hydratedStateOf(
+            traceName = "collapsedLandscapeMedia",
+            initialValue = resources.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed),
+            source =
+                configurationInteractor.onAnyConfigurationChange.emitOnStart().map {
+                    resources.getBoolean(R.bool.config_quickSettingsMediaLandscapeCollapsed)
+                },
+        )
+
+    private val qqsMediaExpansion: Float
+        get() =
+            if (qqsMediaInRow && collapsedLandscapeMedia) {
+                MediaHostState.COLLAPSED
+            } else {
+                MediaHostState.EXPANDED
+            }
+
     private var qsBounds by mutableStateOf(Rect())
 
     private val constrainedSquishinessFraction: Float
@@ -379,6 +398,7 @@
         initMediaHosts() // init regardless of using media (same as current QS).
         coroutineScope {
             launch { hydrateSquishinessInteractor() }
+            launch { hydrateQqsMediaExpansion() }
             launch { hydrator.activate() }
             launch { containerViewModel.activate() }
             launch { qqsMediaInRowViewModel.activate() }
@@ -389,7 +409,7 @@
 
     private fun initMediaHosts() {
         qqsMediaHost.apply {
-            expansion = MediaHostState.EXPANDED
+            expansion = qqsMediaExpansion
             showsOnlyActiveMedia = true
             init(MediaHierarchyManager.LOCATION_QQS)
         }
@@ -405,6 +425,10 @@
             .collect { squishinessInteractor.setSquishinessValue(it) }
     }
 
+    private suspend fun hydrateQqsMediaExpansion() {
+        snapshotFlow { qqsMediaExpansion }.collect { qqsMediaHost.expansion = it }
+    }
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.asIndenting().run {
             printSection("Quick Settings state") {
@@ -448,6 +472,8 @@
                 println("qqsMediaInRow", qqsMediaInRow)
                 println("qsMediaVisible", qsMediaVisible)
                 println("qsMediaInRow", qsMediaInRow)
+                println("collapsedLandscapeMedia", collapsedLandscapeMedia)
+                println("qqsMediaExpansion", qqsMediaExpansion)
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index ce9c441..a5eb92b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -24,7 +24,10 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static android.window.BackEvent.EDGE_NONE;
 
+import static com.android.window.flags.Flags.predictiveBackSwipeEdgeNoneApi;
+import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
@@ -41,6 +44,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION;
 
 import android.annotation.FloatRange;
+import android.annotation.Nullable;
 import android.app.ActivityTaskManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -114,6 +118,7 @@
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder;
+import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInterface;
 
@@ -174,6 +179,7 @@
     private Region mActiveNavBarRegion;
 
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final BackAnimation mBackAnimation;
 
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
@@ -287,11 +293,18 @@
         }
 
         @Override
-        public void onBackPressed() {
-            verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
-                sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
-                sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-            });
+        public void onBackEvent(@Nullable KeyEvent keyEvent) throws RemoteException {
+            if (predictiveBackThreeButtonNav() && predictiveBackSwipeEdgeNoneApi()
+                    && mBackAnimation != null && keyEvent != null) {
+                mBackAnimation.setTriggerBack(!keyEvent.isCanceled());
+                mBackAnimation.onBackMotion(/* touchX */ 0, /* touchY */ 0, keyEvent.getAction(),
+                        EDGE_NONE);
+            } else {
+                verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
+                    sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
+                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
+                });
+            }
         }
 
         @Override
@@ -657,7 +670,8 @@
             AssistUtils assistUtils,
             DumpManager dumpManager,
             Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
-            BroadcastDispatcher broadcastDispatcher
+            BroadcastDispatcher broadcastDispatcher,
+            Optional<BackAnimation> backAnimation
     ) {
         // b/241601880: This component should only be running for primary users or
         // secondaryUsers when visibleBackgroundUsers are supported.
@@ -695,6 +709,7 @@
         mDisplayTracker = displayTracker;
         mUnfoldTransitionProgressForwarder = unfoldTransitionProgressForwarder;
         mBroadcastDispatcher = broadcastDispatcher;
+        mBackAnimation = backAnimation.orElse(null);
 
         if (!KeyguardWmStateRefactor.isEnabled()) {
             mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
index 2ef27a8..60ed2de 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.settings
 
 import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.hardware.display.DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS
 import android.os.Handler
 import android.view.Display
 import androidx.annotation.GuardedBy
@@ -32,7 +32,7 @@
 class DisplayTrackerImpl
 internal constructor(
     val displayManager: DisplayManager,
-    @Background val backgroundHandler: Handler
+    @Background val backgroundHandler: Handler,
 ) : DisplayTracker {
     override val defaultDisplayId: Int = Display.DEFAULT_DISPLAY
     override val allDisplays: Array<Display>
@@ -47,27 +47,21 @@
     val displayChangedListener: DisplayManager.DisplayListener =
         object : DisplayManager.DisplayListener {
             override fun onDisplayAdded(displayId: Int) {
-                traceSection(
-                    "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayAdded",
-                ) {
+                traceSection("DisplayTrackerImpl.displayChangedDisplayListener#onDisplayAdded") {
                     val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
                     onDisplayAdded(displayId, list)
                 }
             }
 
             override fun onDisplayRemoved(displayId: Int) {
-                traceSection(
-                    "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayRemoved",
-                ) {
+                traceSection("DisplayTrackerImpl.displayChangedDisplayListener#onDisplayRemoved") {
                     val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
                     onDisplayRemoved(displayId, list)
                 }
             }
 
             override fun onDisplayChanged(displayId: Int) {
-                traceSection(
-                    "DisplayTrackerImpl.displayChangedDisplayListener#onDisplayChanged",
-                ) {
+                traceSection("DisplayTrackerImpl.displayChangedDisplayListener#onDisplayChanged") {
                     val list = synchronized(displayCallbacks) { displayCallbacks.toList() }
                     onDisplayChanged(displayId, list)
                 }
@@ -83,7 +77,7 @@
 
             override fun onDisplayChanged(displayId: Int) {
                 traceSection(
-                    "DisplayTrackerImpl.displayBrightnessChangedDisplayListener#onDisplayChanged",
+                    "DisplayTrackerImpl.displayBrightnessChangedDisplayListener#onDisplayChanged"
                 ) {
                     val list = synchronized(brightnessCallbacks) { brightnessCallbacks.toList() }
                     onDisplayChanged(displayId, list)
@@ -102,14 +96,15 @@
 
     override fun addBrightnessChangeCallback(
         callback: DisplayTracker.Callback,
-        executor: Executor
+        executor: Executor,
     ) {
         synchronized(brightnessCallbacks) {
             if (brightnessCallbacks.isEmpty()) {
                 displayManager.registerDisplayListener(
                     displayBrightnessChangedListener,
                     backgroundHandler,
-                    EVENT_FLAG_DISPLAY_BRIGHTNESS
+                    /* eventFlags */ 0,
+                    PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS,
                 )
             }
             brightnessCallbacks.add(DisplayTrackerDataItem(WeakReference(callback), executor))
@@ -159,7 +154,7 @@
 
     private inline fun notifySubscribers(
         crossinline action: DisplayTracker.Callback.() -> Unit,
-        list: List<DisplayTrackerDataItem>
+        list: List<DisplayTrackerDataItem>,
     ) {
         list.forEach {
             if (it.callback.get() != null) {
@@ -170,7 +165,7 @@
 
     private data class DisplayTrackerDataItem(
         val callback: WeakReference<DisplayTracker.Callback>,
-        val executor: Executor
+        val executor: Executor,
     ) {
         fun sameOrEmpty(other: DisplayTracker.Callback): Boolean {
             return callback.get()?.equals(other) ?: true
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 649f8db..90d27f4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -44,6 +44,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -56,6 +57,7 @@
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.log.core.LogMessage;
+import com.android.systemui.res.R;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.settings.SecureSettings;
@@ -111,6 +113,7 @@
     private boolean mControlValueInitialized;
     private float mBrightnessMin = PowerManager.BRIGHTNESS_MIN;
     private float mBrightnessMax = PowerManager.BRIGHTNESS_MAX;
+    private boolean mIsBrightnessOverriddenByWindow = false;
 
     private ValueAnimator mSliderAnimator;
 
@@ -246,12 +249,14 @@
         @Override
         public void run() {
             final boolean inVrMode = mIsVrModeEnabled;
-            final BrightnessInfo info = mContext.getDisplay().getBrightnessInfo();
+            final BrightnessInfo info = getBrightnessInfo();
             if (info == null) {
                 return;
             }
             mBrightnessMax = info.brightnessMaximum;
             mBrightnessMin = info.brightnessMinimum;
+            mIsBrightnessOverriddenByWindow = info.isBrightnessOverrideByWindow;
+
             // Value is passed as intbits, since this is what the message takes.
             final int valueAsIntBits = Float.floatToIntBits(info.brightness);
             mMainHandler.obtainMessage(MSG_UPDATE_SLIDER, valueAsIntBits,
@@ -353,7 +358,19 @@
     public void onChanged(boolean tracking, int value, boolean stopTracking) {
         boolean starting = !mTrackingTouch && tracking;
         mTrackingTouch = tracking;
-        if (mExternalChange) return;
+        if (starting) {
+            if (Flags.showToastWhenAppControlBrightness()) {
+                // Showing the warning toast if the current running app window has
+                // controlled the brightness value.
+                if (mIsBrightnessOverriddenByWindow) {
+                    mControl.showToast(R.string.quick_settings_brightness_unable_adjust_msg);
+                }
+            }
+        }
+        if (mExternalChange
+                || (Flags.showToastWhenAppControlBrightness() && mIsBrightnessOverriddenByWindow)) {
+            return;
+        }
 
         if (mSliderAnimator != null) {
             mSliderAnimator.cancel();
@@ -424,6 +441,11 @@
         mDisplayManager.setTemporaryBrightness(mDisplayId, brightness);
     }
 
+    @VisibleForTesting
+    BrightnessInfo getBrightnessInfo() {
+        return mContext.getDisplay().getBrightnessInfo();
+    }
+
     private void updateVrMode(boolean isEnabled) {
         if (mIsVrModeEnabled != isEnabled) {
             mIsVrModeEnabled = isEnabled;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
index 2f7df21..3a90d2b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.settings.brightness;
 
+import android.annotation.StringRes;
 import android.content.Context;
 import android.content.Intent;
 import android.view.LayoutInflater;
@@ -36,6 +37,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.res.R;
+import com.android.systemui.settings.brightness.ui.BrightnessWarningToast;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 import com.android.systemui.util.ViewController;
@@ -68,6 +70,8 @@
     private final HapticSliderPlugin mBrightnessSliderHapticPlugin;
     private final ActivityStarter mActivityStarter;
 
+    private final BrightnessWarningToast mBrightnessWarningToast;
+
     private final Gefingerpoken mOnInterceptListener = new Gefingerpoken() {
         @Override
         public boolean onInterceptTouchEvent(MotionEvent ev) {
@@ -90,12 +94,14 @@
             FalsingManager falsingManager,
             UiEventLogger uiEventLogger,
             HapticSliderPlugin brightnessSliderHapticPlugin,
-            ActivityStarter activityStarter) {
+            ActivityStarter activityStarter,
+            BrightnessWarningToast brightnessWarningToast) {
         super(brightnessSliderView);
         mFalsingManager = falsingManager;
         mUiEventLogger = uiEventLogger;
         mBrightnessSliderHapticPlugin = brightnessSliderHapticPlugin;
         mActivityStarter = activityStarter;
+        mBrightnessWarningToast = brightnessWarningToast;
     }
 
     /**
@@ -225,6 +231,15 @@
     }
 
     @Override
+    public void showToast(@StringRes int resId) {
+        if (mBrightnessWarningToast.isToastActive()) {
+            return;
+        }
+        mBrightnessWarningToast.show(mView.getContext(),
+                R.string.quick_settings_brightness_unable_adjust_msg);
+    }
+
+    @Override
     public boolean isVisible() {
         // this should be called rarely - once or twice per slider's value change, but not for
         // every value change when user slides finger - only the final one.
@@ -286,6 +301,7 @@
         private final SystemClock mSystemClock;
         private final ActivityStarter mActivityStarter;
         private final MSDLPlayer mMSDLPlayer;
+        private final BrightnessWarningToast mBrightnessWarningToast;
 
         @Inject
         public Factory(
@@ -294,7 +310,8 @@
                 VibratorHelper vibratorHelper,
                 MSDLPlayer msdlPlayer,
                 SystemClock clock,
-                ActivityStarter activityStarter
+                ActivityStarter activityStarter,
+                BrightnessWarningToast brightnessWarningToast
         ) {
             mFalsingManager = falsingManager;
             mUiEventLogger = uiEventLogger;
@@ -302,6 +319,7 @@
             mSystemClock = clock;
             mActivityStarter = activityStarter;
             mMSDLPlayer = msdlPlayer;
+            mBrightnessWarningToast = brightnessWarningToast;
         }
 
         /**
@@ -323,8 +341,8 @@
                     mSystemClock,
                     new HapticSlider.SeekBar(root.requireViewById(R.id.slider)));
             HapticSliderViewBinder.bind(viewRoot, plugin);
-            return new BrightnessSliderController(
-                    root, mFalsingManager, mUiEventLogger, plugin, mActivityStarter);
+            return new BrightnessSliderController(root, mFalsingManager, mUiEventLogger, plugin,
+                    mActivityStarter, mBrightnessWarningToast);
         }
 
         /** Get the layout to inflate based on what slider to use */
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
index 24bc670..ed69d35 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSlider.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.settings.brightness;
 
+import android.annotation.StringRes;
 import android.view.MotionEvent;
 
 import com.android.settingslib.RestrictedLockUtils;
@@ -37,5 +38,6 @@
 
     void showView();
     void hideView();
+    void showToast(@StringRes int resId);
     boolean isVisible();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt
new file mode 100644
index 0000000..dfbdaa6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/BrightnessWarningToast.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.settings.brightness.ui
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.annotation.StringRes
+import android.content.Context
+import android.graphics.PixelFormat
+import android.view.Gravity
+import android.view.View
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.toast.ToastFactory
+import javax.inject.Inject
+
+@SysUISingleton
+class BrightnessWarningToast
+@Inject
+constructor(
+    private val toastFactory: ToastFactory,
+    private val windowManager: WindowManager,
+) {
+    private var toastView: View? = null
+
+    fun show(viewContext: Context, @StringRes resId: Int) {
+        val res = viewContext.resources
+        // Show the brightness warning toast with passing the toast inflation required context,
+        // userId and resId from SystemUI package.
+        val systemUIToast = toastFactory.createToast(
+            viewContext,
+            res.getString(resId), viewContext.packageName, viewContext.getUserId(),
+            res.configuration.orientation
+        )
+        if (systemUIToast == null) {
+            return
+        }
+
+        toastView = systemUIToast.view
+
+        val params = WindowManager.LayoutParams()
+        params.height = WindowManager.LayoutParams.WRAP_CONTENT
+        params.width = WindowManager.LayoutParams.WRAP_CONTENT
+        params.format = PixelFormat.TRANSLUCENT
+        params.title = "Brightness warning toast"
+        params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL
+        params.flags = (WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                or WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
+        params.y = systemUIToast.yOffset
+
+        val absGravity = Gravity.getAbsoluteGravity(
+            systemUIToast.gravity,
+            res.configuration.layoutDirection
+        )
+        params.gravity = absGravity
+        if ((absGravity and Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+            params.horizontalWeight = TOAST_PARAMS_HORIZONTAL_WEIGHT
+        }
+        if ((absGravity and Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+            params.verticalWeight = TOAST_PARAMS_VERTICAL_WEIGHT
+        }
+
+        windowManager.addView(toastView, params)
+
+        val inAnimator = systemUIToast.inAnimation
+        inAnimator?.start()
+
+        toastView!!.postDelayed({
+            val outAnimator = systemUIToast.outAnimation
+            if (outAnimator != null) {
+                outAnimator.start()
+                outAnimator.addListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animator: Animator) {
+                        windowManager.removeViewImmediate(toastView)
+                        toastView = null
+                    }
+                })
+            }
+        }, TOAST_DURATION_MS)
+    }
+
+    fun isToastActive(): Boolean {
+        return toastView != null && toastView!!.isAttachedToWindow
+    }
+
+    companion object {
+        private const val TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f
+        private const val TOAST_PARAMS_VERTICAL_WEIGHT = 1.0f
+        private const val TOAST_DURATION_MS: Long = 3000
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 8c5a711..a5595ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -116,6 +116,7 @@
 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor;
 import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.wakelock.SettableWakeLock;
@@ -162,6 +163,7 @@
     private final KeyguardLogger mKeyguardLogger;
     private final UserTracker mUserTracker;
     private final BouncerMessageInteractor mBouncerMessageInteractor;
+
     private ViewGroup mIndicationArea;
     private KeyguardIndicationTextView mTopIndicationView;
     private KeyguardIndicationTextView mLockScreenIndicationView;
@@ -187,6 +189,7 @@
     private final BiometricMessageInteractor mBiometricMessageInteractor;
     private DeviceEntryFingerprintAuthInteractor mDeviceEntryFingerprintAuthInteractor;
     private DeviceEntryFaceAuthInteractor mDeviceEntryFaceAuthInteractor;
+    private final UserLogoutInteractor mUserLogoutInteractor;
     private String mPersistentUnlockMessage;
     private String mAlignmentIndication;
     private boolean mForceIsDismissible;
@@ -237,6 +240,13 @@
                     showTrustAgentErrorMessage(mTrustAgentErrorMessage);
                 }
             };
+    @VisibleForTesting
+    final Consumer<Boolean> mIsLogoutEnabledCallback =
+            (Boolean isLogoutEnabled) -> {
+                if (mVisible) {
+                    updateDeviceEntryIndication(false);
+                }
+            };
     private final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurnedOn() {
@@ -299,7 +309,8 @@
             KeyguardInteractor keyguardInteractor,
             BiometricMessageInteractor biometricMessageInteractor,
             DeviceEntryFingerprintAuthInteractor deviceEntryFingerprintAuthInteractor,
-            DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor
+            DeviceEntryFaceAuthInteractor deviceEntryFaceAuthInteractor,
+            UserLogoutInteractor userLogoutInteractor
     ) {
         mContext = context;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -331,6 +342,8 @@
         mBiometricMessageInteractor = biometricMessageInteractor;
         mDeviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor;
         mDeviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor;
+        mUserLogoutInteractor = userLogoutInteractor;
+
 
         mFaceAcquiredMessageDeferral = faceHelpMessageDeferral.create();
 
@@ -418,6 +431,9 @@
                 mCoExAcquisitionMsgIdsToShowCallback);
         collectFlow(mIndicationArea, mDeviceEntryFingerprintAuthInteractor.isEngaged(),
                 mIsFingerprintEngagedCallback);
+        collectFlow(mIndicationArea,
+                mUserLogoutInteractor.isLogoutEnabled(),
+                mIsLogoutEnabledCallback);
     }
 
     /**
@@ -744,9 +760,7 @@
     }
 
     private void updateLockScreenLogoutView() {
-        final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled()
-                && getCurrentUser() != UserHandle.USER_SYSTEM;
-        if (shouldShowLogout) {
+        if (mUserLogoutInteractor.isLogoutEnabled().getValue()) {
             mRotateTextViewController.updateIndication(
                     INDICATION_TYPE_LOGOUT,
                     new KeyguardIndication.Builder()
@@ -760,7 +774,7 @@
                                 if (mFalsingManager.isFalseTap(LOW_PENALTY)) {
                                     return;
                                 }
-                                mDevicePolicyManager.logoutUser();
+                                mUserLogoutInteractor.logOut();
                             })
                             .build(),
                     false);
@@ -1515,13 +1529,6 @@
         }
 
         @Override
-        public void onLogoutEnabledChanged() {
-            if (mVisible) {
-                updateDeviceEntryIndication(false);
-            }
-        }
-
-        @Override
         public void onRequireUnlockForNfc() {
             showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc));
             hideTransientIndicationDelayed(DEFAULT_HIDE_DELAY_MS);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 3a24ec9..c1b8d9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -383,34 +383,20 @@
         }
 
         protected boolean hasSameIcon(Object parentData, Object childData) {
-            Icon parentIcon = getIcon((Notification) parentData);
-            Icon childIcon = getIcon((Notification) childData);
+            Icon parentIcon = ((Notification) parentData).getSmallIcon();
+            Icon childIcon = ((Notification) childData).getSmallIcon();
             return parentIcon.sameAs(childIcon);
         }
 
-        private static Icon getIcon(Notification notification) {
-            if (notification.shouldUseAppIcon()) {
-                return notification.getAppIcon();
-            }
-            return notification.getSmallIcon();
-        }
-
         /**
          * @return whether two ImageViews have the same colorFilterSet or none at all
          */
         protected boolean hasSameColor(Object parentData, Object childData) {
-            int parentColor = getColor((Notification) parentData);
-            int childColor = getColor((Notification) childData);
+            int parentColor = ((Notification) parentData).color;
+            int childColor = ((Notification) childData).color;
             return parentColor == childColor;
         }
 
-        private static int getColor(Notification notification) {
-            if (notification.shouldUseAppIcon()) {
-                return 0;  // the color filter isn't applied if using the app icon
-            }
-            return notification.color;
-        }
-
         @Override
         public boolean isEmpty(View view) {
             return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index ad3afd4..33f0c64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -27,7 +27,6 @@
 import android.app.Notification;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -36,7 +35,6 @@
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.graphics.Rect;
-import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Trace;
@@ -520,36 +518,10 @@
                 userId = UserHandle.USER_SYSTEM;
             }
 
-            // Try to load the monochrome app icon if applicable
-            Drawable icon = maybeGetMonochromeAppIcon(context, statusBarIcon);
-            // Otherwise, just use the icon normally
-            if (icon == null) {
-                icon = statusBarIcon.icon.loadDrawableAsUser(context, userId);
-            }
-            return icon;
+            return statusBarIcon.icon.loadDrawableAsUser(context, userId);
         }
     }
 
-    @Nullable
-    private Drawable maybeGetMonochromeAppIcon(Context context,
-            StatusBarIcon statusBarIcon) {
-        if (android.app.Flags.notificationsUseMonochromeAppIcon()
-                && statusBarIcon.type == StatusBarIcon.Type.MaybeMonochromeAppIcon) {
-            // Check if we have a monochrome app icon
-            PackageManager pm = context.getPackageManager();
-            Drawable appIcon = context.getApplicationInfo().loadIcon(pm);
-            if (appIcon instanceof AdaptiveIconDrawable) {
-                Drawable monochrome = ((AdaptiveIconDrawable) appIcon).getMonochrome();
-                if (monochrome != null) {
-                    setCropToPadding(true);
-                    setScaleType(ScaleType.CENTER);
-                    return new ScalingDrawableWrapper(monochrome, APP_ICON_SCALE);
-                }
-            }
-        }
-        return null;
-    }
-
     public StatusBarIcon getStatusBarIcon() {
         return mIcon;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
index 9c53cc1..e3dc70a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/screenrecord/domain/interactor/ScreenRecordChipInteractor.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.chips.screenrecord.domain.interactor
 
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.log.LogBuffer
@@ -28,14 +29,19 @@
 import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.transformLatest
+import kotlinx.coroutines.launch
 
 /** Interactor for the screen recording chip shown in the status bar. */
 @SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
 class ScreenRecordChipInteractor
 @Inject
 constructor(
@@ -44,6 +50,32 @@
     private val mediaProjectionRepository: MediaProjectionRepository,
     @StatusBarChipsLog private val logger: LogBuffer,
 ) {
+    /**
+     * Emits true if we should assume that we're currently screen recording, even if
+     * [ScreenRecordRepository.screenRecordState] hasn't emitted [ScreenRecordModel.Recording] yet.
+     */
+    private val shouldAssumeIsRecording: Flow<Boolean> =
+        screenRecordRepository.screenRecordState
+            .transformLatest {
+                when (it) {
+                    is ScreenRecordModel.DoingNothing -> {
+                        emit(false)
+                    }
+                    is ScreenRecordModel.Starting -> {
+                        // If we're told that the recording will start in [it.millisUntilStarted],
+                        // optimistically assume the recording did indeed start after that time even
+                        // if [ScreenRecordRepository.screenRecordState] hasn't emitted
+                        // [ScreenRecordModel.Recording] yet. Start 50ms early so that the chip
+                        // timer will definitely be showing by the time the recording actually
+                        // starts - see b/366448907.
+                        delay(it.millisUntilStarted - 50)
+                        emit(true)
+                    }
+                    is ScreenRecordModel.Recording -> {}
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
     val screenRecordState: StateFlow<ScreenRecordChipModel> =
         // ScreenRecordRepository has the main "is the screen being recorded?" state, and
         // MediaProjectionRepository has information about what specifically is being recorded (a
@@ -51,37 +83,55 @@
         combine(
                 screenRecordRepository.screenRecordState,
                 mediaProjectionRepository.mediaProjectionState,
-            ) { screenRecordState, mediaProjectionState ->
-                when (screenRecordState) {
-                    is ScreenRecordModel.DoingNothing -> {
-                        logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" })
-                        ScreenRecordChipModel.DoingNothing
-                    }
-                    is ScreenRecordModel.Starting -> {
-                        logger.log(
-                            TAG,
-                            LogLevel.INFO,
-                            { long1 = screenRecordState.millisUntilStarted },
-                            { "State: Starting($long1)" }
-                        )
-                        ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted)
-                    }
-                    is ScreenRecordModel.Recording -> {
-                        val recordedTask =
-                            if (
-                                mediaProjectionState is MediaProjectionState.Projecting.SingleTask
-                            ) {
-                                mediaProjectionState.task
-                            } else {
-                                null
-                            }
-                        logger.log(
-                            TAG,
-                            LogLevel.INFO,
-                            { str1 = recordedTask?.baseIntent?.component?.packageName },
-                            { "State: Recording(taskPackage=$str1)" }
-                        )
-                        ScreenRecordChipModel.Recording(recordedTask)
+                shouldAssumeIsRecording,
+            ) { screenRecordState, mediaProjectionState, shouldAssumeIsRecording ->
+                if (
+                    Flags.statusBarAutoStartScreenRecordChip() &&
+                        shouldAssumeIsRecording &&
+                        screenRecordState is ScreenRecordModel.Starting
+                ) {
+                    logger.log(
+                        TAG,
+                        LogLevel.INFO,
+                        {},
+                        { "State: Recording(taskPackage=null) due to force-start" },
+                    )
+                    ScreenRecordChipModel.Recording(recordedTask = null)
+                } else {
+                    when (screenRecordState) {
+                        is ScreenRecordModel.DoingNothing -> {
+                            logger.log(TAG, LogLevel.INFO, {}, { "State: DoingNothing" })
+                            ScreenRecordChipModel.DoingNothing
+                        }
+
+                        is ScreenRecordModel.Starting -> {
+                            logger.log(
+                                TAG,
+                                LogLevel.INFO,
+                                { long1 = screenRecordState.millisUntilStarted },
+                                { "State: Starting($long1)" },
+                            )
+                            ScreenRecordChipModel.Starting(screenRecordState.millisUntilStarted)
+                        }
+
+                        is ScreenRecordModel.Recording -> {
+                            val recordedTask =
+                                if (
+                                    mediaProjectionState
+                                        is MediaProjectionState.Projecting.SingleTask
+                                ) {
+                                    mediaProjectionState.task
+                                } else {
+                                    null
+                                }
+                            logger.log(
+                                TAG,
+                                LogLevel.INFO,
+                                { str1 = recordedTask?.baseIntent?.component?.packageName },
+                                { "State: Recording(taskPackage=$str1)" },
+                            )
+                            ScreenRecordChipModel.Recording(recordedTask)
+                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index f441fd6..4c54fc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -112,12 +112,12 @@
     }
 
     override fun initializeStatusBar() {
-        StatusBarSimpleFragment.assertInLegacyMode()
+        StatusBarRootModernization.assertInLegacyMode()
         doStart()
     }
 
     private fun doStart() {
-        if (StatusBarSimpleFragment.isEnabled) doComposeStart() else doLegacyStart()
+        if (StatusBarRootModernization.isEnabled) doComposeStart() else doLegacyStart()
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
index 2141513..057213f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarSimpleFragment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarRootModernization.kt
@@ -21,9 +21,9 @@
 import com.android.systemui.flags.RefactorFlagUtils
 
 /** Helper for reading and using the status bar simple fragment flag state */
-object StatusBarSimpleFragment {
+object StatusBarRootModernization {
     /** Aconfig flag for removing the fragment */
-    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT
+    const val FLAG_NAME = Flags.FLAG_STATUS_BAR_ROOT_MODERNIZATION
 
     /** A token used for dependency declaration */
     val token: FlagToken
@@ -32,7 +32,7 @@
     /** Is the refactor enabled */
     @JvmStatic
     inline val isEnabled
-        get() = Flags.statusBarSimpleFragment()
+        get() = Flags.statusBarRootModernization()
 
     /**
      * Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
index 16d0cc4..3c8c42f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -18,7 +18,6 @@
 
 import android.app.Notification
 import android.content.Context
-import android.graphics.drawable.Drawable
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.contentDescForNotification
@@ -30,15 +29,11 @@
         return StatusBarIconView(
             context,
             "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
-            entry.sbn
+            entry.sbn,
         )
     }
 
     fun getIconContentDescription(n: Notification): CharSequence {
         return contentDescForNotification(context, n)
     }
-
-    fun getAppIcon(n: Notification): Drawable {
-        return n.loadHeaderAppIcon(context)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index db80483..4717194 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -26,6 +26,7 @@
 import android.util.Log
 import android.view.View
 import android.widget.ImageView
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.traceSection
 import com.android.internal.statusbar.StatusBarIcon
 import com.android.systemui.Flags
@@ -44,7 +45,6 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 /**
@@ -152,13 +152,7 @@
                 setIcon(entry, sensitiveIconDescriptor, shelfIcon)
                 setIcon(entry, sensitiveIconDescriptor, aodIcon)
                 entry.icons =
-                    IconPack.buildPack(
-                        sbIcon,
-                        sbChipIcon,
-                        shelfIcon,
-                        aodIcon,
-                        entry.icons,
-                    )
+                    IconPack.buildPack(sbIcon, sbChipIcon, shelfIcon, aodIcon, entry.icons)
             } catch (e: InflationException) {
                 entry.icons = IconPack.buildEmptyPack(entry.icons)
                 throw e
@@ -182,7 +176,7 @@
                 Log.wtf(
                     TAG,
                     "Updating using the cache is not supported when the " +
-                        "notifications_background_icons flag is off"
+                        "notifications_background_icons flag is off",
                 )
             }
             if (!usingCache || !Flags.notificationsBackgroundIcons()) {
@@ -249,10 +243,6 @@
         val (icon: Icon?, type: StatusBarIcon.Type) =
             if (showPeopleAvatar) {
                 createPeopleAvatar(entry) to StatusBarIcon.Type.PeopleAvatar
-            } else if (
-                android.app.Flags.notificationsUseMonochromeAppIcon() && n.shouldUseAppIcon()
-            ) {
-                n.smallIcon to StatusBarIcon.Type.MaybeMonochromeAppIcon
             } else {
                 n.smallIcon to StatusBarIcon.Type.NotifSmallIcon
             }
@@ -267,33 +257,25 @@
 
     private fun getCachedIconDescriptor(
         entry: NotificationEntry,
-        showPeopleAvatar: Boolean
+        showPeopleAvatar: Boolean,
     ): StatusBarIcon? {
         val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
-        val appIconDescriptor = entry.icons.appIconDescriptor
         val smallIconDescriptor = entry.icons.smallIconDescriptor
 
         // If cached, return corresponding cached values
         return when {
             showPeopleAvatar && peopleAvatarDescriptor != null -> peopleAvatarDescriptor
-            android.app.Flags.notificationsUseMonochromeAppIcon() && appIconDescriptor != null ->
-                appIconDescriptor
             smallIconDescriptor != null -> smallIconDescriptor
             else -> null
         }
     }
 
     private fun cacheIconDescriptor(entry: NotificationEntry, descriptor: StatusBarIcon) {
-        if (
-            android.app.Flags.notificationsUseAppIcon() ||
-                android.app.Flags.notificationsUseMonochromeAppIcon()
-        ) {
-            // If either of the new icon flags is enabled, we cache the icon all the time.
+        if (android.app.Flags.notificationsRedesignAppIcons()) {
+            // Although we're not actually using the app icon in the status bar, let's make sure
+            // we cache the icon all the time when the flag is on.
             when (descriptor.type) {
                 StatusBarIcon.Type.PeopleAvatar -> entry.icons.peopleAvatarDescriptor = descriptor
-                // When notificationsUseMonochromeAppIcon is enabled, we use the appIconDescriptor.
-                StatusBarIcon.Type.MaybeMonochromeAppIcon ->
-                    entry.icons.appIconDescriptor = descriptor
                 // When notificationsUseAppIcon is enabled, the app icon overrides the small icon.
                 // But either way, it's a good idea to cache the descriptor.
                 else -> entry.icons.smallIconDescriptor = descriptor
@@ -312,7 +294,7 @@
     private fun setIcon(
         entry: NotificationEntry,
         iconDescriptor: StatusBarIcon,
-        iconView: StatusBarIconView
+        iconView: StatusBarIconView,
     ) {
         iconView.setShowsConversation(showsConversation(entry, iconView, iconDescriptor))
         iconView.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP)
@@ -323,7 +305,7 @@
 
     private fun Icon.toStatusBarIcon(
         entry: NotificationEntry,
-        type: StatusBarIcon.Type
+        type: StatusBarIcon.Type,
     ): StatusBarIcon {
         val n = entry.sbn.notification
         return StatusBarIcon(
@@ -333,7 +315,7 @@
             n.iconLevel,
             n.number,
             iconBuilder.getIconContentDescription(n),
-            type
+            type,
         )
     }
 
@@ -347,7 +329,7 @@
                 } catch (e: Exception) {
                     Log.e(
                         TAG,
-                        "Error calling LauncherApps#getShortcutIcon for notification $entry: $e"
+                        "Error calling LauncherApps#getShortcutIcon for notification $entry: $e",
                     )
                 }
             }
@@ -431,7 +413,7 @@
     private fun showsConversation(
         entry: NotificationEntry,
         iconView: StatusBarIconView,
-        iconDescriptor: StatusBarIcon
+        iconDescriptor: StatusBarIcon,
     ): Boolean {
         val usedInSensitiveContext =
             iconView === entry.icons.shelfIcon || iconView === entry.icons.aodIcon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index 611cebc..cb6be66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -34,7 +34,6 @@
     @Nullable private final StatusBarIconView mAodIcon;
 
     @Nullable private StatusBarIcon mSmallIconDescriptor;
-    @Nullable private StatusBarIcon mAppIconDescriptor;
     @Nullable private StatusBarIcon mPeopleAvatarDescriptor;
 
     private boolean mIsImportantConversation;
@@ -127,15 +126,6 @@
         mPeopleAvatarDescriptor = peopleAvatarDescriptor;
     }
 
-    @Nullable
-    StatusBarIcon getAppIconDescriptor() {
-        return mAppIconDescriptor;
-    }
-
-    void setAppIconDescriptor(@Nullable StatusBarIcon appIconDescriptor) {
-        mAppIconDescriptor = appIconDescriptor;
-    }
-
     boolean isImportantConversation() {
         return mIsImportantConversation;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
index 6b5642a..83f56a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/StatusBarIconViewBinder.kt
@@ -20,7 +20,6 @@
 import android.view.View
 import com.android.app.tracing.traceSection
 import com.android.internal.util.ContrastColorUtil
-import com.android.systemui.Flags
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.NO_COLOR
@@ -36,11 +35,9 @@
 
     suspend fun bindColor(view: StatusBarIconView, color: Flow<Int>) {
         color.collectTracingEach("SBIV#bindColor") { color ->
-            // Don't change the icon color if an app icon experiment is enabled.
-            if (!android.app.Flags.notificationsUseAppIcon()) {
-                view.staticDrawableColor = color
-            }
-            // Continue changing the overflow dot color
+            // Set the color for the icons
+            view.staticDrawableColor = color
+            // Set the color for the overflow dot
             view.setDecorColor(color)
         }
     }
@@ -59,14 +56,12 @@
         contrastColorUtil: ContrastColorUtil,
     ) {
         iconColors.collectTracingEach("SBIV#bindIconColors") { colors ->
-            // Don't change the icon color if an app icon experiment is enabled.
-            if (!android.app.Flags.notificationsUseAppIcon()) {
-                val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
-                val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
-                view.staticDrawableColor =
-                    if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR
-            }
-            // Continue changing the overflow dot color
+            // Set the icon color
+            val isPreL = java.lang.Boolean.TRUE == view.getTag(R.id.icon_is_pre_L)
+            val isColorized = !isPreL || NotificationUtils.isGrayscale(view, contrastColorUtil)
+            view.staticDrawableColor =
+                if (isColorized) colors.staticDrawableColor(view.viewBounds) else NO_COLOR
+            // Set the color for the overflow dot
             view.setDecorColor(colors.tint)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index f352123..b622def 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -203,11 +203,7 @@
         addRemainingTransformTypes();
         updateCropToPaddingForImageViews();
         Notification n = row.getEntry().getSbn().getNotification();
-        if (n.shouldUseAppIcon()) {
-            mIcon.setTag(ImageTransformState.ICON_TAG, n.getAppIcon());
-        } else {
-            mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon());
-        }
+        mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon());
 
         // We need to reset all views that are no longer transforming in case a view was previously
         // transformed, but now we decided to transform its container instead.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 7389086..80c8e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -203,7 +203,7 @@
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
 import com.android.systemui.statusbar.core.StatusBarInitializer;
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
 import com.android.systemui.statusbar.data.model.StatusBarMode;
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -1231,7 +1231,7 @@
                         checkBarModes();
                     });
         }
-        if (!StatusBarSimpleFragment.isEnabled() && !StatusBarConnectedDisplays.isEnabled()) {
+        if (!StatusBarRootModernization.isEnabled() && !StatusBarConnectedDisplays.isEnabled()) {
             // When the flag is on, we register the fragment as a core startable and this is not
             // needed
             mStatusBarInitializer.initializeStatusBar();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index ba878ed..58386b0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl
 import com.android.systemui.statusbar.core.StatusBarInitializerStore
 import com.android.systemui.statusbar.core.StatusBarOrchestrator
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule
 import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
@@ -87,7 +87,7 @@
             return if (StatusBarConnectedDisplays.isEnabled) {
                 // Will be started through MultiDisplayStatusBarStarter
                 CoreStartable.NOP
-            } else if (StatusBarSimpleFragment.isEnabled) {
+            } else if (StatusBarRootModernization.isEnabled) {
                 defaultInitializerLazy.get()
             } else {
                 // Will be started through CentralSurfaces
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 5cc4476..c55a63c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -58,7 +58,7 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
@@ -365,7 +365,7 @@
         mPrimaryOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip_primary);
         mSecondaryOngoingActivityChip =
                 mStatusBar.findViewById(R.id.ongoing_activity_chip_secondary);
-        if (!StatusBarSimpleFragment.isEnabled()) {
+        if (!StatusBarRootModernization.isEnabled()) {
             showEndSideContent(false);
             showClock(false);
         }
@@ -464,7 +464,7 @@
         super.onPause();
         mCommandQueue.removeCallback(this);
         mStatusBarStateController.removeCallback(this);
-        if (!StatusBarSimpleFragment.isEnabled()) {
+        if (!StatusBarRootModernization.isEnabled()) {
             mOngoingCallController.removeCallback(mOngoingCallListener);
         }
         mAnimationScheduler.removeCallback(this);
@@ -507,7 +507,7 @@
             mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons, displayId);
         }
 
-        if (!StatusBarSimpleFragment.isEnabled()) {
+        if (!StatusBarRootModernization.isEnabled()) {
             updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false);
         }
         Trace.endSection();
@@ -528,7 +528,7 @@
             new StatusBarVisibilityChangeListener() {
                 @Override
                 public void onStatusBarVisibilityMaybeChanged() {
-                    if (StatusBarSimpleFragment.isEnabled()) {
+                    if (StatusBarRootModernization.isEnabled()) {
                         return;
                     }
                     updateStatusBarVisibilities(/* animate= */ true);
@@ -536,7 +536,7 @@
 
                 @Override
                 public void onTransitionFromLockscreenToDreamStarted() {
-                    if (StatusBarSimpleFragment.isEnabled()) {
+                    if (StatusBarRootModernization.isEnabled()) {
                         return;
                     }
                     mTransitionFromLockscreenToDreamStarted = true;
@@ -547,7 +547,7 @@
                         boolean hasPrimaryOngoingActivity,
                         boolean hasSecondaryOngoingActivity,
                         boolean shouldAnimate) {
-                    if (StatusBarSimpleFragment.isEnabled()) {
+                    if (StatusBarRootModernization.isEnabled()) {
                         return;
                     }
                     mHasPrimaryOngoingActivity = hasPrimaryOngoingActivity;
@@ -558,7 +558,7 @@
                 @Override
                 public void onIsHomeStatusBarAllowedBySceneChanged(
                         boolean isHomeStatusBarAllowedByScene) {
-                    if (StatusBarSimpleFragment.isEnabled()) {
+                    if (StatusBarRootModernization.isEnabled()) {
                         return;
                     }
                     mHomeStatusBarAllowedByScene = isHomeStatusBarAllowedByScene;
@@ -568,7 +568,7 @@
 
     @Override
     public void disable(int displayId, int state1, int state2, boolean animate) {
-        if (StatusBarSimpleFragment.isEnabled()) {
+        if (StatusBarRootModernization.isEnabled()) {
             return;
         }
         if (displayId != getContext().getDisplayId()) {
@@ -582,7 +582,7 @@
     }
 
     private void updateStatusBarVisibilities(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
 
         StatusBarVisibilityModel previousModel = mLastModifiedVisibility;
         StatusBarVisibilityModel newModel = calculateInternalModel(mLastSystemVisibility);
@@ -623,7 +623,7 @@
 
     private StatusBarVisibilityModel calculateInternalModel(
             StatusBarVisibilityModel externalModel) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
 
         // TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead.
         boolean headsUpVisible =
@@ -677,7 +677,7 @@
      * mLastModifiedVisibility.
      */
     private void updateNotificationIconAreaAndOngoingActivityChip(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
 
         StatusBarVisibilityModel visibilityModel = mLastModifiedVisibility;
         boolean disableNotifications = !visibilityModel.getShowNotificationIcons();
@@ -714,7 +714,7 @@
     }
 
     private boolean shouldHideStatusBar() {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
 
         boolean isDefaultDisplay = getContext().getDisplayId() == Display.DEFAULT_DISPLAY;
         boolean shouldHideForCurrentDisplay =
@@ -776,7 +776,7 @@
     }
 
     private void hideEndSideContent(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         if (!animate || !mAnimationsEnabled) {
             mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER);
         } else {
@@ -786,7 +786,7 @@
     }
 
     private void showEndSideContent(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         if (!animate || !mAnimationsEnabled) {
             mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER);
             return;
@@ -803,18 +803,18 @@
     }
 
     private void hideClock(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         animateHiddenState(mClockView, clockHiddenMode(), animate);
     }
 
     private void showClock(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         animateShow(mClockView, animate);
     }
 
     /** Hides the primary ongoing activity chip. */
     private void hidePrimaryOngoingActivityChip(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         animateHiddenState(mPrimaryOngoingActivityChip, View.GONE, animate);
     }
 
@@ -826,18 +826,18 @@
      * activities. See b/332662551.
      */
     private void showPrimaryOngoingActivityChip(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         animateShow(mPrimaryOngoingActivityChip, animate);
     }
 
     private void hideSecondaryOngoingActivityChip(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         animateHiddenState(mSecondaryOngoingActivityChip, View.GONE, animate);
     }
 
     private void showSecondaryOngoingActivityChip(boolean animate) {
         StatusBarNotifChips.assertInNewMode();
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         animateShow(mSecondaryOngoingActivityChip, animate);
     }
 
@@ -846,7 +846,7 @@
      * don't set the clock GONE otherwise it'll mess up the animation.
      */
     private int clockHiddenMode() {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         if (!mShadeExpansionStateManager.isClosed() && !mKeyguardStateController.isShowing()
                 && !mStatusBarStateController.isDozing()) {
             return View.INVISIBLE;
@@ -855,24 +855,24 @@
     }
 
     public void hideNotificationIconArea(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         animateHide(mNotificationIconAreaInner, animate);
     }
 
     public void showNotificationIconArea(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         animateShow(mNotificationIconAreaInner, animate);
     }
 
     public void hideOperatorName(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         if (mOperatorNameViewController != null) {
             animateHide(mOperatorNameViewController.getView(), animate);
         }
     }
 
     public void showOperatorName(boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         if (mOperatorNameViewController != null) {
             animateShow(mOperatorNameViewController.getView(), animate);
         }
@@ -882,7 +882,7 @@
      * Animate a view to INVISIBLE or GONE
      */
     private void animateHiddenState(final View v, int state, boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         v.animate().cancel();
         if (!animate || !mAnimationsEnabled) {
             v.setAlpha(0f);
@@ -902,7 +902,7 @@
      * Hides a view.
      */
     private void animateHide(final View v, boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         animateHiddenState(v, View.INVISIBLE, animate);
     }
 
@@ -910,7 +910,7 @@
      * Shows a view, and synchronizes the animation with Keyguard exit animations, if applicable.
      */
     private void animateShow(View v, boolean animate) {
-        StatusBarSimpleFragment.assertInLegacyMode();
+        StatusBarRootModernization.assertInLegacyMode();
         v.animate().cancel();
         v.setVisibility(View.VISIBLE);
         if (!animate || !mAnimationsEnabled) {
@@ -949,7 +949,7 @@
             mOperatorNameViewController.init();
             // This view should not be visible on lock-screen
             if (mKeyguardStateController.isShowing()) {
-                if (!StatusBarSimpleFragment.isEnabled()) {
+                if (!StatusBarRootModernization.isEnabled()) {
                     hideOperatorName(false);
                 }
             }
@@ -957,7 +957,7 @@
     }
 
     private void initOngoingCallChip() {
-        if (!StatusBarSimpleFragment.isEnabled()) {
+        if (!StatusBarRootModernization.isEnabled()) {
             mOngoingCallController.addCallback(mOngoingCallListener);
         }
         // TODO(b/364653005): Do we also need to set the secondary activity chip?
@@ -969,7 +969,7 @@
 
     @Override
     public void onDozingChanged(boolean isDozing) {
-        if (StatusBarSimpleFragment.isEnabled()) {
+        if (StatusBarRootModernization.isEnabled()) {
             return;
         }
         updateStatusBarVisibilities(/* animate= */ false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
index eaf15a8..e4929b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt
@@ -20,7 +20,7 @@
 import androidx.core.animation.Interpolator
 import androidx.core.animation.ValueAnimator
 import com.android.app.animation.InterpolatorsAndroidX
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 
 /**
  * A controller that keeps track of multiple sources applying alpha value changes to a view. It will
@@ -75,7 +75,7 @@
 
     private fun applyAlphaToView() {
         val minAlpha = getMinAlpha()
-        if (!StatusBarSimpleFragment.isEnabled) {
+        if (!StatusBarRootModernization.isEnabled) {
             view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE
             view.alpha = minAlpha
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 11d7339..2177e02 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -22,6 +22,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -30,13 +31,12 @@
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
 import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder
 import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
 import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
 import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
 import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Interface to assist with binding the [CollapsedStatusBarFragment] to [HomeStatusBarViewModel].
@@ -91,7 +91,7 @@
                     launch {
                         viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
                             OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView)
-                            if (StatusBarSimpleFragment.isEnabled) {
+                            if (StatusBarRootModernization.isEnabled) {
                                 when (primaryChipModel) {
                                     is OngoingActivityChipModel.Shown ->
                                         primaryChipView.show(shouldAnimateChange = true)
@@ -133,7 +133,7 @@
                             // enough space for it.
                             OngoingActivityChipBinder.bind(chips.secondary, secondaryChipView)
 
-                            if (StatusBarSimpleFragment.isEnabled) {
+                            if (StatusBarRootModernization.isEnabled) {
                                 primaryChipView.adjustVisibility(chips.primary.toVisibilityModel())
                                 secondaryChipView.adjustVisibility(
                                     chips.secondary.toVisibilityModel()
@@ -160,7 +160,7 @@
                     }
                 }
 
-                if (StatusBarSimpleFragment.isEnabled) {
+                if (StatusBarRootModernization.isEnabled) {
                     val clockView = view.requireViewById<View>(R.id.clock)
                     launch { viewModel.isClockVisible.collect { clockView.adjustVisibility(it) } }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 247abc3..a9c2f72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -104,7 +104,7 @@
     darkIconDispatcher: DarkIconDispatcher,
     onViewCreated: (ViewGroup) -> Unit,
 ) {
-    // None of these methods are used when [StatusBarSimpleFragment] is on.
+    // None of these methods are used when [StatusBarRootModernization] is on.
     // This can be deleted once the fragment is gone
     val nopVisibilityChangeListener =
         object : StatusBarVisibilityChangeListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
index 3f6ef16..b79d39e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
@@ -35,7 +35,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.compose.ComposeInitializer;
-import com.android.systemui.statusbar.core.StatusBarSimpleFragment;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
 import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
 
 /**
@@ -64,7 +64,7 @@
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
 
-        if (StatusBarSimpleFragment.isEnabled()) {
+        if (StatusBarRootModernization.isEnabled()) {
             ComposeInitializer.INSTANCE.onAttachedToWindow(this);
         }
     }
@@ -73,7 +73,7 @@
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
-        if (StatusBarSimpleFragment.isEnabled()) {
+        if (StatusBarRootModernization.isEnabled()) {
             ComposeInitializer.INSTANCE.onDetachedFromWindow(this);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 493aa8c..e9a33e0 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -19,12 +19,18 @@
 
 import android.annotation.SuppressLint
 import android.annotation.UserIdInt
+import android.app.admin.DevicePolicyManager
 import android.content.Context
+import android.content.IntentFilter
 import android.content.pm.UserInfo
+import android.content.res.Resources
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -38,6 +44,7 @@
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.GlobalSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -49,11 +56,12 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 
@@ -100,6 +108,12 @@
     /** Whether refresh users should be paused. */
     var isRefreshUsersPaused: Boolean
 
+    /** Whether logout for secondary users is enabled by admin device policy. */
+    val isSecondaryUserLogoutEnabled: StateFlow<Boolean>
+
+    /** Whether logout into system user is enabled. */
+    val isLogoutToSystemUserEnabled: StateFlow<Boolean>
+
     /** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */
     fun refreshUsers()
 
@@ -109,6 +123,12 @@
 
     fun isUserSwitcherEnabled(): Boolean
 
+    /** Performs logout logout for secondary users. */
+    suspend fun logOutSecondaryUser()
+
+    /** Performs logout into the system user. */
+    suspend fun logOutToSystemUser()
+
     /**
      * Returns the user ID of the "main user" of the device. This user may have access to certain
      * features which are limited to at most one user. There will never be more than one main user
@@ -131,12 +151,16 @@
 @Inject
 constructor(
     @Application private val appContext: Context,
+    @Main private val resources: Resources,
     private val manager: UserManager,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val globalSettings: GlobalSettings,
     private val tracker: UserTracker,
+    private val devicePolicyManager: DevicePolicyManager,
+    private val broadcastDispatcher: BroadcastDispatcher,
+    private val statusBarService: IStatusBarService,
 ) : UserRepository {
 
     private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> =
@@ -147,7 +171,7 @@
                         SETTING_SIMPLE_USER_SWITCHER,
                         Settings.Global.ADD_USERS_WHEN_LOCKED,
                         Settings.Global.USER_SWITCHER_ENABLED,
-                    ),
+                    )
             )
             .onStart { emit(Unit) } // Forces an initial update.
             .map { getSettings() }
@@ -163,6 +187,7 @@
 
     override var mainUserId: Int = UserHandle.USER_NULL
         private set
+
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL
         private set
 
@@ -221,12 +246,73 @@
             .stateIn(
                 applicationScope,
                 SharingStarted.Eagerly,
-                initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus)
+                initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus),
             )
     }
 
     override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
 
+    /** Whether the secondary user logout is enabled by the admin device policy. */
+    private val isSecondaryUserLogoutSupported: Flow<Boolean> =
+        broadcastDispatcher
+            .broadcastFlow(
+                filter =
+                    IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
+            ) { intent, _ ->
+                if (
+                    DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED == intent.action
+                ) {
+                    Unit
+                } else {
+                    null
+                }
+            }
+            .filterNotNull()
+            .onStart { emit(Unit) }
+            .map { _ -> devicePolicyManager.isLogoutEnabled() }
+            .flowOn(backgroundDispatcher)
+
+    @SuppressLint("MissingPermission")
+    override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> =
+        selectedUser
+            .flatMapLatestConflated { selectedUser ->
+                if (selectedUser.isEligibleForLogout()) {
+                    isSecondaryUserLogoutSupported
+                } else {
+                    flowOf(false)
+                }
+            }
+            .stateIn(applicationScope, SharingStarted.Eagerly, false)
+
+    @SuppressLint("MissingPermission")
+    override val isLogoutToSystemUserEnabled: StateFlow<Boolean> =
+        selectedUser
+            .flatMapLatestConflated { selectedUser ->
+                if (selectedUser.isEligibleForLogout()) {
+                    flowOf(
+                        resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen)
+                    )
+                } else {
+                    flowOf(false)
+                }
+            }
+            .stateIn(applicationScope, SharingStarted.Eagerly, false)
+
+    @SuppressLint("MissingPermission")
+    override suspend fun logOutSecondaryUser() {
+        if (isSecondaryUserLogoutEnabled.value) {
+            withContext(backgroundDispatcher) { devicePolicyManager.logoutUser() }
+        }
+    }
+
+    override suspend fun logOutToSystemUser() {
+        // TODO(b/377493351) : start using proper logout API once it is available.
+        // Using reboot is a temporary solution.
+        if (isLogoutToSystemUserEnabled.value) {
+            withContext(backgroundDispatcher) { statusBarService.reboot(false) }
+        }
+    }
+
     @SuppressLint("MissingPermission")
     override fun refreshUsers() {
         applicationScope.launch {
@@ -277,10 +363,7 @@
                 ) != 0
 
             val isAddUsersFromLockscreen =
-                globalSettings.getInt(
-                    Settings.Global.ADD_USERS_WHEN_LOCKED,
-                    0,
-                ) != 0
+                globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0
 
             val isUserSwitcherEnabled =
                 globalSettings.getInt(
@@ -309,3 +392,11 @@
         @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
     }
 }
+
+fun SelectedUserModel.isEligibleForLogout(): Boolean {
+    // TODO(b/206032495): should call mDevicePolicyManager.getLogoutUserId() instead of
+    // hardcode it to USER_SYSTEM so it properly supports headless system user mode
+    // (and then call mDevicePolicyManager.clearLogoutUser() after switched)
+    return selectionStatus == SelectionStatus.SELECTION_COMPLETE &&
+        userInfo.id != android.os.UserHandle.USER_SYSTEM
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
new file mode 100644
index 0000000..f2dd25f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLogoutInteractor.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.user.domain.interactor
+
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
+
+/** Encapsulates business logic to for the logout. */
+@SysUISingleton
+class UserLogoutInteractor
+@Inject
+constructor(
+    private val userRepository: UserRepository,
+    @Application private val applicationScope: CoroutineScope,
+) {
+
+    val isLogoutEnabled: StateFlow<Boolean> =
+        combine(
+                userRepository.isSecondaryUserLogoutEnabled,
+                userRepository.isLogoutToSystemUserEnabled,
+                Boolean::or,
+            )
+            .stateIn(applicationScope, SharingStarted.Eagerly, false)
+
+    fun logOut() {
+        applicationScope.launch {
+            if (userRepository.isSecondaryUserLogoutEnabled.value) {
+                userRepository.logOutSecondaryUser()
+            } else if (userRepository.isLogoutToSystemUserEnabled.value) {
+                userRepository.logOutToSystemUser()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
index 71335ec..bc3726d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
@@ -30,6 +30,7 @@
  * Repository for observing values of [Settings.Secure] for the currently active user. That means
  * when user is switched and the new user has different value, flow will emit new value.
  */
+// TODO: b/377244768 - Make internal once call sites inject SecureSettingsRepository instead.
 @SysUISingleton
 class UserAwareSecureSettingsRepository
 @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
index a31b8d9..49a0f14 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.util.settings.repository
 
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -34,10 +33,10 @@
 
 /**
  * Repository for observing values of a [UserSettingsProxy], for the currently active user. That
- * means that when user is switched and the new user has a different value, the flow will emit the
- * new value.
+ * means that when the user is switched and the new user has a different value, the flow will emit
+ * the new value.
  */
-@SysUISingleton
+// TODO: b/377244768 - Make internal when UserAwareSecureSettingsRepository can be made internal.
 @OptIn(ExperimentalCoroutinesApi::class)
 abstract class UserAwareSettingsRepository(
     private val userSettings: UserSettingsProxy,
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
index 8b1fca5..4b01ded 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
@@ -17,12 +17,10 @@
 package com.android.systemui.util.settings.repository
 
 import android.provider.Settings
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.settings.SystemSettings
-import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineDispatcher
 
@@ -30,10 +28,8 @@
  * Repository for observing values of [Settings.Secure] for the currently active user. That means
  * when user is switched and the new user has different value, flow will emit new value.
  */
-@SysUISingleton
-class UserAwareSystemSettingsRepository
-@Inject
-constructor(
+// TODO: b/377244768 - Make internal once call sites inject SystemSettingsRepository instead.
+class UserAwareSystemSettingsRepository(
     systemSettings: SystemSettings,
     userRepository: UserRepository,
     @Background backgroundDispatcher: CoroutineDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
index 9440a93..fb15795 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.dialog.dagger
 
+import com.android.systemui.volume.dialog.dagger.module.VolumeDialogModule
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
@@ -28,7 +29,7 @@
  * [com.android.systemui.volume.dialog.VolumeDialogPlugin] and lives alongside it.
  */
 @VolumeDialogScope
-@Subcomponent(modules = [])
+@Subcomponent(modules = [VolumeDialogModule::class])
 interface VolumeDialogComponent {
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
new file mode 100644
index 0000000..025e269
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/module/VolumeDialogModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.volume.dialog.dagger.module
+
+import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepository
+import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** Dagger module for volume dialog code in the volume package */
+@Module
+interface VolumeDialogModule {
+
+    @Binds
+    fun bindVolumeDialogRingerFeedbackRepository(
+        ringerFeedbackRepository: VolumeDialogRingerFeedbackRepositoryImpl
+    ): VolumeDialogRingerFeedbackRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.kt
new file mode 100644
index 0000000..263972b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepository.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.volume.dialog.ringer.data.repository
+
+import android.content.Context
+import com.android.systemui.Prefs
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+interface VolumeDialogRingerFeedbackRepository {
+
+    /** gets number of shown toasts */
+    suspend fun getToastCount(): Int
+
+    /** updates number of shown toasts */
+    suspend fun updateToastCount(toastCount: Int)
+}
+
+class VolumeDialogRingerFeedbackRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    @Background val backgroundDispatcher: CoroutineDispatcher,
+) : VolumeDialogRingerFeedbackRepository {
+
+    override suspend fun getToastCount(): Int =
+        withContext(backgroundDispatcher) {
+            return@withContext Prefs.getInt(
+                applicationContext,
+                Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT,
+                0,
+            )
+        }
+
+    override suspend fun updateToastCount(toastCount: Int) {
+        withContext(backgroundDispatcher) {
+            Prefs.putInt(applicationContext, Prefs.Key.SEEN_RINGER_GUIDANCE_COUNT, toastCount + 1)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
index 281e57f..b83613b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractor.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.plugins.VolumeDialogController
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
+import com.android.systemui.volume.dialog.ringer.data.repository.VolumeDialogRingerFeedbackRepository
 import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
 import javax.inject.Inject
@@ -45,6 +46,7 @@
     volumeDialogStateInteractor: VolumeDialogStateInteractor,
     private val controller: VolumeDialogController,
     private val audioSystemRepository: AudioSystemRepository,
+    private val ringerFeedbackRepository: VolumeDialogRingerFeedbackRepository,
 ) {
 
     val ringerModel: Flow<VolumeDialogRingerModel> =
@@ -84,4 +86,12 @@
     fun scheduleTouchFeedback() {
         controller.scheduleTouchFeedback()
     }
+
+    suspend fun getToastCount(): Int {
+        return ringerFeedbackRepository.getToastCount()
+    }
+
+    suspend fun updateToastCount(toastCount: Int) {
+        ringerFeedbackRepository.updateToastCount(toastCount)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
index d4da226..e040638 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -16,17 +16,23 @@
 
 package com.android.systemui.volume.dialog.ringer.ui.viewmodel
 
+import android.content.Context
 import android.media.AudioAttributes
 import android.media.AudioManager.RINGER_MODE_NORMAL
 import android.media.AudioManager.RINGER_MODE_SILENT
 import android.media.AudioManager.RINGER_MODE_VIBRATE
 import android.os.VibrationEffect
+import android.widget.Toast
+import com.android.internal.R as internalR
+import com.android.settingslib.Utils
 import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.ringer.domain.VolumeDialogRingerInteractor
 import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
 import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
@@ -40,26 +46,37 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+private const val SHOW_RINGER_TOAST_COUNT = 12
 
 class VolumeDialogRingerDrawerViewModel
 @AssistedInject
 constructor(
+    @Application private val applicationContext: Context,
     @VolumeDialog private val coroutineScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val interactor: VolumeDialogRingerInteractor,
     private val vibrator: VibratorHelper,
     private val volumeDialogLogger: VolumeDialogLogger,
+    private val visibilityInteractor: VolumeDialogVisibilityInteractor,
 ) {
 
     private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial)
 
     val ringerViewModel: StateFlow<RingerViewModelState> =
         combine(interactor.ringerModel, drawerState) { ringerModel, state ->
+                level = ringerModel.level
+                levelMax = ringerModel.levelMax
                 ringerModel.toViewModel(state)
             }
             .flowOn(backgroundDispatcher)
             .stateIn(coroutineScope, SharingStarted.Eagerly, RingerViewModelState.Unavailable)
 
+    // Level and Maximum level of Ring Stream.
+    private var level = -1
+    private var levelMax = -1
+
     // Vibration attributes.
     private val sonificiationVibrationAttributes =
         AudioAttributes.Builder()
@@ -71,8 +88,10 @@
         if (drawerState.value is RingerDrawerState.Open) {
             Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value)
             provideTouchFeedback(ringerMode)
+            maybeShowToast(ringerMode)
             interactor.setRingerMode(ringerMode)
         }
+        visibilityInteractor.resetDismissTimeout()
         drawerState.value =
             when (drawerState.value) {
                 is RingerDrawerState.Initial -> {
@@ -201,6 +220,46 @@
         }
     }
 
+    private fun maybeShowToast(ringerMode: RingerMode) {
+        coroutineScope.launch {
+            val seenToastCount = interactor.getToastCount()
+            if (seenToastCount > SHOW_RINGER_TOAST_COUNT) {
+                return@launch
+            }
+
+            val toastText =
+                when (ringerMode.value) {
+                    RINGER_MODE_NORMAL -> {
+                        if (level != -1 && levelMax != -1) {
+                            applicationContext.getString(
+                                R.string.volume_dialog_ringer_guidance_ring,
+                                Utils.formatPercentage(level.toLong(), levelMax.toLong()),
+                            )
+                        } else {
+                            null
+                        }
+                    }
+
+                    RINGER_MODE_SILENT ->
+                        applicationContext.getString(
+                            internalR.string.volume_dialog_ringer_guidance_silent
+                        )
+
+                    RINGER_MODE_VIBRATE ->
+                        applicationContext.getString(
+                            internalR.string.volume_dialog_ringer_guidance_vibrate
+                        )
+
+                    else ->
+                        applicationContext.getString(
+                            internalR.string.volume_dialog_ringer_guidance_vibrate
+                        )
+                }
+            toastText?.let { Toast.makeText(applicationContext, it, Toast.LENGTH_SHORT).show() }
+            interactor.updateToastCount(seenToastCount)
+        }
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): VolumeDialogRingerDrawerViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 8039e00..073781e 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -271,6 +271,12 @@
                         // No op.
                     }
                 }, mSysUiMainExecutor);
+        pip.addOnIsInPipStateChangedListener((isInPip) -> {
+            if (!isInPip) {
+                mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
+                        .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+            }
+        });
         mSysUiState.addCallback(sysUiStateFlag -> {
             mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
             pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 3d9eb53..a39ca5d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -103,6 +103,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.dreams.IDreamManager;
 import android.service.trust.TrustAgentService;
@@ -129,6 +130,7 @@
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.keyguard.logging.SimLogger;
 import com.android.settingslib.fuelgauge.BatteryStatus;
+import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
@@ -190,6 +192,7 @@
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper
+@EnableFlags(Flags.FLAG_USER_ENCRYPTED_SOURCE)
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
     private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY =
             "test_app_fp_listen_on_occluding_activity";
@@ -1292,12 +1295,15 @@
 
     @Test
     public void testIsUserUnlocked() {
+        when(mUserManager.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId())).thenReturn(
+                true);
         // mUserManager will report the user as unlocked on @Before
         assertThat(
                 mKeyguardUpdateMonitor.isUserUnlocked(mSelectedUserInteractor.getSelectedUserId()))
                 .isTrue();
         // Invalid user should not be unlocked.
         int randomUser = 99;
+        when(mUserManager.isUserUnlocked(randomUser)).thenReturn(false);
         assertThat(mKeyguardUpdateMonitor.isUserUnlocked(randomUser)).isFalse();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index df50f76..24bca70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -31,7 +31,6 @@
 import static org.mockito.Mockito.when;
 
 import android.app.IActivityManager;
-import android.app.admin.DevicePolicyManager;
 import android.app.trust.TrustManager;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
@@ -80,6 +79,7 @@
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor;
 import com.android.systemui.util.RingerModeLiveData;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.settings.FakeGlobalSettings;
@@ -106,7 +106,6 @@
 
     @Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs;
     @Mock private AudioManager mAudioManager;
-    @Mock private DevicePolicyManager mDevicePolicyManager;
     @Mock private LockPatternUtils mLockPatternUtils;
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private TelephonyListenerManager mTelephonyListenerManager;
@@ -140,6 +139,7 @@
     @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Mock private DialogTransitionAnimator mDialogTransitionAnimator;
     @Mock private SelectedUserInteractor mSelectedUserInteractor;
+    @Mock private UserLogoutInteractor mLogoutInteractor;
     @Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
     @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
 
@@ -166,7 +166,6 @@
         mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
                 mWindowManagerFuncs,
                 mAudioManager,
-                mDevicePolicyManager,
                 mLockPatternUtils,
                 mBroadcastDispatcher,
                 mTelephonyListenerManager,
@@ -198,6 +197,7 @@
                 mKeyguardUpdateMonitor,
                 mDialogTransitionAnimator,
                 mSelectedUserInteractor,
+                mLogoutInteractor,
                 mInteractor);
         mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 3bfde68..9096808 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -59,6 +59,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.back.BackAnimation
 import com.android.wm.shell.sysui.ShellInterface
 import com.google.common.util.concurrent.MoreExecutors
 import java.util.Optional
@@ -120,6 +121,7 @@
     private lateinit var unfoldTransitionProgressForwarder:
         Optional<UnfoldTransitionProgressForwarder>
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var backAnimation: Optional<BackAnimation>
 
     @Before
     fun setUp() {
@@ -289,6 +291,7 @@
             dumpManager,
             unfoldTransitionProgressForwarder,
             broadcastDispatcher,
+            backAnimation,
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt
index ae976a0..9fb752a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/DisplayTrackerImplTest.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.settings
 
 import android.hardware.display.DisplayManager
-import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.hardware.display.DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS
 import android.hardware.display.DisplayManagerGlobal
 import android.os.Handler
 import android.testing.AndroidTestingRunner
@@ -59,14 +59,14 @@
                 DisplayManagerGlobal.getInstance(),
                 Display.DEFAULT_DISPLAY,
                 DisplayInfo(),
-                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
             )
         mSecondaryDisplay =
             Display(
                 DisplayManagerGlobal.getInstance(),
                 Display.DEFAULT_DISPLAY + 1,
                 DisplayInfo(),
-                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+                DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS,
             )
 
         `when`(displayManager.displays).thenReturn(arrayOf(mDefaultDisplay, mSecondaryDisplay))
@@ -94,7 +94,12 @@
     fun registerBrightnessCallback_registersDisplayListener() {
         tracker.addBrightnessChangeCallback(TestCallback(), executor)
         verify(displayManager)
-            .registerDisplayListener(any(), any(), eq(EVENT_FLAG_DISPLAY_BRIGHTNESS))
+            .registerDisplayListener(
+                any(),
+                any(),
+                eq(0L),
+                eq(PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS),
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 4b648a3..d1e4f64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -17,7 +17,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS;
-import static com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
 
@@ -61,6 +60,7 @@
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.OperatorNameViewController;
 import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
+import com.android.systemui.statusbar.core.StatusBarRootModernization;
 import com.android.systemui.statusbar.disableflags.DisableFlagsLogger;
 import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder;
@@ -156,7 +156,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testDisableNone() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -167,7 +167,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testDisableSystemInfo_systemAnimationIdle_doesHide() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -185,7 +185,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -215,7 +215,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() {
         // GIVEN the status bar hides the system info via disable flags, while there is no event
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -232,7 +232,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -248,7 +248,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() {
         // GIVEN the status bar is not disabled
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
@@ -272,7 +272,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testDisableNotifications() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -290,7 +290,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testDisableNotifications_doesNothingWhenFlagEnabled() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -308,7 +308,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testDisableClock() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -326,7 +326,7 @@
     }
 
     @Test
-    @EnableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @EnableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testDisableClock_doesNothingWhenFlagEnabled() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -345,7 +345,7 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_shadeOpenAndShouldHide_everythingHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -363,7 +363,7 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_shadeOpenButNotShouldHide_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -382,7 +382,7 @@
     /** Regression test for b/279790651. */
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_shadeOpenAndShouldHide_thenShadeNotOpenAndDozingUpdate_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -410,7 +410,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_notTransitioningToOccluded_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -426,7 +426,7 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_isTransitioningToOccluded_everythingHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -442,7 +442,7 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_wasTransitioningToOccluded_transitionFinished_everythingShown() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -473,7 +473,7 @@
     }
 
     @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
     public void disable_noOngoingCall_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -485,7 +485,7 @@
     }
 
     @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
     public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -498,7 +498,7 @@
     }
 
     @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
     public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -511,7 +511,7 @@
     }
 
     @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
     public void disable_hasOngoingCallButAlsoHun_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -524,7 +524,7 @@
     }
 
     @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
     public void disable_ongoingCallEnded_chipHidden() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -548,7 +548,7 @@
     }
 
     @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
     public void disable_hasOngoingCall_hidesNotifsWithoutAnimation() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
@@ -565,7 +565,7 @@
     }
 
     @Test
-    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarRootModernization.FLAG_NAME})
     public void screenSharingChipsDisabled_ignoresNewCallback() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -599,7 +599,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void noOngoingActivity_chipHidden() {
         resumeAndGetFragment();
 
@@ -617,7 +617,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void hasPrimaryOngoingActivity_primaryChipDisplayedAndNotificationIconsHidden() {
         resumeAndGetFragment();
 
@@ -634,8 +634,8 @@
     @EnableFlags({
             FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS,
             StatusBarNotifChips.FLAG_NAME,
-            FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
-    public void hasPrimaryOngoingActivity_viewsUnchangedWhenSimpleFragmentFlagOn() {
+            StatusBarRootModernization.FLAG_NAME})
+    public void hasPrimaryOngoingActivity_viewsUnchangedWhenRootModernizationFlagOn() {
         resumeAndGetFragment();
 
         assertEquals(View.VISIBLE, getPrimaryOngoingActivityChipView().getVisibility());
@@ -660,7 +660,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
     public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() {
         resumeAndGetFragment();
 
@@ -674,7 +674,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() {
         resumeAndGetFragment();
 
@@ -689,7 +689,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
     public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -706,7 +706,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -724,7 +724,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
     public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -741,7 +741,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -759,7 +759,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
     public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() {
         resumeAndGetFragment();
 
@@ -782,7 +782,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() {
         resumeAndGetFragment();
 
@@ -805,7 +805,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void secondaryOngoingActivityEnded_chipHidden() {
         resumeAndGetFragment();
 
@@ -828,7 +828,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
     public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
@@ -847,7 +847,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         // Enable animations for testing so that we can verify we still aren't animating
@@ -866,7 +866,7 @@
 
     @Test
     @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS)
-    @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT})
+    @DisableFlags({StatusBarNotifChips.FLAG_NAME, StatusBarRootModernization.FLAG_NAME})
     public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -899,7 +899,7 @@
 
     @Test
     @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME})
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -933,7 +933,7 @@
 
     @Test
     @EnableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void isHomeStatusBarAllowedByScene_false_everythingHidden() {
         resumeAndGetFragment();
 
@@ -947,7 +947,7 @@
 
     @Test
     @EnableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void isHomeStatusBarAllowedByScene_true_everythingShown() {
         resumeAndGetFragment();
 
@@ -961,7 +961,7 @@
 
     @Test
     @EnableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_isHomeStatusBarAllowedBySceneFalse_disableValuesIgnored() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -979,7 +979,7 @@
 
     @Test
     @EnableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_isHomeStatusBarAllowedBySceneTrue_disableValuesUsed() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -997,7 +997,7 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void isHomeStatusBarAllowedByScene_sceneContainerDisabled_valueNotUsed() {
         resumeAndGetFragment();
 
@@ -1011,7 +1011,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_isDozing_clockAndSystemInfoVisible() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mStatusBarStateController.isDozing()).thenReturn(true);
@@ -1023,7 +1023,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_NotDozing_clockAndSystemInfoVisible() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mStatusBarStateController.isDozing()).thenReturn(false);
@@ -1035,7 +1035,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_headsUpShouldBeVisibleTrue_clockDisabled() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true);
@@ -1046,7 +1046,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() {
         CollapsedStatusBarFragment fragment = resumeAndGetFragment();
         when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false);
@@ -1100,7 +1100,7 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testStatusBarIcons_hiddenThroughoutCameraLaunch() {
         final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -1123,7 +1123,7 @@
 
     @Test
     @DisableSceneContainer
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testStatusBarIcons_hiddenThroughoutLockscreenToDreamTransition() {
         final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
@@ -1159,7 +1159,7 @@
     }
 
     @Test
-    @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+    @DisableFlags(StatusBarRootModernization.FLAG_NAME)
     public void testStatusBarIcons_lockscreenToDreamTransitionButNotDreaming_iconsVisible() {
         final CollapsedStatusBarFragment fragment = resumeAndGetFragment();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
index c435d3d..37671e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt
@@ -21,9 +21,9 @@
 import android.testing.TestableLooper
 import android.view.View
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.statusbar.core.StatusBarRootModernization
 import junit.framework.Assert.assertEquals
 import org.junit.Before
 import org.junit.Rule
@@ -38,7 +38,7 @@
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @SmallTest
-@DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT)
+@DisableFlags(StatusBarRootModernization.FLAG_NAME)
 class MultiSourceMinAlphaControllerTest : SysuiTestCase() {
 
     private val view = View(context)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
index ad5242e..4546b99 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
@@ -26,7 +26,7 @@
 
 class FakeScreenBrightnessRepository(
     initialBrightnessInfo: BrightnessInfo =
-        BrightnessInfo(0f, 0f, 1f, HIGH_BRIGHTNESS_MODE_OFF, 1f, BRIGHTNESS_MAX_REASON_NONE)
+        BrightnessInfo(0f, 0f, 1f, HIGH_BRIGHTNESS_MODE_OFF, 1f, BRIGHTNESS_MAX_REASON_NONE),
 ) : ScreenBrightnessRepository {
 
     private val brightnessInfo = MutableStateFlow(initialBrightnessInfo)
@@ -36,6 +36,8 @@
     override val linearBrightness = brightnessInfo.map { LinearBrightness(it.brightness) }
     override val minLinearBrightness = brightnessInfo.map { LinearBrightness(it.brightnessMinimum) }
     override val maxLinearBrightness = brightnessInfo.map { LinearBrightness(it.brightnessMaximum) }
+    override val isBrightnessOverriddenByWindow =
+        MutableStateFlow(initialBrightnessInfo.isBrightnessOverrideByWindow).asStateFlow()
 
     override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
         return minMaxLinearBrightness()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
index 52cdbed..2198e04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
+import com.android.systemui.kosmos.brightnessWarningToast
 
 val Kosmos.brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory by
     Kosmos.Fixture {
@@ -32,6 +33,7 @@
                     hapticsViewModelFactory = sliderHapticsViewModelFactory,
                     brightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor,
                     supportsMirroring = allowsMirroring,
+                    brightnessWarningToast = brightnessWarningToast,
                 )
             }
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index c41493e..8022e6e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -31,8 +31,11 @@
 import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
 import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
 import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
+import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
 import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
+import com.android.systemui.keyboard.shortcut.ui.ShortcutCustomizationDialogStarter
+import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel
 import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
 import com.android.systemui.keyguard.data.repository.fakeCommandQueue
 import com.android.systemui.kosmos.Kosmos
@@ -42,6 +45,7 @@
 import com.android.systemui.model.sysUiState
 import com.android.systemui.settings.displayTracker
 import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.phone.systemUIDialogFactory
 
 var Kosmos.shortcutHelperAppCategoriesShortcutsSource: KeyboardShortcutGroupsSource by
     Kosmos.Fixture { AppCategoriesShortcutsSource(windowManager, testDispatcher) }
@@ -121,3 +125,26 @@
             shortcutHelperCategoriesInteractor,
         )
     }
+
+val Kosmos.shortcutCustomizationDialogStarterFactory by
+    Kosmos.Fixture {
+        object : ShortcutCustomizationDialogStarter.Factory {
+            override fun create(): ShortcutCustomizationDialogStarter {
+                return ShortcutCustomizationDialogStarter(
+                    shortcutCustomizationViewModelFactory,
+                    systemUIDialogFactory,
+                )
+            }
+        }
+    }
+
+val Kosmos.shortcutCustomizationInteractor by Kosmos.Fixture { ShortcutCustomizationInteractor() }
+
+val Kosmos.shortcutCustomizationViewModelFactory by
+    Kosmos.Fixture {
+        object : ShortcutCustomizationViewModel.Factory {
+            override fun create(): ShortcutCustomizationViewModel {
+                return ShortcutCustomizationViewModel(shortcutCustomizationInteractor)
+            }
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 72cb1df..f43841b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -3,6 +3,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.settings.brightness.ui.BrightnessWarningToast
+
+import com.android.systemui.util.mockito.mock
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -38,6 +41,9 @@
     testScope.backgroundScope.coroutineContext
 }
 var Kosmos.mainCoroutineContext: CoroutineContext by Fixture { testScope.coroutineContext }
+var Kosmos.brightnessWarningToast: BrightnessWarningToast by Kosmos.Fixture {
+    mock<BrightnessWarningToast>()
+}
 
 /**
  * Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt
index d1b613f..f63698a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/MediaInRowInLandscapeViewModelKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
 import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager
 import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
 import com.android.systemui.shade.data.repository.shadeRepository
@@ -56,4 +57,5 @@
         }
     mainResources.configuration.updateFrom(config)
     fakeConfigurationRepository.onConfigurationChange(config)
+    runCurrent()
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
index 88063c9..aac122c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/BrightnessSliderControllerKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.haptics.msdl.msdlPlayer
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.brightnessWarningToast
 import com.android.systemui.plugins.activityStarter
 import com.android.systemui.settings.brightness.BrightnessSliderController
 import com.android.systemui.util.time.systemClock
@@ -35,5 +36,6 @@
             msdlPlayer,
             systemClock,
             activityStarter,
+            brightnessWarningToast,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index ed335f9..85d582a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -29,6 +29,7 @@
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.yield
@@ -67,6 +68,14 @@
         )
     override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo }
 
+    private val _isSecondaryUserLogoutEnabled = MutableStateFlow<Boolean>(false)
+    override val isSecondaryUserLogoutEnabled: StateFlow<Boolean> =
+        _isSecondaryUserLogoutEnabled.asStateFlow()
+
+    private val _isLogoutToSystemUserEnabled = MutableStateFlow<Boolean>(false)
+    override val isLogoutToSystemUserEnabled: StateFlow<Boolean> =
+        _isLogoutToSystemUserEnabled.asStateFlow()
+
     override var mainUserId: Int = MAIN_USER_ID
     override var lastSelectedNonGuestUserId: Int = mainUserId
 
@@ -107,6 +116,28 @@
         return _userSwitcherSettings.value.isUserSwitcherEnabled
     }
 
+    fun setSecondaryUserLogoutEnabled(logoutEnabled: Boolean) {
+        _isSecondaryUserLogoutEnabled.value = logoutEnabled
+    }
+
+    var logOutSecondaryUserCallCount: Int = 0
+        private set
+
+    override suspend fun logOutSecondaryUser() {
+        logOutSecondaryUserCallCount++
+    }
+
+    fun setLogoutToSystemUserEnabled(logoutEnabled: Boolean) {
+        _isLogoutToSystemUserEnabled.value = logoutEnabled
+    }
+
+    var logOutToSystemUserCallCount: Int = 0
+        private set
+
+    override suspend fun logOutToSystemUser() {
+        logOutToSystemUserCallCount++
+    }
+
     fun setUserInfos(infos: List<UserInfo>) {
         _userInfos.value = infos
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt
new file mode 100644
index 0000000..d06e744
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLogoutInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.user.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.user.data.repository.userRepository
+
+val Kosmos.userLogoutInteractor by
+    Kosmos.Fixture {
+        UserLogoutInteractor(
+            userRepository = userRepository,
+            applicationScope = applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt
new file mode 100644
index 0000000..d42de1e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/FakeVolumeDialogRingerFeedbackRepository.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.volume.dialog.ringer.data.repository
+
+class FakeVolumeDialogRingerFeedbackRepository : VolumeDialogRingerFeedbackRepository {
+
+    private var seenToastCount = 0
+
+    override suspend fun getToastCount(): Int {
+        return seenToastCount
+    }
+
+    override suspend fun updateToastCount(toastCount: Int) {
+        seenToastCount = toastCount
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
new file mode 100644
index 0000000..44371b4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/data/repository/VolumeDialogRingerFeedbackRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open 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.volume.dialog.ringer.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeVolumeDialogRingerFeedbackRepository by
+    Kosmos.Fixture { FakeVolumeDialogRingerFeedbackRepository() }
+val Kosmos.volumeDialogRingerFeedbackRepository by
+    Kosmos.Fixture { fakeVolumeDialogRingerFeedbackRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
index 1addd91..a494d04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/domain/VolumeDialogRingerInteractorKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.plugins.volumeDialogController
 import com.android.systemui.volume.data.repository.audioSystemRepository
 import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
+import com.android.systemui.volume.dialog.ringer.data.repository.fakeVolumeDialogRingerFeedbackRepository
 
 val Kosmos.volumeDialogRingerInteractor by
     Kosmos.Fixture {
@@ -29,5 +30,6 @@
             volumeDialogStateInteractor = volumeDialogStateInteractor,
             controller = volumeDialogController,
             audioSystemRepository = audioSystemRepository,
+            ringerFeedbackRepository = fakeVolumeDialogRingerFeedbackRepository,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
index db1c01a..c8ba551 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
@@ -16,20 +16,24 @@
 
 package com.android.systemui.volume.dialog.ringer.ui.viewmodel
 
+import android.content.applicationContext
 import com.android.systemui.haptics.vibratorHelper
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor
 import com.android.systemui.volume.dialog.shared.volumeDialogLogger
 
 val Kosmos.volumeDialogRingerDrawerViewModel by
     Kosmos.Fixture {
         VolumeDialogRingerDrawerViewModel(
+            applicationContext = applicationContext,
             backgroundDispatcher = testDispatcher,
             coroutineScope = applicationCoroutineScope,
             interactor = volumeDialogRingerInteractor,
             vibrator = vibratorHelper,
             volumeDialogLogger = volumeDialogLogger,
+            visibilityInteractor = volumeDialogVisibilityInteractor,
         )
     }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 57b58d8..3a0f8c0 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -306,7 +306,7 @@
     }
 
     private fun isOnLargeScreen(): Boolean {
-        return context.resources.configuration.smallestScreenWidthDp >
+        return context.applicationContext.resources.configuration.smallestScreenWidthDp >
             INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
     }
 
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 4731cfb..0c2ce8d 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -376,6 +376,7 @@
         ":ravenwood-empty-res",
         ":framework-platform-compat-config",
         ":services-platform-compat-config",
+        "texts/ravenwood-build.prop",
     ],
     device_first_srcs: [
         ":apex_icu.dat",
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index e61a054..678a97b 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -22,7 +22,6 @@
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
 import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;
-import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
 
 import static org.junit.Assert.assertThrows;
 import static org.mockito.ArgumentMatchers.any;
@@ -95,8 +94,6 @@
     private static final String LIBRAVENWOOD_INITIALIZER_NAME = "ravenwood_initializer";
     private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop";
     private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
-    private static final String RAVENWOOD_BUILD_PROP =
-            getRavenwoodRuntimePath() + "ravenwood-data/build.prop";
 
     /**
      * When enabled, attempt to dump all thread stacks just before we hit the
@@ -209,7 +206,7 @@
         System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME));
 
         // Do the basic set up for the android sysprops.
-        RavenwoodSystemProperties.initialize(RAVENWOOD_BUILD_PROP);
+        RavenwoodSystemProperties.initialize();
         setSystemProperties(null);
 
         // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index d8f2b70..3ed8b0a 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -16,7 +16,6 @@
 package android.platform.test.ravenwood;
 
 import static android.os.Process.FIRST_APPLICATION_UID;
-import static android.os.Process.SYSTEM_UID;
 import static android.os.UserHandle.SYSTEM;
 
 import android.annotation.NonNull;
@@ -61,17 +60,14 @@
      * Unless the test author requests differently, run as "nobody", and give each collection of
      * tests its own unique PID.
      */
-    int mUid = NOBODY_UID;
+    int mUid = FIRST_APPLICATION_UID;
     int mPid = sNextPid.getAndIncrement();
 
     String mTestPackageName;
     String mTargetPackageName;
 
-    int mMinSdkLevel;
     int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
 
-    boolean mProvideMainThread = false;
-
     final RavenwoodSystemProperties mSystemProperties = new RavenwoodSystemProperties();
 
     final List<Class<?>> mServicesRequired = new ArrayList<>();
@@ -108,20 +104,18 @@
         }
 
         /**
-         * Configure the identity of this process to be the system UID for the duration of the
-         * test. Has no effect on non-Ravenwood environments.
+         * @deprecated no longer used. We always use an app UID.
          */
+        @Deprecated
         public Builder setProcessSystem() {
-            mConfig.mUid = SYSTEM_UID;
             return this;
         }
 
         /**
-         * Configure the identity of this process to be an app UID for the duration of the
-         * test. Has no effect on non-Ravenwood environments.
+         * @deprecated no longer used. We always use an app UID.
          */
+        @Deprecated
         public Builder setProcessApp() {
-            mConfig.mUid = FIRST_APPLICATION_UID;
             return this;
         }
 
@@ -144,14 +138,6 @@
         }
 
         /**
-         * Configure the min SDK level of the test.
-         */
-        public Builder setMinSdkLevel(int sdkLevel) {
-            mConfig.mMinSdkLevel = sdkLevel;
-            return this;
-        }
-
-        /**
          * Configure the target SDK level of the test.
          */
         public Builder setTargetSdkLevel(int sdkLevel) {
@@ -160,14 +146,10 @@
         }
 
         /**
-         * Configure a "main" thread to be available for the duration of the test, as defined
-         * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
-         *
-         * @deprecated
+         * @deprecated no longer used. Main thread is always available.
          */
         @Deprecated
         public Builder setProvideMainThread(boolean provideMainThread) {
-            mConfig.mProvideMainThread = provideMainThread;
             return this;
         }
 
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index 3d6ac0f..bfa3802 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -112,20 +112,18 @@
         }
 
         /**
-         * Configure the identity of this process to be the system UID for the duration of the
-         * test. Has no effect on non-Ravenwood environments.
+         * @deprecated no longer used. We always use an app UID.
          */
+        @Deprecated
         public Builder setProcessSystem() {
-            mBuilder.setProcessSystem();
             return this;
         }
 
         /**
-         * Configure the identity of this process to be an app UID for the duration of the
-         * test. Has no effect on non-Ravenwood environments.
+         * @deprecated no longer used. We always use an app UID.
          */
+        @Deprecated
         public Builder setProcessApp() {
-            mBuilder.setProcessApp();
             return this;
         }
 
@@ -139,14 +137,10 @@
         }
 
         /**
-         * Configure a "main" thread to be available for the duration of the test, as defined
-         * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments.
-         *
-         * @deprecated
+         * @deprecated no longer used. Main thread is always available.
          */
         @Deprecated
         public Builder setProvideMainThread(boolean provideMainThread) {
-            mBuilder.setProvideMainThread(provideMainThread);
             return this;
         }
 
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
index 9bc45be..3e4619f 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java
@@ -16,21 +16,30 @@
 
 package android.platform.test.ravenwood;
 
-import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_SYSPROP;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath;
 
-import com.android.ravenwood.common.RavenwoodCommonUtils;
+import android.util.Log;
 
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
 public class RavenwoodSystemProperties {
     private static final String TAG = "RavenwoodSystemProperties";
 
+    /** We pull in propeties from this file. */
+    private static final String RAVENWOOD_BUILD_PROP = "ravenwood-data/ravenwood-build.prop";
+
+    /** This is the actual build.prop we use to build the device (contents depends on lunch). */
+    private static final String DEVICE_BUILD_PROP = "ravenwood-data/build.prop";
+
+    /** The default values. */
     private static final Map<String, String> sDefaultValues = new HashMap<>();
 
     private static final String[] PARTITIONS = {
@@ -43,52 +52,54 @@
             "vendor_dlkm",
     };
 
-    /**
-     * More info about property file loading: system/core/init/property_service.cpp
-     * In the following logic, the only partition we would need to consider is "system",
-     * since we only read from system-build.prop
-     */
-    static void initialize(String propFile) {
-        // Load all properties from build.prop
+    private static Map<String, String> readProperties(String propFile) {
+        // Use an ordered map just for cleaner dump log.
+        final Map<String, String> ret = new LinkedHashMap<>();
         try {
             Files.readAllLines(Path.of(propFile)).stream()
                     .map(String::trim)
                     .filter(s -> !s.startsWith("#"))
                     .map(s -> s.split("\\s*=\\s*", 2))
                     .filter(a -> a.length == 2)
-                    .forEach(a -> sDefaultValues.put(a[0], a[1]));
+                    .forEach(a -> ret.put(a[0], a[1]));
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
+        return ret;
+    }
 
-        // If ro.product.${name} is not set, derive from ro.product.${partition}.${name}
-        // If ro.product.cpu.abilist* is not set, derive from ro.${partition}.product.cpu.abilist*
-        for (var entry : Set.copyOf(sDefaultValues.entrySet())) {
-            final String key;
-            if (entry.getKey().startsWith("ro.product.system.")) {
-                var name = entry.getKey().substring(18);
-                key = "ro.product." + name;
+    /**
+     * Load default sysprops from {@link #RAVENWOOD_BUILD_PROP}. We also pull in
+     * certain properties from the acutual device's build.prop {@link #DEVICE_BUILD_PROP} too.
+     *
+     * More info about property file loading: system/core/init/property_service.cpp
+     * In the following logic, the only partition we would need to consider is "system",
+     * since we only read from system-build.prop
+     */
+    static void initialize() {
+        var path = getRavenwoodRuntimePath();
+        var ravenwoodProps = readProperties(path + RAVENWOOD_BUILD_PROP);
+        var deviceProps = readProperties(path + DEVICE_BUILD_PROP);
 
-            } else if (entry.getKey().startsWith("ro.system.product.cpu.abilist")) {
-                var name = entry.getKey().substring(22);
-                key = "ro.product.cpu." + name;
+        Log.i(TAG, "Default system properties:");
+        ravenwoodProps.forEach((key, origValue) -> {
+            final String value;
+
+            // If a value starts with "$$$", then this is a reference to the device-side value.
+            if (origValue.startsWith("$$$")) {
+                var deviceKey = origValue.substring(3);
+                var deviceValue = deviceProps.get(deviceKey);
+                if (deviceValue == null) {
+                    throw new RuntimeException("Failed to initialize system properties. Key '"
+                             + deviceKey + "' doesn't exist in the device side build.prop");
+                }
+                value = deviceValue;
             } else {
-                continue;
+                value = origValue;
             }
-            if (!sDefaultValues.containsKey(key)) {
-                sDefaultValues.put(key, entry.getValue());
-            }
-        }
-
-        // Some other custom values
-        sDefaultValues.put("ro.board.first_api_level", "1");
-        sDefaultValues.put("ro.product.first_api_level", "1");
-        sDefaultValues.put("ro.soc.manufacturer", "Android");
-        sDefaultValues.put("ro.soc.model", "Ravenwood");
-        sDefaultValues.put(RAVENWOOD_SYSPROP, "1");
-
-        // Log all values
-        sDefaultValues.forEach((key, value) -> RavenwoodCommonUtils.log(TAG, key + "=" + value));
+            Log.i(TAG, key + "=" + value);
+            sDefaultValues.put(key, value);
+        });
 
         // Copy ro.product.* and ro.build.* to all partitions, just in case
         // We don't want to log these because these are just a lot of duplicate values
@@ -104,6 +115,13 @@
                 }
             }
         }
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            // Dump all properties for local debugging.
+            Log.v(TAG, "All system properties:");
+            for (var key : sDefaultValues.keySet().stream().sorted().toList()) {
+                Log.v(TAG, "" + key + "=" + sDefaultValues.get(key));
+            }
+        }
     }
 
     private volatile boolean mIsImmutable;
diff --git a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
index d232ef2..c85bd23 100644
--- a/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
+++ b/ravenwood/runtime-helper-src/framework/android/util/Log_host.java
@@ -18,6 +18,7 @@
 import android.util.Log.Level;
 
 import com.android.internal.os.RuntimeInit;
+import com.android.ravenwood.common.RavenwoodCommonUtils;
 
 import java.io.PrintStream;
 
@@ -35,6 +36,9 @@
     }
 
     public static int println_native(int bufID, int priority, String tag, String msg) {
+        if (priority < Log.INFO && !RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING) {
+            return msg.length(); // No verbose logging.
+        }
         final String buffer;
         switch (bufID) {
             case Log.LOG_ID_MAIN: buffer = "main"; break;
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index 1910100..fe2269a 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -33,7 +33,7 @@
 exclude_re=""
 smoke_exclude_re=""
 dry_run=""
-while getopts "sx:f:d" opt; do
+while getopts "sx:f:dt" opt; do
 case "$opt" in
     s)
         # Remove slow tests.
@@ -51,6 +51,9 @@
         # Dry run
         dry_run="echo"
         ;;
+    t)
+        export RAVENWOOD_LOG_OUT=$(tty)
+        ;;
     '?')
         exit 1
         ;;
diff --git a/ravenwood/texts/build.prop-sample-cuttlefish b/ravenwood/texts/build.prop-sample-cuttlefish
new file mode 100644
index 0000000..f78b727
--- /dev/null
+++ b/ravenwood/texts/build.prop-sample-cuttlefish
@@ -0,0 +1,132 @@
+# This is file is generated with `aosp_cf_x86_64_phone-trunk_staging-eng` on 2024-11-06.
+# We have this file here only as a reference. We don't actually use this file anywhere.
+
+####################################
+# from generate_common_build_props
+# These properties identify this partition image.
+####################################
+ro.product.system.brand=Android
+ro.product.system.device=generic
+ro.product.system.manufacturer=Android
+ro.product.system.model=mainline
+ro.product.system.name=mainline
+ro.system.product.cpu.abilist=x86_64,x86,arm64-v8a,armeabi-v7a,armeabi
+ro.system.product.cpu.abilist32=x86,armeabi-v7a,armeabi
+ro.system.product.cpu.abilist64=x86_64,arm64-v8a
+ro.system.build.date=Tue Nov  5 13:25:43 PST 2024
+ro.system.build.date.utc=1730841943
+ro.system.build.fingerprint=generic/aosp_cf_x86_64_phone/vsoc_x86_64:Baklava/MAIN/eng.omakot:eng/test-keys
+ro.system.build.id=MAIN
+ro.system.build.tags=test-keys
+ro.system.build.type=eng
+ro.system.build.version.incremental=eng.omakot
+ro.system.build.version.release=15
+ro.system.build.version.release_or_codename=Baklava
+ro.system.build.version.sdk=35
+####################################
+# from gen_build_prop.py:generate_build_info
+####################################
+# begin build properties
+ro.build.legacy.id=MAIN
+ro.build.display.id=aosp_cf_x86_64_phone-eng Baklava MAIN eng.omakot test-keys
+ro.build.version.incremental=eng.omakot
+ro.build.version.sdk=35
+ro.build.version.preview_sdk=1
+ro.build.version.preview_sdk_fingerprint=2ef06129940d459014cf4dede3950d71
+ro.build.version.codename=Baklava
+ro.build.version.all_codenames=Baklava
+ro.build.version.known_codenames=Base,Base11,Cupcake,Donut,Eclair,Eclair01,EclairMr1,Froyo,Gingerbread,GingerbreadMr1,Honeycomb,HoneycombMr1,HoneycombMr2,IceCreamSandwich,IceCreamSandwichMr1,JellyBean,JellyBeanMr1,JellyBeanMr2,Kitkat,KitkatWatch,Lollipop,LollipopMr1,M,N,NMr1,O,OMr1,P,Q,R,S,Sv2,Tiramisu,UpsideDownCake,VanillaIceCream,Baklava
+ro.build.version.release=15
+ro.build.version.release_or_codename=Baklava
+ro.build.version.release_or_preview_display=Baklava
+ro.build.version.security_patch=2024-08-05
+ro.build.version.base_os=
+ro.build.version.min_supported_target_sdk=28
+ro.build.date=Tue Nov  5 13:25:43 PST 2024
+ro.build.date.utc=1730841943
+ro.build.type=eng
+ro.build.user=omakoto
+ro.build.host=omakoto-ct1.c.googlers.com
+ro.build.tags=test-keys
+ro.build.flavor=aosp_cf_x86_64_phone-eng
+# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,
+# use ro.product.cpu.abilist instead.
+ro.product.cpu.abi=x86_64
+ro.product.locale=en-US
+ro.wifi.channels=
+# ro.build.product is obsolete; use ro.product.device
+ro.build.product=vsoc_x86_64
+# Do not try to parse description or thumbprint
+ro.build.description=aosp_cf_x86_64_phone-eng Baklava MAIN eng.omakot test-keys
+# end build properties
+####################################
+# from variable ADDITIONAL_SYSTEM_PROPERTIES
+####################################
+ro.treble.enabled=true
+ro.llndk.api_level=202504
+ro.actionable_compatible_property.enabled=true
+persist.debug.dalvik.vm.core_platform_api_policy=just-warn
+ro.postinstall.fstab.prefix=/system
+ro.kernel.android.checkjni=1
+ro.secure=0
+ro.allow.mock.location=1
+dalvik.vm.lockprof.threshold=500
+ro.debuggable=1
+dalvik.vm.image-dex2oat-filter=extract
+init.svc_debug.no_fatal.zygote=true
+net.bt.name=Android
+ro.force.debuggable=0
+####################################
+# from variable PRODUCT_SYSTEM_PROPERTIES
+####################################
+debug.atrace.tags.enableflags=0
+persist.traced.enable=1
+dalvik.vm.image-dex2oat-Xms=64m
+dalvik.vm.image-dex2oat-Xmx=64m
+dalvik.vm.dex2oat-Xms=64m
+dalvik.vm.dex2oat-Xmx=512m
+dalvik.vm.usejit=true
+dalvik.vm.dexopt.secondary=true
+dalvik.vm.dexopt.thermal-cutoff=2
+dalvik.vm.appimageformat=lz4
+ro.dalvik.vm.native.bridge=0
+pm.dexopt.post-boot=verify
+pm.dexopt.first-boot=verify
+pm.dexopt.boot-after-ota=verify
+pm.dexopt.boot-after-mainline-update=verify
+pm.dexopt.install=speed-profile
+pm.dexopt.install-fast=skip
+pm.dexopt.install-bulk=speed-profile
+pm.dexopt.install-bulk-secondary=verify
+pm.dexopt.install-bulk-downgraded=verify
+pm.dexopt.install-bulk-secondary-downgraded=verify
+pm.dexopt.bg-dexopt=speed-profile
+pm.dexopt.ab-ota=speed-profile
+pm.dexopt.inactive=verify
+pm.dexopt.cmdline=verify
+pm.dexopt.shared=speed
+dalvik.vm.disable-art-service-dexopt=true
+dalvik.vm.disable-odrefresh=true
+dalvik.vm.dex2oat-resolve-startup-strings=true
+dalvik.vm.dex2oat-max-image-block-size=524288
+dalvik.vm.minidebuginfo=true
+dalvik.vm.dex2oat-minidebuginfo=true
+dalvik.vm.madvise.vdexfile.size=104857600
+dalvik.vm.madvise.odexfile.size=104857600
+dalvik.vm.madvise.artfile.size=4294967295
+dalvik.vm.usap_pool_enabled=false
+dalvik.vm.usap_refill_threshold=1
+dalvik.vm.usap_pool_size_max=3
+dalvik.vm.usap_pool_size_min=1
+dalvik.vm.usap_pool_refill_delay_ms=3000
+dalvik.vm.useartservice=true
+dalvik.vm.enable_pr_dexopt=true
+ro.cp_system_other_odex=1
+ro.apex.updatable=true
+ro.launcher.depth.widget=0
+####################################
+# from variable PRODUCT_SYSTEM_DEFAULT_PROPERTIES
+####################################
+# Auto-added by post_process_props.py
+persist.sys.usb.config=adb
+# end of file
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 82be2c0..c196a09 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -15,7 +15,9 @@
 com.android.internal.os.BatteryStatsHistoryIterator
 com.android.internal.os.Clock
 com.android.internal.os.LongArrayMultiStateCounter
+com.android.internal.os.LongArrayMultiStateCounter_ravenwood
 com.android.internal.os.LongMultiStateCounter
+com.android.internal.os.LongMultiStateCounter_ravenwood
 com.android.internal.os.MonotonicClock
 com.android.internal.os.PowerProfile
 com.android.internal.os.PowerStats
@@ -143,6 +145,7 @@
 android.os.Looper
 android.os.Message
 android.os.MessageQueue
+android.os.MessageQueue_ravenwood
 android.os.PackageTagsList
 android.os.Parcel
 android.os.ParcelFileDescriptor
@@ -235,6 +238,7 @@
 android.database.CursorIndexOutOfBoundsException
 android.database.CursorJoiner
 android.database.CursorWindow
+android.database.CursorWindow_ravenwood
 android.database.CursorWrapper
 android.database.DataSetObservable
 android.database.DataSetObserver
@@ -370,4 +374,3 @@
 com.android.server.compat.*
 com.android.internal.compat.*
 android.app.AppCompatCallbacks
-
diff --git a/ravenwood/texts/ravenwood-build.prop b/ravenwood/texts/ravenwood-build.prop
new file mode 100644
index 0000000..93a18cf
--- /dev/null
+++ b/ravenwood/texts/ravenwood-build.prop
@@ -0,0 +1,44 @@
+# This file contains system properties used on ravenwood.
+
+ro.is_on_ravenwood=1
+
+ro.board.first_api_level=1
+ro.product.first_api_level=1
+ro.soc.manufacturer=Android
+ro.soc.model=Ravenwood
+ro.debuggable=1
+
+# The ones starting with "ro.product" or "ro.bild" will be copied to all "partitions" too.
+# See RavenwoodSystemProperties.
+ro.product.brand=Android
+ro.product.device=Ravenwood
+ro.product.manufacturer=Android
+ro.product.model=Ravenwood
+ro.product.name=Ravenwood
+ro.product.cpu.abilist=x86_64
+ro.product.cpu.abilist32=
+ro.product.cpu.abilist64=x86_64
+
+ro.build.date=Thu Jan 01 00:00:00 GMT 2024
+ro.build.date.utc=1704092400
+ro.build.id=MAIN
+ro.build.tags=dev-keys
+ro.build.type=userdebug
+ro.build.version.incremental=userdebug.ravenwood.20240101
+
+# These are what we used to use on Ravenwood, copied here as a reference.
+#ro.build.version.codename=REL
+#ro.build.version.all_codenames=REL
+#ro.build.version.known_codenames=REL
+#ro.build.version.release=14
+#ro.build.version.release_or_codename=VanillaIceCream
+#ro.build.version.sdk=34
+
+# We pull in the following values from the real build.prop file.
+ro.build.version.codename=$$$ro.build.version.codename
+ro.build.version.all_codenames=$$$ro.build.version.codename
+ro.build.version.known_codenames=$$$ro.build.version.codename
+ro.build.version.release=$$$ro.build.version.release
+ro.build.version.release_or_codename=$$$ro.build.version.release_or_codename
+ro.build.version.release_or_preview_display=$$$ro.build.version.release_or_preview_display
+ro.build.version.sdk=$$$ro.build.version.sdk
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 974cba2..86d3ee6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -5071,6 +5071,11 @@
     @EnforcePermission(MANAGE_ACCESSIBILITY)
     public boolean isAccessibilityServiceWarningRequired(AccessibilityServiceInfo info) {
         isAccessibilityServiceWarningRequired_enforcePermission();
+        if (info == null) {
+            Log.e(LOG_TAG, "Called isAccessibilityServiceWarningRequired with null service info");
+            return true;
+        }
+
         final ComponentName componentName = info.getComponentName();
 
         // Warning is not required if the service is already enabled.
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 268e564..f13e229 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -29,7 +29,7 @@
 import android.app.appfunctions.AppFunctionRuntimeMetadata;
 import android.app.appfunctions.AppFunctionStaticMetadataHelper;
 import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
-import android.app.appfunctions.ExecuteAppFunctionResponse;
+import android.app.appfunctions.AppFunctionException;
 import android.app.appfunctions.IAppFunctionEnabledCallback;
 import android.app.appfunctions.IAppFunctionManager;
 import android.app.appfunctions.IAppFunctionService;
@@ -156,11 +156,10 @@
             mCallerValidator.verifyTargetUserHandle(
                     requestInternal.getUserHandle(), validatedCallingPackage);
         } catch (SecurityException exception) {
-            safeExecuteAppFunctionCallback.onResult(
-                    ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_DENIED,
-                            exception.getMessage(),
-                            /* extras= */ null));
+            safeExecuteAppFunctionCallback.onError(
+                    new AppFunctionException(
+                            AppFunctionException.ERROR_DENIED,
+                            exception.getMessage()));
             return null;
         }
 
@@ -180,7 +179,7 @@
                                 safeExecuteAppFunctionCallback,
                                 executeAppFunctionCallback.asBinder());
                     } catch (Exception e) {
-                        safeExecuteAppFunctionCallback.onResult(
+                        safeExecuteAppFunctionCallback.onError(
                                 mapExceptionToExecuteAppFunctionResponse(e));
                     }
                 });
@@ -198,22 +197,19 @@
         UserHandle targetUser = requestInternal.getUserHandle();
         // TODO(b/354956319): Add and honor the new enterprise policies.
         if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
-            safeExecuteAppFunctionCallback.onResult(
-                    ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
+            safeExecuteAppFunctionCallback.onError(
+                    new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
                             "Cannot run on a device with a device owner or from the managed"
-                                    + " profile.",
-                            /* extras= */ null));
+                                    + " profile."));
             return;
         }
 
         String targetPackageName = requestInternal.getClientRequest().getTargetPackageName();
         if (TextUtils.isEmpty(targetPackageName)) {
-            safeExecuteAppFunctionCallback.onResult(
-                    ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT,
-                            "Target package name cannot be empty.",
-                            /* extras= */ null));
+            safeExecuteAppFunctionCallback.onError(
+                    new AppFunctionException(
+                            AppFunctionException.ERROR_INVALID_ARGUMENT,
+                            "Target package name cannot be empty."));
             return;
         }
 
@@ -253,11 +249,10 @@
                                     mInternalServiceHelper.resolveAppFunctionService(
                                             targetPackageName, targetUser);
                             if (serviceIntent == null) {
-                                safeExecuteAppFunctionCallback.onResult(
-                                        ExecuteAppFunctionResponse.newFailure(
-                                                ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
-                                                "Cannot find the target service.",
-                                                /* extras= */ null));
+                                safeExecuteAppFunctionCallback.onError(
+                                        new AppFunctionException(
+                                                AppFunctionException.ERROR_SYSTEM_ERROR,
+                                                "Cannot find the target service."));
                                 return;
                             }
                             bindAppFunctionServiceUnchecked(
@@ -272,7 +267,7 @@
                         })
                 .exceptionally(
                         ex -> {
-                            safeExecuteAppFunctionCallback.onResult(
+                            safeExecuteAppFunctionCallback.onError(
                                     mapExceptionToExecuteAppFunctionResponse(ex));
                             return null;
                         });
@@ -446,11 +441,9 @@
 
         if (!bindServiceResult) {
             Slog.e(TAG, "Failed to bind to the AppFunctionService");
-            safeExecuteAppFunctionCallback.onResult(
-                    ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
-                            "Failed to bind the AppFunctionService.",
-                            /* extras= */ null));
+            safeExecuteAppFunctionCallback.onError(
+                    new AppFunctionException(AppFunctionException.ERROR_SYSTEM_ERROR,
+                            "Failed to bind the AppFunctionService."));
         }
     }
 
@@ -459,22 +452,21 @@
                 .getSystemService(AppSearchManager.class);
     }
 
-    private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) {
+    private AppFunctionException mapExceptionToExecuteAppFunctionResponse(Throwable e) {
         if (e instanceof CompletionException) {
             e = e.getCause();
         }
-        int resultCode = ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
+        int resultCode = AppFunctionException.ERROR_SYSTEM_ERROR;
         if (e instanceof AppSearchException appSearchException) {
             resultCode =
                     mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
                             appSearchException.getResultCode());
         } else if (e instanceof SecurityException) {
-            resultCode = ExecuteAppFunctionResponse.RESULT_DENIED;
+            resultCode = AppFunctionException.ERROR_DENIED;
         } else if (e instanceof DisabledAppFunctionException) {
-            resultCode = ExecuteAppFunctionResponse.RESULT_DISABLED;
+            resultCode = AppFunctionException.ERROR_DISABLED;
         }
-        return ExecuteAppFunctionResponse.newFailure(
-                resultCode, e.getMessage(), /* extras= */ null);
+        return new AppFunctionException(resultCode, e.getMessage());
     }
 
     private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) {
@@ -485,13 +477,13 @@
 
         switch (resultCode) {
             case AppSearchResult.RESULT_NOT_FOUND:
-                return ExecuteAppFunctionResponse.RESULT_FUNCTION_NOT_FOUND;
+                return AppFunctionException.ERROR_FUNCTION_NOT_FOUND;
             case AppSearchResult.RESULT_INVALID_ARGUMENT:
             case AppSearchResult.RESULT_INTERNAL_ERROR:
             case AppSearchResult.RESULT_SECURITY_ERROR:
                 // fall-through
         }
-        return ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
+        return AppFunctionException.ERROR_SYSTEM_ERROR;
     }
 
     private void registerAppSearchObserver(@NonNull TargetUser user) {
diff --git a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
index 129be65..c689bb9 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/RunAppFunctionServiceCallback.java
@@ -17,6 +17,7 @@
 
 import android.annotation.NonNull;
 import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+import android.app.appfunctions.AppFunctionException;
 import android.app.appfunctions.ExecuteAppFunctionResponse;
 import android.app.appfunctions.IAppFunctionService;
 import android.app.appfunctions.ICancellationCallback;
@@ -57,17 +58,22 @@
                     mCancellationCallback,
                     new IExecuteAppFunctionCallback.Stub() {
                         @Override
-                        public void onResult(ExecuteAppFunctionResponse response) {
+                        public void onSuccess(ExecuteAppFunctionResponse response) {
                             mSafeExecuteAppFunctionCallback.onResult(response);
                             serviceUsageCompleteListener.onCompleted();
                         }
+
+                        @Override
+                        public void onError(AppFunctionException error) {
+                            mSafeExecuteAppFunctionCallback.onError(error);
+                            serviceUsageCompleteListener.onCompleted();
+                        }
                     });
         } catch (Exception e) {
-            mSafeExecuteAppFunctionCallback.onResult(
-                    ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
-                            e.getMessage(),
-                            /* extras= */ null));
+            mSafeExecuteAppFunctionCallback.onError(
+                    new AppFunctionException(
+                            AppFunctionException.ERROR_APP_UNKNOWN_ERROR,
+                            e.getMessage()));
             serviceUsageCompleteListener.onCompleted();
         }
     }
@@ -75,11 +81,9 @@
     @Override
     public void onFailedToConnect() {
         Slog.e(TAG, "Failed to connect to service");
-        mSafeExecuteAppFunctionCallback.onResult(
-                ExecuteAppFunctionResponse.newFailure(
-                        ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR,
-                        "Failed to connect to AppFunctionService",
-                        /* extras= */ null));
+        mSafeExecuteAppFunctionCallback.onError(
+                new AppFunctionException(AppFunctionException.ERROR_APP_UNKNOWN_ERROR,
+                        "Failed to connect to AppFunctionService"));
     }
 
     @Override
diff --git a/services/art-profile b/services/art-profile
index 6fa4c88..ce1e2c6 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -5657,7 +5657,7 @@
 Lcom/android/server/utils/Watcher;
 Lcom/android/server/vibrator/VibratorController$NativeWrapper;
 Lcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener;
-Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;
+Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;
 Lcom/android/server/vibrator/VibratorManagerService;
 Lcom/android/server/vr/EnabledComponentsObserver$EnabledComponentChangeListener;
 Lcom/android/server/vr/VrManagerService;
diff --git a/services/art-wear-profile b/services/art-wear-profile
index 47bdb13..1e3090f 100644
--- a/services/art-wear-profile
+++ b/services/art-wear-profile
@@ -1330,7 +1330,7 @@
 Lcom/android/server/utils/Watcher;
 Lcom/android/server/vibrator/VibratorController$NativeWrapper;
 Lcom/android/server/vibrator/VibratorController$OnVibrationCompleteListener;
-Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;
+Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;
 Lcom/android/server/vibrator/VibratorManagerService;
 Lcom/android/server/vr/EnabledComponentsObserver$EnabledComponentChangeListener;
 Lcom/android/server/vr/VrManagerService;
@@ -24948,7 +24948,7 @@
 PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->cancelSynced()V
 PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->getCapabilities()J
 PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->getVibratorIds()[I
-PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->init(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)V
+PLcom/android/server/vibrator/VibratorManagerService$NativeWrapper;->init(Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;)V
 PLcom/android/server/vibrator/VibratorManagerService$VibrationCompleteListener;-><init>(Lcom/android/server/vibrator/VibratorManagerService;)V
 PLcom/android/server/vibrator/VibratorManagerService$VibrationCompleteListener;->onComplete(IJ)V
 PLcom/android/server/vibrator/VibratorManagerService$VibrationRecords;-><init>(II)V
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 3ccad160..7f482ac 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -158,6 +158,7 @@
         "android.hardware.gnss-V2-java",
         "android.hardware.vibrator-V3-java",
         "app-compat-annotations",
+        "art_exported_aconfig_flags_lib",
         "framework-tethering.stubs.module_lib",
         "keepanno-annotations",
         "service-art.stubs.system_server",
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 78bc658..3dcca14 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -22,6 +22,9 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import static com.android.server.health.Utils.copyV1Battery;
 
+import static java.lang.Math.abs;
+
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
@@ -48,6 +51,7 @@
 import android.os.Handler;
 import android.os.IBatteryPropertiesRegistrar;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.OsProtoEnums;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -67,6 +71,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.SomeArgs;
@@ -84,6 +89,7 @@
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
@@ -149,19 +155,112 @@
     private HealthInfo mHealthInfo;
     private final HealthInfo mLastHealthInfo = new HealthInfo();
     private boolean mBatteryLevelCritical;
-    private int mLastBatteryStatus;
-    private int mLastBatteryHealth;
-    private boolean mLastBatteryPresent;
-    private int mLastBatteryLevel;
-    private int mLastBatteryVoltage;
-    private int mLastBatteryTemperature;
-    private boolean mLastBatteryLevelCritical;
-    private int mLastMaxChargingCurrent;
-    private int mLastMaxChargingVoltage;
-    private int mLastChargeCounter;
-    private int mLastBatteryCycleCount;
-    private int mLastChargingState;
-    private int mLastBatteryCapacityLevel;
+
+    /**
+     * {@link HealthInfo#batteryStatus} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryStatus;
+    /**
+     * {@link HealthInfo#batteryHealth} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryHealth;
+    /**
+     * {@link HealthInfo#batteryPresent} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private boolean mLastBroadcastBatteryPresent;
+    /**
+     * {@link HealthInfo#batteryLevel} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryLevel;
+    /**
+     * {@link HealthInfo#batteryVoltageMillivolts} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryVoltage;
+    /**
+     * {@link HealthInfo#batteryTemperatureTenthsCelsius} value when
+     * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryTemperature;
+    /**
+     * {@link #mBatteryLevelCritical} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: These values may be used for internal operations and/or to determine whether to trigger
+     * the broadcast or not.
+     */
+    private boolean mLastBroadcastBatteryLevelCritical;
+    /**
+     * {@link HealthInfo#maxChargingCurrentMicroamps} value when
+     * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastMaxChargingCurrent;
+    /**
+     * {@link HealthInfo#maxChargingVoltageMicrovolts} value when
+     * {@link Intent#ACTION_BATTERY_CHANGED} broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastMaxChargingVoltage;
+    /**
+     * {@link HealthInfo#batteryChargeCounterUah} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastChargeCounter;
+    /**
+     * {@link HealthInfo#batteryCycleCount} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryCycleCount;
+    /**
+     * {@link HealthInfo#chargingState} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastChargingState;
+    /**
+     * {@link HealthInfo#batteryCapacityLevel} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: This value may be used for internal operations and/or to determine whether to trigger
+     * the {@link Intent#ACTION_BATTERY_CHANGED} broadcast or not.
+     */
+    private int mLastBroadcastBatteryCapacityLevel;
+    /**
+     * {@link #mPlugType} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: These values may be used for internal operations and/or to determine whether to trigger
+     * the broadcast or not.
+     */
+    private int mLastBroadcastPlugType = -1; // Extra state so we can detect first run
+    /**
+     * {@link #mInvalidCharger} value when {@link Intent#ACTION_BATTERY_CHANGED}
+     * broadcast was sent last.
+     * Note: These values may be used for internal operations and/or to determine whether to trigger
+     * the broadcast or not.
+     */
+    private int mLastBroadcastInvalidCharger;
     /**
      * The last seen charging policy. This requires the
      * {@link android.Manifest.permission#BATTERY_STATS} permission and should therefore not be
@@ -172,7 +271,6 @@
     private int mSequence = 1;
 
     private int mInvalidCharger;
-    private int mLastInvalidCharger;
 
     private int mLowBatteryWarningLevel;
     private int mLastLowBatteryWarningLevel;
@@ -184,7 +282,6 @@
     private static String sSystemUiPackage;
 
     private int mPlugType;
-    private int mLastPlugType = -1; // Extra state so we can detect first run
 
     private boolean mBatteryLevelLow;
 
@@ -197,6 +294,16 @@
     private boolean mUpdatesStopped;
     private boolean mBatteryInputSuspended;
 
+    /**
+     * Time when the voltage was updated last by HAL and we sent the
+     * {@link Intent#ACTION_BATTERY_CHANGED} broadcast.
+     * Note: This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast
+     * so it is possible that voltage was updated but we did not send the broadcast so in that
+     * case we do not update the time.
+     */
+    @VisibleForTesting
+    public long mLastBroadcastVoltageUpdateTime;
+
     private Led mLed;
 
     private boolean mSentLowBatteryBroadcast = false;
@@ -211,7 +318,8 @@
     private final CopyOnWriteArraySet<BatteryManagerInternal.ChargingPolicyChangeListener>
             mChargingPolicyChangeListeners = new CopyOnWriteArraySet<>();
 
-    private static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic()
+    @VisibleForTesting
+    public static final Bundle BATTERY_CHANGED_OPTIONS = BroadcastOptions.makeBasic()
             .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
             .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
             .toBundle();
@@ -234,6 +342,25 @@
     private static final int MSG_BROADCAST_POWER_CONNECTION_CHANGED = 2;
     private static final int MSG_BROADCAST_BATTERY_LOW_OKAY = 3;
 
+    /**
+     * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+     * only send the broadcast and update the temperature value when the temp change is greater or
+     * equals to 1 degree celsius.
+     */
+    private static final int ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE = 10;
+    /**
+     * This value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+     * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+     * fluctuation of at least 1%.
+     */
+    private static final int TIME_DIFF_FOR_VOLTAGE_UPDATE_MS = 20000;
+    /**
+     * The value is used to rate limit the {@link Intent#ACTION_BATTERY_CHANGED} broadcast. We
+     * only send the broadcast if the last voltage was updated at least 20s seconds back and has a
+     * fluctuation of at least 1%.
+     */
+    private static final float BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE = 0.01f;
+
     private final Handler.Callback mLocalCallback = msg -> {
         switch (msg.what) {
             case MSG_BROADCAST_BATTERY_CHANGED: {
@@ -283,10 +410,19 @@
     };
 
     public BatteryService(Context context) {
+        this(context, Objects.requireNonNull(Looper.myLooper(),
+                "BatteryService uses handler!! Can't create handler inside thread that has not "
+                        + "called Looper.prepare()"));
+    }
+
+    @VisibleForTesting
+    public BatteryService(Context context, @NonNull Looper looper) {
         super(context);
 
+        Objects.requireNonNull(looper);
+
         mContext = context;
-        mHandler = new Handler(mLocalCallback, true /*async*/);
+        mHandler = new Handler(looper, mLocalCallback, true /*async*/);
         mLed = new Led(context, getLocalService(LightsManager.class));
         mBatteryStats = BatteryStatsService.getService();
         mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -436,7 +572,7 @@
 
     private boolean shouldSendBatteryLowLocked() {
         final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
-        final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
+        final boolean oldPlugged = mLastBroadcastPlugType != BATTERY_PLUGGED_NONE;
 
         /* The ACTION_BATTERY_LOW broadcast is sent in these situations:
          * - is just un-plugged (previously was plugged) and battery level is
@@ -447,7 +583,7 @@
         return !plugged
                 && mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
                 && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel
-                && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel
+                && (oldPlugged || mLastBroadcastBatteryLevel > mLowBatteryWarningLevel
                     || mHealthInfo.batteryLevel > mLastLowBatteryWarningLevel);
     }
 
@@ -515,7 +651,13 @@
         }
     }
 
-    private void update(android.hardware.health.HealthInfo info) {
+    /**
+     * Updates the healthInfo and triggers the broadcast.
+     *
+     * @param info the new health info
+     */
+    @VisibleForTesting
+    public void update(android.hardware.health.HealthInfo info) {
         traceBegin("HealthInfoUpdate");
 
         Trace.traceCounter(
@@ -556,8 +698,8 @@
         long dischargeDuration = 0;
 
         mBatteryLevelCritical =
-            mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
-            && mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
+                mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+                        && mHealthInfo.batteryLevel <= mCriticalBatteryLevel;
         mPlugType = plugType(mHealthInfo);
 
         if (DEBUG) {
@@ -591,24 +733,28 @@
             mHandler.post(this::notifyChargingPolicyChanged);
         }
 
-        if (force
-                || (mHealthInfo.batteryStatus != mLastBatteryStatus
-                        || mHealthInfo.batteryHealth != mLastBatteryHealth
-                        || mHealthInfo.batteryPresent != mLastBatteryPresent
-                        || mHealthInfo.batteryLevel != mLastBatteryLevel
-                        || mPlugType != mLastPlugType
-                        || mHealthInfo.batteryVoltageMillivolts != mLastBatteryVoltage
-                        || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBatteryTemperature
-                        || mHealthInfo.maxChargingCurrentMicroamps != mLastMaxChargingCurrent
-                        || mHealthInfo.maxChargingVoltageMicrovolts != mLastMaxChargingVoltage
-                        || mHealthInfo.batteryChargeCounterUah != mLastChargeCounter
-                        || mInvalidCharger != mLastInvalidCharger
-                        || mHealthInfo.batteryCycleCount != mLastBatteryCycleCount
-                        || mHealthInfo.chargingState != mLastChargingState
-                        || mHealthInfo.batteryCapacityLevel != mLastBatteryCapacityLevel)) {
+        final boolean includeChargeCounter =
+                !com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()
+                        && mHealthInfo.batteryChargeCounterUah != mLastBroadcastChargeCounter;
 
-            if (mPlugType != mLastPlugType) {
-                if (mLastPlugType == BATTERY_PLUGGED_NONE) {
+        if (force
+                || (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+                || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+                || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+                || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
+                || mPlugType != mLastBroadcastPlugType
+                || mHealthInfo.batteryVoltageMillivolts != mLastBroadcastBatteryVoltage
+                || mHealthInfo.batteryTemperatureTenthsCelsius != mLastBroadcastBatteryTemperature
+                || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
+                || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
+                || includeChargeCounter
+                || mInvalidCharger != mLastBroadcastInvalidCharger
+                || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
+                || mHealthInfo.chargingState != mLastBroadcastChargingState
+                || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel)) {
+
+            if (mPlugType != mLastBroadcastPlugType) {
+                if (mLastBroadcastPlugType == BATTERY_PLUGGED_NONE) {
                     // discharging -> charging
                     mChargeStartLevel = mHealthInfo.batteryLevel;
                     mChargeStartTime = SystemClock.elapsedRealtime();
@@ -622,7 +768,8 @@
 
                     // There's no value in this data unless we've discharged at least once and the
                     // battery level has changed; so don't log until it does.
-                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mHealthInfo.batteryLevel) {
+                    if (mDischargeStartTime != 0
+                            && mDischargeStartLevel != mHealthInfo.batteryLevel) {
                         dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
                         logOutlier = true;
                         EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
@@ -639,7 +786,7 @@
                     if (mChargeStartTime != 0 && chargeDuration != 0) {
                         final LogMaker builder = new LogMaker(MetricsEvent.ACTION_CHARGE);
                         builder.setType(MetricsEvent.TYPE_DISMISS);
-                        builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastPlugType);
+                        builder.addTaggedData(MetricsEvent.FIELD_PLUG_TYPE, mLastBroadcastPlugType);
                         builder.addTaggedData(MetricsEvent.FIELD_CHARGING_DURATION_MILLIS,
                                 chargeDuration);
                         builder.addTaggedData(MetricsEvent.FIELD_BATTERY_LEVEL_START,
@@ -651,19 +798,20 @@
                     mChargeStartTime = 0;
                 }
             }
-            if (mHealthInfo.batteryStatus != mLastBatteryStatus ||
-                    mHealthInfo.batteryHealth != mLastBatteryHealth ||
-                    mHealthInfo.batteryPresent != mLastBatteryPresent ||
-                    mPlugType != mLastPlugType) {
+            if (mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+                    || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+                    || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+                    || mPlugType != mLastBroadcastPlugType) {
                 EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
-                        mHealthInfo.batteryStatus, mHealthInfo.batteryHealth, mHealthInfo.batteryPresent ? 1 : 0,
+                        mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
+                        mHealthInfo.batteryPresent ? 1 : 0,
                         mPlugType, mHealthInfo.batteryTechnology);
                 SystemProperties.set(
                         "debug.tracing.battery_status",
                         Integer.toString(mHealthInfo.batteryStatus));
                 SystemProperties.set("debug.tracing.plug_type", Integer.toString(mPlugType));
             }
-            if (mHealthInfo.batteryLevel != mLastBatteryLevel) {
+            if (mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel) {
                 // Don't do this just from voltage or temperature changes, that is
                 // too noisy.
                 EventLog.writeEvent(
@@ -672,8 +820,8 @@
                         mHealthInfo.batteryVoltageMillivolts,
                         mHealthInfo.batteryTemperatureTenthsCelsius);
             }
-            if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
-                    mPlugType == BATTERY_PLUGGED_NONE) {
+            if (mBatteryLevelCritical && !mLastBroadcastBatteryLevelCritical
+                    && mPlugType == BATTERY_PLUGGED_NONE) {
                 // We want to make sure we log discharge cycle outliers
                 // if the battery is about to die.
                 dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
@@ -684,7 +832,7 @@
                 // Should we now switch in to low battery mode?
                 if (mPlugType == BATTERY_PLUGGED_NONE
                         && mHealthInfo.batteryStatus !=
-                           BatteryManager.BATTERY_STATUS_UNKNOWN
+                        BatteryManager.BATTERY_STATUS_UNKNOWN
                         && mHealthInfo.batteryLevel <= mLowBatteryWarningLevel) {
                     mBatteryLevelLow = true;
                 }
@@ -692,7 +840,7 @@
                 // Should we now switch out of low battery mode?
                 if (mPlugType != BATTERY_PLUGGED_NONE) {
                     mBatteryLevelLow = false;
-                } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel)  {
+                } else if (mHealthInfo.batteryLevel >= mLowBatteryCloseWarningLevel) {
                     mBatteryLevelLow = false;
                 } else if (force && mHealthInfo.batteryLevel >= mLowBatteryWarningLevel) {
                     // If being forced, the previous state doesn't matter, we will just
@@ -706,7 +854,7 @@
             // Separate broadcast is sent for power connected / not connected
             // since the standard intent will not wake any applications and some
             // applications may want to have smart behavior based on this.
-            if (mPlugType != 0 && mLastPlugType == 0) {
+            if (mPlugType != 0 && mLastBroadcastPlugType == 0) {
                 final Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
@@ -726,8 +874,7 @@
                         }
                     });
                 }
-            }
-            else if (mPlugType == 0 && mLastPlugType != 0) {
+            } else if (mPlugType == 0 && mLastBroadcastPlugType != 0) {
                 final Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
                 statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                 statusIntent.putExtra(BatteryManager.EXTRA_SEQUENCE, mSequence);
@@ -797,8 +944,14 @@
             // We are doing this after sending the above broadcasts, so anything processing
             // them will get the new sequence number at that point.  (See for example how testing
             // of JobScheduler's BatteryController works.)
-            sendBatteryChangedIntentLocked(force);
-            if (mLastBatteryLevel != mHealthInfo.batteryLevel || mLastPlugType != mPlugType) {
+
+            boolean rateLimitBatteryChangedBroadcast = rateLimitBatteryChangedBroadcast(force);
+
+            if (!rateLimitBatteryChangedBroadcast) {
+                sendBatteryChangedIntentLocked(force);
+            }
+            if (mLastBroadcastBatteryLevel != mHealthInfo.batteryLevel
+                    || mLastBroadcastPlugType != mPlugType) {
                 sendBatteryLevelChangedIntentLocked();
             }
 
@@ -811,21 +964,24 @@
                 logOutlierLocked(dischargeDuration);
             }
 
-            mLastBatteryStatus = mHealthInfo.batteryStatus;
-            mLastBatteryHealth = mHealthInfo.batteryHealth;
-            mLastBatteryPresent = mHealthInfo.batteryPresent;
-            mLastBatteryLevel = mHealthInfo.batteryLevel;
-            mLastPlugType = mPlugType;
-            mLastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts;
-            mLastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius;
-            mLastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps;
-            mLastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts;
-            mLastChargeCounter = mHealthInfo.batteryChargeCounterUah;
-            mLastBatteryLevelCritical = mBatteryLevelCritical;
-            mLastInvalidCharger = mInvalidCharger;
-            mLastBatteryCycleCount = mHealthInfo.batteryCycleCount;
-            mLastChargingState = mHealthInfo.chargingState;
-            mLastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
+            // Only update the values when we send the broadcast
+            if (!rateLimitBatteryChangedBroadcast) {
+                mLastBroadcastBatteryStatus = mHealthInfo.batteryStatus;
+                mLastBroadcastBatteryHealth = mHealthInfo.batteryHealth;
+                mLastBroadcastBatteryPresent = mHealthInfo.batteryPresent;
+                mLastBroadcastBatteryLevel = mHealthInfo.batteryLevel;
+                mLastBroadcastPlugType = mPlugType;
+                mLastBroadcastBatteryVoltage = mHealthInfo.batteryVoltageMillivolts;
+                mLastBroadcastBatteryTemperature = mHealthInfo.batteryTemperatureTenthsCelsius;
+                mLastBroadcastMaxChargingCurrent = mHealthInfo.maxChargingCurrentMicroamps;
+                mLastBroadcastMaxChargingVoltage = mHealthInfo.maxChargingVoltageMicrovolts;
+                mLastBroadcastChargeCounter = mHealthInfo.batteryChargeCounterUah;
+                mLastBroadcastBatteryLevelCritical = mBatteryLevelCritical;
+                mLastBroadcastInvalidCharger = mInvalidCharger;
+                mLastBroadcastBatteryCycleCount = mHealthInfo.batteryCycleCount;
+                mLastBroadcastChargingState = mHealthInfo.chargingState;
+                mLastBroadcastBatteryCapacityLevel = mHealthInfo.batteryCapacityLevel;
+            }
         }
     }
 
@@ -1089,6 +1245,74 @@
         }
     }
 
+    /**
+     * Rate limit's the broadcast based on the changes in temp, voltage and chargeCounter.
+     */
+    private boolean rateLimitBatteryChangedBroadcast(boolean forceUpdate) {
+        if (!com.android.server.flags.Flags.rateLimitBatteryChangedBroadcast()) {
+            return false;
+        }
+        if (mLastBroadcastBatteryVoltage == 0 || mLastBroadcastBatteryTemperature == 0) {
+            mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+            return false;
+        }
+
+        final boolean voltageUpdated =
+                mLastBroadcastBatteryVoltage != mHealthInfo.batteryVoltageMillivolts;
+        final boolean temperatureUpdated =
+                mLastBroadcastBatteryTemperature != mHealthInfo.batteryTemperatureTenthsCelsius;
+        final boolean otherStatesUpdated = forceUpdate
+                || mHealthInfo.batteryStatus != mLastBroadcastBatteryStatus
+                || mHealthInfo.batteryHealth != mLastBroadcastBatteryHealth
+                || mHealthInfo.batteryPresent != mLastBroadcastBatteryPresent
+                || mHealthInfo.batteryLevel != mLastBroadcastBatteryLevel
+                || mPlugType != mLastBroadcastPlugType
+                || mHealthInfo.maxChargingCurrentMicroamps != mLastBroadcastMaxChargingCurrent
+                || mHealthInfo.maxChargingVoltageMicrovolts != mLastBroadcastMaxChargingVoltage
+                || mInvalidCharger != mLastBroadcastInvalidCharger
+                || mHealthInfo.batteryCycleCount != mLastBroadcastBatteryCycleCount
+                || mHealthInfo.chargingState != mLastBroadcastChargingState
+                || mHealthInfo.batteryCapacityLevel != mLastBroadcastBatteryCapacityLevel;
+
+        // We only rate limit based on changes in the temp, voltage.
+        if (otherStatesUpdated) {
+
+            if (voltageUpdated) {
+                mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+            }
+            return false;
+        }
+
+        final float basePointDiff =
+                (float) (mLastBroadcastBatteryVoltage - mHealthInfo.batteryVoltageMillivolts)
+                        / mLastBroadcastBatteryVoltage;
+
+        // We only send the broadcast if voltage change is greater than 1% and last voltage
+        // update was sent at least 20 seconds back.
+        if (voltageUpdated
+                && abs(basePointDiff) >= BASE_POINT_DIFF_FOR_VOLTAGE_UPDATE
+                && SystemClock.elapsedRealtime() - mLastBroadcastVoltageUpdateTime
+                        >= TIME_DIFF_FOR_VOLTAGE_UPDATE_MS) {
+            mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+
+            return false;
+        }
+
+        // Only send the broadcast if the temperature update is greater than 1 degree celsius.
+        if (temperatureUpdated
+                && abs(
+                mLastBroadcastBatteryTemperature - mHealthInfo.batteryTemperatureTenthsCelsius)
+                        >= ABSOLUTE_DECI_CELSIUS_DIFF_FOR_TEMP_UPDATE) {
+
+            if (voltageUpdated) {
+                mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime();
+            }
+            return false;
+        }
+
+        return true;
+    }
+
     class Shell extends ShellCommand {
         @Override
         public int onCommand(String cmd) {
@@ -1399,6 +1623,10 @@
                 pw.println("  level: " + mHealthInfo.batteryLevel);
                 pw.println("  scale: " + BATTERY_SCALE);
                 pw.println("  voltage: " + mHealthInfo.batteryVoltageMillivolts);
+                pw.println(" Time when the latest updated value of the voltage was sent via "
+                        + "battery changed broadcast: " + mLastBroadcastVoltageUpdateTime);
+                pw.println(" The last voltage value sent via the battery changed broadcast: "
+                        + mLastBroadcastBatteryVoltage);
                 pw.println("  temperature: " + mHealthInfo.batteryTemperatureTenthsCelsius);
                 pw.println("  technology: " + mHealthInfo.batteryTechnology);
                 pw.println("  Charging state: " + mHealthInfo.chargingState);
@@ -1457,6 +1685,11 @@
         Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
     }
 
+    @VisibleForTesting
+    public Handler getHandlerForTest() {
+        return mHandler;
+    }
+
     @SuppressLint("AndroidFrameworkRequiresPermission")
     private static void sendBroadcastToAllUsers(Context context, Intent intent,
             Bundle options) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b5dcdb1..18e8abb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1101,7 +1101,7 @@
                 ProfilingServiceHelper.getInstance().onProfilingTriggerOccurred(
                         startInfo.getRealUid(),
                         startInfo.getPackageName(),
-                        ProfilingTrigger.TRIGGER_TYPE_APP_COLD_START_ACTIVITY);
+                        ProfilingTrigger.TRIGGER_TYPE_APP_FULLY_DRAWN);
             }
         }
     };
@@ -19307,31 +19307,18 @@
     public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
         if (!preventIntentRedirect()) return;
 
-        if (intent == null || intent.getExtraIntentKeys() == null) return;
-        for (String key : intent.getExtraIntentKeys()) {
-            try {
-                Intent extraIntent = intent.getParcelableExtra(key, Intent.class);
-                if (extraIntent == null) {
-                    Slog.w(TAG, "The key {" + key
-                            + "} does not correspond to an intent in the extra bundle.");
-                    continue;
-                }
-                IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent,
-                        creatorPackage);
-                if (creatorToken != null) {
-                    extraIntent.setCreatorToken(creatorToken);
-                    Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
-                            + creatorPackage + "; intent: " + intent);
-                    FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
-                            creatorToken.getCreatorUid());
-                }
-            } catch (Exception e) {
-                Slog.wtf(TAG,
-                        "Something went wrong when trying to add creator token for embedded "
-                                + "intents of intent: ."
-                                + intent, e);
+        if (intent == null) return;
+        intent.forEachNestedCreatorToken(extraIntent -> {
+            IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorPackage);
+            if (creatorToken != null) {
+                extraIntent.setCreatorToken(creatorToken);
+                // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329
+                Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
+                        + creatorPackage + "; intent: " + intent);
+                FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
+                        creatorToken.getCreatorUid());
             }
-        }
+        });
     }
 
     private IntentCreatorToken createIntentCreatorToken(Intent intent, String creatorPackage) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index aa9ac6c..2eb9f3c 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -124,6 +124,7 @@
 import com.android.server.Watchdog;
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.power.feature.PowerManagerFlags;
 import com.android.server.power.optimization.Flags;
 import com.android.server.power.stats.BatteryExternalStatsWorker;
 import com.android.server.power.stats.BatteryStatsDumpHelperImpl;
@@ -195,6 +196,7 @@
     private final BatteryStats.BatteryStatsDumpHelper mDumpHelper;
     private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver();
     private final PowerAttributor mPowerAttributor;
+    private final PowerManagerFlags mPowerManagerFlags = new PowerManagerFlags();
 
     private volatile boolean mMonitorEnabled = true;
     private boolean mRailsStatsCollectionEnabled = true;
@@ -617,6 +619,9 @@
                 BatteryConsumer.POWER_COMPONENT_ANY,
                 Flags.streamlinedMiscBatteryStats());
 
+        mStats.setMoveWscLoggingToNotifierEnabled(
+                mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled());
+
         mWorker.systemServicesReady();
         mStats.systemServicesReady(mContext);
         mCpuWakeupStats.systemServicesReady();
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 08632fe..c067662 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -514,27 +514,11 @@
         mLogger = new OomAdjusterDebugLogger(this, mService.mConstants);
 
         mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
-            final int pid = msg.arg1;
-            final int group = msg.arg2;
-            if (pid == ActivityManagerService.MY_PID) {
-                // Skip setting the process group for system_server, keep it as default.
-                return true;
-            }
-            final boolean traceEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-            if (traceEnabled) {
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup "
-                        + msg.obj + " to " + group);
-            }
-            try {
-                android.os.Process.setProcessGroup(pid, group);
-            } catch (Exception e) {
-                if (DEBUG_ALL) {
-                    Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e);
-                }
-            } finally {
-                if (traceEnabled) {
-                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-                }
+            final int group = msg.what;
+            final ProcessRecord app = (ProcessRecord) msg.obj;
+            setProcessGroup(app.getPid(), group, app.processName);
+            if (Flags.phantomProcessesFix()) {
+                mService.mPhantomProcessList.setProcessGroupForPhantomProcessOfApp(app, group);
             }
             return true;
         });
@@ -545,8 +529,31 @@
     }
 
     void setProcessGroup(int pid, int group, String processName) {
+        if (pid == ActivityManagerService.MY_PID) {
+            // Skip setting the process group for system_server, keep it as default.
+            return;
+        }
+        final boolean traceEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        if (traceEnabled) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setProcessGroup "
+                    + processName + " to " + group);
+        }
+        try {
+            android.os.Process.setProcessGroup(pid, group);
+        } catch (Exception e) {
+            if (DEBUG_ALL) {
+                Slog.w(TAG, "Failed setting process group of " + pid + " to " + group, e);
+            }
+        } finally {
+            if (traceEnabled) {
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            }
+        }
+    }
+
+    void setAppAndChildProcessGroup(ProcessRecord app, int group) {
         mProcessGroupHandler.sendMessage(mProcessGroupHandler.obtainMessage(
-                0 /* unused */, pid, group, processName));
+                group, app));
     }
 
     void initSettings() {
@@ -3503,8 +3510,7 @@
                     processGroup = THREAD_GROUP_DEFAULT;
                     break;
             }
-            setProcessGroup(app.getPid(), processGroup, app.processName);
-            mService.mPhantomProcessList.setProcessGroupForPhantomProcessOfApp(app, processGroup);
+            setAppAndChildProcessGroup(app, processGroup);
             try {
                 final int renderThreadTid = app.getRenderThreadTid();
                 if (curSchedGroup == SCHED_GROUP_TOP_APP) {
diff --git a/services/core/java/com/android/server/am/PhantomProcessList.java b/services/core/java/com/android/server/am/PhantomProcessList.java
index bfdced7..123780f 100644
--- a/services/core/java/com/android/server/am/PhantomProcessList.java
+++ b/services/core/java/com/android/server/am/PhantomProcessList.java
@@ -548,6 +548,7 @@
      */
     void setProcessGroupForPhantomProcessOfApp(final ProcessRecord app, final int group) {
         synchronized (mLock) {
+            lookForPhantomProcessesLocked(app);
             final SparseArray<PhantomProcessRecord> array = getPhantomProcessOfAppLocked(app);
             if (array == null) {
                 return;
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 7b4d6c7..5d5b35b 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -250,4 +250,14 @@
     is_fixed_read_only: true
     description: "Add +X to the prev scores according to their positions in the process LRU list"
     bug: "359912586"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "phantom_processes_fix"
+    namespace: "backstage_power"
+    description: "Make sure setProcessGroupForPhantomProcessOfApp deals with phantom processes properly"
+    bug: "375058190"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 4ad7c10..d2c044f 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -255,6 +255,11 @@
     public static final int DIFF_MODE_ID = 1 << 7;
 
     /**
+     * Diff result: The frame rate override list differs.
+     */
+    public static final int DIFF_FRAME_RATE_OVERRIDE = 1 << 8;
+
+    /**
      * Diff result: Catch-all for "everything changed"
      */
     public static final int DIFF_EVERYTHING = 0XFFFFFFFF;
@@ -523,6 +528,9 @@
         if (modeId != other.modeId) {
             diff |= DIFF_MODE_ID;
         }
+        if (!Arrays.equals(frameRateOverrides, other.frameRateOverrides)) {
+            diff |= DIFF_FRAME_RATE_OVERRIDE;
+        }
         if (!Objects.equals(name, other.name)
                 || !Objects.equals(uniqueId, other.uniqueId)
                 || width != other.width
@@ -546,7 +554,6 @@
                 || !Objects.equals(deviceProductInfo, other.deviceProductInfo)
                 || ownerUid != other.ownerUid
                 || !Objects.equals(ownerPackageName, other.ownerPackageName)
-                || !Arrays.equals(frameRateOverrides, other.frameRateOverrides)
                 || !BrightnessSynchronizer.floatEquals(brightnessMinimum, other.brightnessMinimum)
                 || !BrightnessSynchronizer.floatEquals(brightnessMaximum, other.brightnessMaximum)
                 || !BrightnessSynchronizer.floatEquals(brightnessDefault,
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 086f8a9..5f7bc4e 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -27,6 +27,7 @@
 import com.android.server.display.DisplayManagerService.SyncRoot;
 import com.android.server.display.utils.DebugUtils;
 
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Consumer;
@@ -177,18 +178,22 @@
                         "handleDisplayDeviceChanged");
             }
             int diff = device.mDebugLastLoggedDeviceInfo.diff(info);
-            if (diff == DisplayDeviceInfo.DIFF_STATE) {
+            if (diff == 0) {
+                Slog.i(TAG, "Display device same: " + info);
+            } else if (diff == DisplayDeviceInfo.DIFF_STATE) {
                 Slog.i(TAG, "Display device changed state: \"" + info.name
                         + "\", " + Display.stateToString(info.state));
             } else if (diff == DisplayDeviceInfo.DIFF_ROTATION) {
                 Slog.i(TAG, "Display device rotated: \"" + info.name
                         + "\", " + Surface.rotationToString(info.rotation));
-            } else if (diff
-                    == (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS)) {
+            } else if ((diff &
+                    (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS
+                            | DisplayDeviceInfo.DIFF_FRAME_RATE_OVERRIDE)) != 0) {
                 Slog.i(TAG, "Display device changed render timings: \"" + info.name
                         + "\", renderFrameRate=" + info.renderFrameRate
                         + ", presentationDeadlineNanos=" + info.presentationDeadlineNanos
-                        + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos);
+                        + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos
+                        + ", frameRateOverrides=" + Arrays.toString(info.frameRateOverrides));
             } else if (diff == DisplayDeviceInfo.DIFF_COMMITTED_STATE) {
                 if (DEBUG) {
                     Slog.i(TAG, "Display device changed committed state: \"" + info.name
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f5a75c7d..5a2610b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -25,7 +25,7 @@
 import static android.Manifest.permission.RESTRICT_DISPLAY_MODES;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
-import static android.hardware.display.DisplayManager.EventFlag;
+import static android.hardware.display.DisplayManagerGlobal.InternalEventFlag;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
@@ -96,6 +96,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.DisplayGroupListener;
 import android.hardware.display.DisplayManagerInternal.DisplayTransactionListener;
+import android.hardware.display.DisplayTopology;
 import android.hardware.display.DisplayViewport;
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -118,6 +119,7 @@
 import android.os.IThermalService;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PermissionEnforcer;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -1390,16 +1392,16 @@
     }
 
     private void registerCallbackInternal(IDisplayManagerCallback callback, int callingPid,
-            int callingUid, @EventFlag long eventFlagsMask) {
+            int callingUid, @InternalEventFlag long internalEventFlagsMask) {
         synchronized (mSyncRoot) {
             CallbackRecord record = mCallbacks.get(callingPid);
 
             if (record != null) {
-                record.updateEventFlagsMask(eventFlagsMask);
+                record.updateEventFlagsMask(internalEventFlagsMask);
                 return;
             }
 
-            record = new CallbackRecord(callingPid, callingUid, callback, eventFlagsMask);
+            record = new CallbackRecord(callingPid, callingUid, callback, internalEventFlagsMask);
             try {
                 IBinder binder = callback.asBinder();
                 binder.linkToDeath(record, 0);
@@ -4009,7 +4011,7 @@
         public final int mPid;
         public final int mUid;
         private final IDisplayManagerCallback mCallback;
-        private @DisplayManager.EventFlag AtomicLong mEventFlagsMask;
+        private @InternalEventFlag AtomicLong mInternalEventFlagsMask;
         private final String mPackageName;
 
         public boolean mWifiDisplayScanRequested;
@@ -4030,11 +4032,11 @@
         private boolean mFrozen;
 
         CallbackRecord(int pid, int uid, @NonNull IDisplayManagerCallback callback,
-                @EventFlag long eventFlagsMask) {
+                @InternalEventFlag long internalEventFlagsMask) {
             mPid = pid;
             mUid = uid;
             mCallback = callback;
-            mEventFlagsMask = new AtomicLong(eventFlagsMask);
+            mInternalEventFlagsMask = new AtomicLong(internalEventFlagsMask);
             mCached = false;
             mFrozen = false;
 
@@ -4056,8 +4058,8 @@
             mPackageName = packageNames == null ? null : packageNames[0];
         }
 
-        public void updateEventFlagsMask(@EventFlag long eventFlag) {
-            mEventFlagsMask.set(eventFlag);
+        public void updateEventFlagsMask(@InternalEventFlag long internalEventFlag) {
+            mInternalEventFlagsMask.set(internalEventFlag);
         }
 
         /**
@@ -4121,13 +4123,13 @@
             if (!shouldSendEvent(event)) {
                 if (extraLogging(mPackageName)) {
                     Slog.i(TAG,
-                            "Not sending displayEvent: " + event + " due to flag:"
-                                    + mEventFlagsMask);
+                            "Not sending displayEvent: " + event + " due to mask:"
+                                    + mInternalEventFlagsMask);
                 }
                 if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
                     Trace.instant(Trace.TRACE_TAG_POWER,
-                            "notifyDisplayEventAsync#notSendingEvent=" + event + ",mEventsFlag="
-                                    + mEventFlagsMask);
+                            "notifyDisplayEventAsync#notSendingEvent=" + event
+                                    + ",mInternalEventFlagsMask=" + mInternalEventFlagsMask);
                 }
                 // The client is not interested in this event, so do nothing.
                 return true;
@@ -4173,22 +4175,29 @@
          * Return true if the client is interested in this event.
          */
         private boolean shouldSendEvent(@DisplayEvent int event) {
-            final long flag = mEventFlagsMask.get();
+            final long mask = mInternalEventFlagsMask.get();
             switch (event) {
                 case DisplayManagerGlobal.EVENT_DISPLAY_ADDED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_ADDED) != 0;
+                    return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED) != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_CHANGED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_CHANGED) != 0;
+                    return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED) != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_BRIGHTNESS_CHANGED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS) != 0;
+                    return (mask
+                            & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BRIGHTNESS_CHANGED)
+                            != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_REMOVED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_REMOVED) != 0;
+                    return (mask & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED) != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_HDR_SDR_RATIO_CHANGED:
-                    return (flag & DisplayManager.EVENT_FLAG_HDR_SDR_RATIO_CHANGED) != 0;
+                    return (mask
+                            & DisplayManagerGlobal
+                            .INTERNAL_EVENT_FLAG_DISPLAY_HDR_SDR_RATIO_CHANGED)
+                            != 0;
                 case DisplayManagerGlobal.EVENT_DISPLAY_CONNECTED:
                     // fallthrough
                 case DisplayManagerGlobal.EVENT_DISPLAY_DISCONNECTED:
-                    return (flag & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0;
+                    return (mask
+                            & DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED)
+                            != 0;
                 default:
                     // This should never happen.
                     Slog.e(TAG, "Unknown display event " + event);
@@ -4314,6 +4323,10 @@
 
     @VisibleForTesting
     final class BinderService extends IDisplayManager.Stub {
+        BinderService() {
+            super(PermissionEnforcer.fromContext(getContext()));
+        }
+
         /**
          * Returns information about the specified logical display.
          *
@@ -4374,15 +4387,16 @@
 
         @Override // Binder call
         public void registerCallback(IDisplayManagerCallback callback) {
-            registerCallbackWithEventMask(callback, DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+            registerCallbackWithEventMask(callback,
+                    DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED);
         }
 
         @Override // Binder call
         @SuppressLint("AndroidFrameworkRequiresPermission") // Permission only required sometimes
         public void registerCallbackWithEventMask(IDisplayManagerCallback callback,
-                @EventFlag long eventFlagsMask) {
+                @InternalEventFlag long internalEventFlagsMask) {
             if (callback == null) {
                 throw new IllegalArgumentException("listener must not be null");
             }
@@ -4391,7 +4405,9 @@
             final int callingUid = Binder.getCallingUid();
 
             if (mFlags.isConnectedDisplayManagementEnabled()) {
-                if ((eventFlagsMask & DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
+                if ((internalEventFlagsMask
+                        & DisplayManagerGlobal
+                        .INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED) != 0) {
                     mContext.enforceCallingOrSelfPermission(MANAGE_DISPLAYS,
                             "Permission required to get signals about connection events.");
                 }
@@ -4399,7 +4415,7 @@
 
             final long token = Binder.clearCallingIdentity();
             try {
-                registerCallbackInternal(callback, callingPid, callingUid, eventFlagsMask);
+                registerCallbackInternal(callback, callingPid, callingUid, internalEventFlagsMask);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -5192,6 +5208,25 @@
             }
             return ddc.getDefaultDozeBrightness();
         }
+
+        @EnforcePermission(MANAGE_DISPLAYS)
+        @Override // Binder call
+        public DisplayTopology getDisplayTopology() {
+            getDisplayTopology_enforcePermission();
+            if (mDisplayTopologyCoordinator == null) {
+                return null;
+            }
+            return mDisplayTopologyCoordinator.getTopology();
+        }
+
+        @EnforcePermission(MANAGE_DISPLAYS)
+        @Override // Binder call
+        public void setDisplayTopology(DisplayTopology topology) {
+            setDisplayTopology_enforcePermission();
+            if (mDisplayTopologyCoordinator != null) {
+                mDisplayTopologyCoordinator.setTopology(topology);
+            }
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index a9ed0aa..c90dfbf 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1595,7 +1595,8 @@
         // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
         // we broadcast this change through setting.
         final float unthrottledBrightnessState = rawBrightnessState;
-        DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
+        DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(
+                displayBrightnessState, mPowerRequest,
                 brightnessState, slowChange, /* displayState= */ state);
         brightnessState = clampedState.getBrightness();
         slowChange = clampedState.isSlowChange();
@@ -2003,7 +2004,9 @@
                     mCachedBrightnessInfo.brightnessMax.value,
                     mCachedBrightnessInfo.hbmMode.value,
                     mCachedBrightnessInfo.hbmTransitionPoint.value,
-                    mCachedBrightnessInfo.brightnessMaxReason.value);
+                    mCachedBrightnessInfo.brightnessMaxReason.value,
+                    mCachedBrightnessInfo.brightnessReason.value
+                            == BrightnessReason.REASON_OVERRIDE);
         }
     }
 
@@ -2028,6 +2031,8 @@
             @BrightnessInfo.BrightnessMaxReason int maxReason =
                     state != null ? state.getBrightnessMaxReason()
                             : BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+            BrightnessReason brightnessReason = state != null ? state.getBrightnessReason()
+                    : new BrightnessReason(BrightnessReason.REASON_UNKNOWN);
             final float minBrightness = Math.max(stateMin, Math.min(
                     mBrightnessRangeController.getCurrentBrightnessMin(), stateMax));
             final float maxBrightness = Math.min(
@@ -2055,6 +2060,9 @@
             changed |=
                     mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessMaxReason,
                             maxReason);
+            changed |=
+                    mCachedBrightnessInfo.checkAndSetInt(mCachedBrightnessInfo.brightnessReason,
+                            brightnessReason.getReason());
             return changed;
         }
     }
@@ -2683,6 +2691,8 @@
                     + mCachedBrightnessInfo.hbmTransitionPoint.value);
             pw.println("  mCachedBrightnessInfo.brightnessMaxReason ="
                     + mCachedBrightnessInfo.brightnessMaxReason.value);
+            pw.println("  mCachedBrightnessInfo.brightnessReason ="
+                    + mCachedBrightnessInfo.brightnessReason);
         }
         pw.println("  mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
         pw.println("  mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -3390,6 +3400,7 @@
                 new MutableFloat(HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID);
         public MutableInt brightnessMaxReason =
                 new MutableInt(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE);
+        public MutableInt brightnessReason = new MutableInt(BrightnessReason.REASON_UNKNOWN);
 
         public boolean checkAndSetFloat(MutableFloat mf, float f) {
             if (mf.value != f) {
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
index b101e58..4722686 100644
--- a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -16,6 +16,7 @@
 
 package com.android.server.display;
 
+import android.hardware.display.DisplayTopology;
 import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.DisplayInfo;
@@ -33,7 +34,7 @@
 class DisplayTopologyCoordinator {
 
     @GuardedBy("mLock")
-    private final DisplayTopology mTopology;
+    private DisplayTopology mTopology;
 
     /**
      * Check if extended displays are enabled. If not, a topology is not needed.
@@ -76,6 +77,21 @@
     }
 
     /**
+     * @return A deep copy of the topology.
+     */
+    DisplayTopology getTopology() {
+        synchronized (mLock) {
+            return mTopology;
+        }
+    }
+
+    void setTopology(DisplayTopology topology) {
+        synchronized (mLock) {
+            mTopology = topology;
+        }
+    }
+
+    /**
      * Print the object's state and debug information into the given stream.
      * @param pw The stream to dump information to.
      */
@@ -108,6 +124,7 @@
                 && info.displayGroupId == Display.DEFAULT_DISPLAY_GROUP;
     }
 
+    @VisibleForTesting
     static class Injector {
         DisplayTopology getTopology() {
             return new DisplayTopology();
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index a10094f..6e579bf 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -172,17 +172,18 @@
      * Applies clamping
      * Called in DisplayControllerHandler
      */
-    public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request,
+    public DisplayBrightnessState clamp(DisplayBrightnessState displayBrightnessState,
+            DisplayManagerInternal.DisplayPowerRequest request,
             float brightnessValue, boolean slowChange, int displayState) {
         float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
 
-        DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.Builder.from(
+                displayBrightnessState);
         builder.setIsSlowChange(slowChange);
         builder.setBrightness(cappedBrightness);
         builder.setMaxBrightness(mBrightnessCap);
         builder.setCustomAnimationRate(mCustomAnimationRate);
         builder.setBrightnessMaxReason(getBrightnessMaxReason());
-
         if (mClamperType != null) {
             builder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED);
             if (!mClamperApplied) {
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 88562ab..8423e19 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -2077,8 +2077,8 @@
             mDeviceConfigDisplaySettings.startListening();
 
             mInjector.registerDisplayListener(this, mHandler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                            | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+                    DisplayManager.EVENT_FLAG_DISPLAY_CHANGED,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS);
         }
 
         private void setLoggingEnabled(boolean loggingEnabled) {
@@ -2878,8 +2878,8 @@
             }
             mDisplayManagerInternal = mInjector.getDisplayManagerInternal();
             mInjector.registerDisplayListener(this, mHandler,
-                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                    | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
+                    DisplayManager.EVENT_FLAG_DISPLAY_REMOVED,
+                    DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS);
         }
 
         /**
@@ -3108,6 +3108,9 @@
         void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
                 Handler handler, long flags);
 
+        void registerDisplayListener(@NonNull DisplayManager.DisplayListener listener,
+                Handler handler, long flags, long privateFlags);
+
         Display getDisplay(int displayId);
 
         Display[] getDisplays();
@@ -3175,6 +3178,12 @@
         }
 
         @Override
+        public void registerDisplayListener(DisplayManager.DisplayListener listener,
+                Handler handler, long flags, long privateFlags) {
+            getDisplayManager().registerDisplayListener(listener, handler, flags, privateFlags);
+        }
+
+        @Override
         public Display getDisplay(int displayId) {
             return getDisplayManager().getDisplay(displayId);
         }
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 794eb87..0c04be1 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.service.dreams.Flags.cleanupDreamSettingsOnUninstall;
 import static android.service.dreams.Flags.dreamHandlesBeingObscured;
 
 import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
@@ -353,28 +354,32 @@
     @Override
     public void onUserStarting(@NonNull TargetUser user) {
         super.onUserStarting(user);
-        mHandler.post(() -> {
-            final int userId = user.getUserIdentifier();
-            if (!mPackageMonitors.contains(userId)) {
-                final PackageMonitor monitor = new PerUserPackageMonitor();
-                monitor.register(mContext, UserHandle.of(userId), mHandler);
-                mPackageMonitors.put(userId, monitor);
-            } else {
-                Slog.w(TAG, "Package monitor already registered for " + userId);
-            }
-        });
+        if (cleanupDreamSettingsOnUninstall()) {
+            mHandler.post(() -> {
+                final int userId = user.getUserIdentifier();
+                if (!mPackageMonitors.contains(userId)) {
+                    final PackageMonitor monitor = new PerUserPackageMonitor();
+                    monitor.register(mContext, UserHandle.of(userId), mHandler);
+                    mPackageMonitors.put(userId, monitor);
+                } else {
+                    Slog.w(TAG, "Package monitor already registered for " + userId);
+                }
+            });
+        }
     }
 
     @Override
     public void onUserStopping(@NonNull TargetUser user) {
         super.onUserStopping(user);
-        mHandler.post(() -> {
-            final PackageMonitor monitor = mPackageMonitors.removeReturnOld(
-                    user.getUserIdentifier());
-            if (monitor != null) {
-                monitor.unregister();
-            }
-        });
+        if (cleanupDreamSettingsOnUninstall()) {
+            mHandler.post(() -> {
+                final PackageMonitor monitor = mPackageMonitors.removeReturnOld(
+                        user.getUserIdentifier());
+                if (monitor != null) {
+                    monitor.unregister();
+                }
+            });
+        }
     }
 
     private void dumpInternal(PrintWriter pw) {
@@ -715,15 +720,23 @@
                         userId));
         if (componentNames != null) {
             // Filter out any components in the removed package.
-            final ComponentName[] filteredComponents = Arrays.stream(componentNames).filter(
-                    (componentName -> !TextUtils.equals(componentName.getPackageName(),
-                            packageName))).toArray(ComponentName[]::new);
+            final ComponentName[] filteredComponents =
+                    Arrays.stream(componentNames)
+                            .filter((componentName -> !isSamePackage(packageName, componentName)))
+                            .toArray(ComponentName[]::new);
             if (filteredComponents.length != componentNames.length) {
                 setDreamComponentsForUser(userId, filteredComponents);
             }
         }
     }
 
+    private static boolean isSamePackage(String packageName, ComponentName componentName) {
+        if (packageName == null || componentName == null) {
+            return false;
+        }
+        return TextUtils.equals(componentName.getPackageName(), packageName);
+    }
+
     private void setDreamComponentsForUser(int userId, ComponentName[] componentNames) {
         Settings.Secure.putStringForUser(mContext.getContentResolver(),
                 Settings.Secure.SCREENSAVER_COMPONENTS,
@@ -884,7 +897,10 @@
         }
         StringBuilder names = new StringBuilder();
         for (ComponentName componentName : componentNames) {
-            if (names.length() > 0) {
+            if (componentName == null) {
+                continue;
+            }
+            if (!names.isEmpty()) {
                 names.append(',');
             }
             names.append(componentName.flattenToString());
diff --git a/services/core/java/com/android/server/flags/services.aconfig b/services/core/java/com/android/server/flags/services.aconfig
index 69ba785..eea5c98 100644
--- a/services/core/java/com/android/server/flags/services.aconfig
+++ b/services/core/java/com/android/server/flags/services.aconfig
@@ -67,3 +67,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    namespace: "backstage_power"
+    name: "rate_limit_battery_changed_broadcast"
+    description: "Optimize the delivery of the battery changed broadcast by rate limiting the frequency of the updates"
+    bug: "362337621"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index f2e2f65..5b4c033 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -1001,7 +1001,7 @@
             try {
                 // Create an AIDL callback that can callback onHotplugEvent
                 mHdmiConnection.setCallback(new HdmiConnectionCallbackAidl(callback));
-            } catch (RemoteException e) {
+            } catch (RemoteException | NullPointerException e) {
                 HdmiLogger.error("Couldn't initialise tv.hdmi callback : ", e);
             }
         }
@@ -1134,7 +1134,7 @@
                     i++;
                 }
                 return hdmiPortInfo;
-            } catch (RemoteException e) {
+            } catch (RemoteException | NullPointerException e) {
                 HdmiLogger.error("Failed to get port information : ", e);
                 return null;
             }
@@ -1144,7 +1144,7 @@
         public boolean nativeIsConnected(int port) {
             try {
                 return mHdmiConnection.isConnected(port);
-            } catch (RemoteException e) {
+            } catch (RemoteException | NullPointerException e) {
                 HdmiLogger.error("Failed to get connection info : ", e);
                 return false;
             }
@@ -1158,7 +1158,7 @@
                 HdmiLogger.error(
                         "Could not set HPD signal type for portId " + portId + " to " + signal
                                 + ". Error: ", sse.errorCode);
-            } catch (RemoteException e) {
+            } catch (RemoteException | NullPointerException e) {
                 HdmiLogger.error(
                         "Could not set HPD signal type for portId " + portId + " to " + signal
                                 + ". Exception: ", e);
@@ -1169,7 +1169,7 @@
         public int nativeGetHpdSignalType(int portId) {
             try {
                 return mHdmiConnection.getHpdSignal(portId);
-            } catch (RemoteException e) {
+            } catch (RemoteException | NullPointerException e) {
                 HdmiLogger.error(
                         "Could not get HPD signal type for portId " + portId + ". Exception: ", e);
                 return Constants.HDMI_HPD_TYPE_PHYSICAL;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index bf415a3..7505c71 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -646,9 +646,9 @@
         int address = message.getSource();
         int type = message.getParams()[2];
 
-        if (!ActiveSource.of(address, path).equals(getActiveSource())) {
-            HdmiLogger.debug("Check if a new device is connected to the active path");
-            handleNewDeviceAtTheTailOfActivePath(path);
+        if (getActiveSource().logicalAddress != address && getActivePath() == path) {
+            HdmiLogger.debug("New logical address detected on the current active path.");
+            startRoutingControl(path, path, null);
         }
         startNewDeviceAction(ActiveSource.of(address, path), type);
         return Constants.HANDLED;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 132d6fa..0c5069f 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -771,6 +771,14 @@
             Slog.i(TAG, "Device does not support eARC.");
         }
         mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
+        if (isTvDevice() && getWasCecDisabledOnStandbyByLowEnergyMode()) {
+            Slog.w(TAG, "Re-enable CEC on boot-up since it was disabled due to low energy "
+                    + " mode.");
+            getHdmiCecConfig().setIntValue(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                    HDMI_CEC_CONTROL_ENABLED);
+            setWasCecDisabledOnStandbyByLowEnergyMode(false);
+            setCecEnabled(HDMI_CEC_CONTROL_ENABLED);
+        }
         if (isCecControlEnabled()) {
             initializeCec(INITIATED_BY_BOOT_UP);
         } else {
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 8cb51ce..e545dd5 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -323,26 +323,50 @@
                 return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
             }
             customGestures.remove(data.getTrigger());
-            if (customGestures.size() == 0) {
+            if (customGestures.isEmpty()) {
                 mCustomInputGestures.remove(userId);
             }
             return InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS;
         }
     }
 
-    public void removeAllCustomInputGestures(int userId) {
+    public void removeAllCustomInputGestures(int userId, @Nullable InputGestureData.Filter filter) {
         synchronized (mGestureLock) {
-            mCustomInputGestures.remove(userId);
+            Map<InputGestureData.Trigger, InputGestureData> customGestures =
+                    mCustomInputGestures.get(userId);
+            if (customGestures == null) {
+                return;
+            }
+            if (filter == null) {
+                mCustomInputGestures.remove(userId);
+                return;
+            }
+            customGestures.entrySet().removeIf(entry -> filter.matches(entry.getValue()));
+            if (customGestures.isEmpty()) {
+                mCustomInputGestures.remove(userId);
+            }
         }
     }
 
     @NonNull
-    public List<InputGestureData> getCustomInputGestures(int userId) {
+    public List<InputGestureData> getCustomInputGestures(int userId,
+            @Nullable InputGestureData.Filter filter) {
         synchronized (mGestureLock) {
             if (!mCustomInputGestures.contains(userId)) {
                 return List.of();
             }
-            return new ArrayList<>(mCustomInputGestures.get(userId).values());
+            Map<InputGestureData.Trigger, InputGestureData> customGestures =
+                    mCustomInputGestures.get(userId);
+            if (filter == null) {
+                return new ArrayList<>(customGestures.values());
+            }
+            List<InputGestureData> result = new ArrayList<>();
+            for (InputGestureData customGesture : customGestures.values()) {
+                if (filter.matches(customGesture)) {
+                    result.add(customGesture);
+                }
+            }
+            return result;
         }
     }
 
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index e0f3a9b..f4dd717 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -3017,15 +3017,16 @@
 
     @Override
     @PermissionManuallyEnforced
-    public void removeAllCustomInputGestures(@UserIdInt int userId) {
+    public void removeAllCustomInputGestures(@UserIdInt int userId, int tag) {
         enforceManageKeyGesturePermission();
 
-        mKeyGestureController.removeAllCustomInputGestures(userId);
+        mKeyGestureController.removeAllCustomInputGestures(userId, InputGestureData.Filter.of(tag));
     }
 
     @Override
-    public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId) {
-        return mKeyGestureController.getCustomInputGestures(userId);
+    public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId, int tag) {
+        return mKeyGestureController.getCustomInputGestures(userId,
+                InputGestureData.Filter.of(tag));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index fc10640..0124e25 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
 
+import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
 import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
 
@@ -750,6 +751,28 @@
                     }
                 }
                 break;
+            case KeyEvent.KEYCODE_LOCK:
+                if (enableNew25q2Keycodes()) {
+                    if (firstDown) {
+                        handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_LOCK},
+                                /* modifierState = */0,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken,
+                                /* flags = */0, /* appLaunchData = */null);
+                    }
+                }
+                return true;
+            case KeyEvent.KEYCODE_FULLSCREEN:
+                if (enableNew25q2Keycodes()) {
+                    if (firstDown) {
+                        handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_FULLSCREEN},
+                                /* modifierState = */0,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken,
+                                /* flags = */0, /* appLaunchData = */null);
+                    }
+                }
+                return true;
             case KeyEvent.KEYCODE_ASSIST:
                 Slog.wtf(TAG, "KEYCODE_ASSIST should be handled in interceptKeyBeforeQueueing");
                 return true;
@@ -764,6 +787,9 @@
                 Slog.wtf(TAG, "KEYCODE_STYLUS_BUTTON_* should be handled in"
                         + " interceptKeyBeforeQueueing");
                 return true;
+            case KeyEvent.KEYCODE_DO_NOT_DISTURB:
+                // TODO(b/365920375): Implement 25Q2 keycode implementation in system
+                return true;
         }
 
         // Handle custom shortcuts
@@ -1021,13 +1047,16 @@
     }
 
     @BinderThread
-    public void removeAllCustomInputGestures(@UserIdInt int userId) {
-        mInputGestureManager.removeAllCustomInputGestures(userId);
+    public void removeAllCustomInputGestures(@UserIdInt int userId,
+            @Nullable InputGestureData.Filter filter) {
+        mInputGestureManager.removeAllCustomInputGestures(userId, filter);
     }
 
     @BinderThread
-    public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId) {
-        List<InputGestureData> customGestures = mInputGestureManager.getCustomInputGestures(userId);
+    public AidlInputGestureData[] getCustomInputGestures(@UserIdInt int userId,
+            @Nullable InputGestureData.Filter filter) {
+        List<InputGestureData> customGestures = mInputGestureManager.getCustomInputGestures(userId,
+                filter);
         AidlInputGestureData[] result = new AidlInputGestureData[customGestures.size()];
         for (int i = 0; i < customGestures.size(); i++) {
             result[i] = customGestures.get(i).getAidlData();
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index acc8f66..f611c57 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -35,6 +35,7 @@
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubMessage;
 import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.HubInfo;
 import android.hardware.location.IContextHubCallback;
 import android.hardware.location.IContextHubClient;
 import android.hardware.location.IContextHubClientCallback;
@@ -57,6 +58,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Pair;
 import android.util.proto.ProtoOutputStream;
@@ -134,6 +136,9 @@
     private Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
     private List<String> mSupportedContextHubPerms;
     private List<ContextHubInfo> mContextHubInfoList;
+
+    @Nullable private final HubInfoRegistry mHubInfoRegistry;
+
     private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
             new RemoteCallbackList<>();
 
@@ -309,10 +314,21 @@
         mContext = context;
         long startTimeNs = SystemClock.elapsedRealtimeNanos();
         mContextHubWrapper = contextHubWrapper;
+
         if (!initContextHubServiceState(startTimeNs)) {
             Log.e(TAG, "Failed to initialize the Context Hub Service");
+            mHubInfoRegistry = null;
             return;
         }
+
+        if (Flags.offloadApi()) {
+            mHubInfoRegistry = new HubInfoRegistry(mContextHubWrapper);
+            Log.i(TAG, "Enabling generic offload API");
+        } else {
+            mHubInfoRegistry = null;
+            Log.i(TAG, "Disabling generic offload API");
+        }
+
         initDefaultClientMap();
 
         initLocationSettingNotifications();
@@ -427,7 +443,7 @@
 
         Pair<List<ContextHubInfo>, List<String>> hubInfo;
         try {
-            hubInfo = mContextHubWrapper.getHubs();
+            hubInfo = mContextHubWrapper.getContextHubs();
         } catch (RemoteException e) {
             Log.e(TAG, "RemoteException while getting Context Hub info", e);
             hubInfo = new Pair<>(Collections.emptyList(), Collections.emptyList());
@@ -713,6 +729,16 @@
         return mContextHubInfoList;
     }
 
+    @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
+    @Override
+    public List<HubInfo> getHubs() throws RemoteException {
+        super.getHubs_enforcePermission();
+        if (mHubInfoRegistry == null) {
+            return Collections.emptyList();
+        }
+        return mHubInfoRegistry.getHubs();
+    }
+
     /**
      * Creates an internal load transaction callback to be used for old API clients
      *
@@ -1417,6 +1443,8 @@
             }
         }
 
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        pw = ipw;
         pw.println("Dumping ContextHub Service");
 
         pw.println("");
@@ -1428,6 +1456,11 @@
         pw.println("Supported permissions: "
                 + Arrays.toString(mSupportedContextHubPerms.toArray()));
         pw.println("");
+
+        if (mHubInfoRegistry != null) {
+            mHubInfoRegistry.dump(ipw);
+        }
+
         pw.println("=================== NANOAPPS ====================");
         // Dump nanoAppHash
         mNanoAppStateManager.foreachNanoAppInstanceInfo(pw::println);
diff --git a/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
new file mode 100644
index 0000000..68de9db
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/HubInfoRegistry.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location.contexthub;
+
+import android.hardware.location.HubInfo;
+import android.os.RemoteException;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+class HubInfoRegistry {
+    private static final String TAG = "HubInfoRegistry";
+
+    private final IContextHubWrapper mContextHubWrapper;
+
+    private final List<HubInfo> mHubsInfo;
+
+    HubInfoRegistry(IContextHubWrapper contextHubWrapper) {
+        List<HubInfo> hubInfos;
+        mContextHubWrapper = contextHubWrapper;
+        try {
+            hubInfos = mContextHubWrapper.getHubs();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while getting Hub info", e);
+            hubInfos = Collections.emptyList();
+        }
+        mHubsInfo = hubInfos;
+    }
+
+    /** Retrieve the list of hubs available. */
+    List<HubInfo> getHubs() {
+        return mHubsInfo;
+    }
+
+    void dump(IndentingPrintWriter ipw) {
+        ipw.println(TAG);
+
+        ipw.increaseIndent();
+        for (HubInfo hubInfo : mHubsInfo) {
+            ipw.println(hubInfo);
+        }
+        ipw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 5e9277a..6656a6f 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -30,9 +30,11 @@
 import android.hardware.contexthub.V1_2.IContexthubCallback;
 import android.hardware.location.ContextHubInfo;
 import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.HubInfo;
 import android.hardware.location.NanoAppBinary;
 import android.hardware.location.NanoAppMessage;
 import android.hardware.location.NanoAppState;
+import android.hardware.location.VendorHubInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -52,13 +54,14 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * @hide
  */
 public abstract class IContextHubWrapper {
+    private static final boolean DEBUG = false;
     private static final String TAG = "IContextHubWrapper";
 
     /**
@@ -217,10 +220,14 @@
         return proxy == null ? null : new ContextHubWrapperAidl(proxy);
     }
 
-    /**
-     * Calls the appropriate getHubs function depending on the HAL version.
-     */
-    public abstract Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException;
+    /** Calls the appropriate getHubs function depending on the HAL version. */
+    public abstract Pair<List<ContextHubInfo>, List<String>> getContextHubs()
+            throws RemoteException;
+
+    /** Calls the appropriate getHubs function depending on the HAL version. */
+    public List<HubInfo> getHubs() throws RemoteException {
+        return Collections.emptyList();
+    }
 
     /**
      * @return True if this version of the Contexthub HAL supports Location setting notifications.
@@ -556,7 +563,7 @@
             mIsTestModeEnabled.set(false);
         }
 
-        public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+        public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
             android.hardware.contexthub.IContextHub hub = getHub();
             if (hub == null) {
                 return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(),
@@ -574,6 +581,47 @@
             return new Pair(hubInfoList, new ArrayList<String>(supportedPermissions));
         }
 
+        public List<HubInfo> getHubs() throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return Collections.emptyList();
+            }
+
+            List<HubInfo> retVal = new ArrayList<>();
+            final List<android.hardware.contexthub.HubInfo> halHubs = hub.getHubs();
+
+            for (android.hardware.contexthub.HubInfo halHub : halHubs) {
+                /* HAL -> API Type conversion */
+                final HubInfo hubInfo;
+                switch (halHub.hubDetails.getTag()) {
+                    case android.hardware.contexthub.HubInfo.HubDetails.contextHubInfo:
+                        ContextHubInfo contextHubInfo =
+                                new ContextHubInfo(halHub.hubDetails.getContextHubInfo());
+                        hubInfo = new HubInfo(halHub.hubId, contextHubInfo);
+                        break;
+                    case android.hardware.contexthub.HubInfo.HubDetails.vendorHubInfo:
+                        VendorHubInfo vendorHubInfo =
+                                new VendorHubInfo(halHub.hubDetails.getVendorHubInfo());
+                        hubInfo = new HubInfo(halHub.hubId, vendorHubInfo);
+                        break;
+                    default:
+                        Log.w(TAG, "getHubs: invalid hub: " + halHub);
+                        // Invalid
+                        continue;
+                }
+
+                if (DEBUG) {
+                    Log.i(TAG, "getHubs: hubInfo=" + hubInfo);
+                }
+                retVal.add(hubInfo);
+            }
+
+            if (DEBUG) {
+                Log.i(TAG, "getHubs: total count=" + retVal.size());
+            }
+            return retVal;
+        }
+
         public boolean supportsLocationSettingNotifications() {
             return true;
         }
@@ -1061,7 +1109,7 @@
             mHub = hub;
         }
 
-        public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+        public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
             for (ContextHub hub : mHub.getHubs()) {
                 hubInfoList.add(new ContextHubInfo(hub));
@@ -1106,7 +1154,7 @@
             mHub = hub;
         }
 
-        public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+        public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
             for (ContextHub hub : mHub.getHubs()) {
                 hubInfoList.add(new ContextHubInfo(hub));
@@ -1170,7 +1218,7 @@
             mHubInfo = new Pair(hubInfoList, supportedPermissions);
         }
 
-        public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+        public Pair<List<ContextHubInfo>, List<String>> getContextHubs() throws RemoteException {
             mHub.getHubs_1_2(this);
             return mHubInfo;
         }
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index a45ea1d..21ae182 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -59,15 +59,15 @@
             return pp;
         }
         @Override
-        public void updatePictureProfile(long id, PictureProfile pp) {
+        public void updatePictureProfile(String id, PictureProfile pp) {
             // TODO: implement
         }
         @Override
-        public void removePictureProfile(long id) {
+        public void removePictureProfile(String id) {
             // TODO: implement
         }
         @Override
-        public PictureProfile getPictureProfileById(long id) {
+        public PictureProfile getPictureProfile(int type, String name) {
             return null;
         }
         @Override
@@ -79,7 +79,7 @@
             return new ArrayList<>();
         }
         @Override
-        public List<PictureProfile> getAllPictureProfiles() {
+        public List<String> getPictureProfilePackageNames() {
             return new ArrayList<>();
         }
 
@@ -89,15 +89,15 @@
             return pp;
         }
         @Override
-        public void updateSoundProfile(long id, SoundProfile pp) {
+        public void updateSoundProfile(String id, SoundProfile pp) {
             // TODO: implement
         }
         @Override
-        public void removeSoundProfile(long id) {
+        public void removeSoundProfile(String id) {
             // TODO: implement
         }
         @Override
-        public SoundProfile getSoundProfileById(long id) {
+        public SoundProfile getSoundProfileById(String id) {
             return null;
         }
         @Override
@@ -109,7 +109,7 @@
             return new ArrayList<>();
         }
         @Override
-        public List<SoundProfile> getAllSoundProfiles() {
+        public List<String> getSoundProfilePackageNames() {
             return new ArrayList<>();
         }
 
@@ -138,6 +138,14 @@
             return new ArrayList<>();
         }
 
+        @Override
+        public List<String> getPictureProfileAllowList() {
+            return new ArrayList<>();
+        }
+
+        @Override
+        public void setPictureProfileAllowList(List<String> packages) {
+        }
 
         @Override
         public boolean isSupported() {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 2a3be1e..7de2815 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -513,12 +513,6 @@
     private boolean mLoadedRestrictBackground;
 
     /**
-     * Whether or not network for apps in proc-states greater than
-     * {@link NetworkPolicyManager#BACKGROUND_THRESHOLD_STATE} is always blocked.
-     */
-    private boolean mBackgroundNetworkRestricted;
-
-    /**
      * Whether or not metered firewall chains should be used for uid policy controlling access to
      * metered networks.
      */
@@ -1117,14 +1111,7 @@
                         writePolicyAL();
                     }
 
-                    // The flag is boot-stable.
-                    mBackgroundNetworkRestricted = Flags.networkBlockedForTopSleepingAndAbove();
-                    if (mBackgroundNetworkRestricted) {
-                        // Firewall rules and UidBlockedState will get updated in
-                        // updateRulesForGlobalChangeAL below.
-                        enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true);
-                    }
-
+                    enableFirewallChainUL(FIREWALL_CHAIN_BACKGROUND, true);
                     setRestrictBackgroundUL(mLoadedRestrictBackground, "init_service");
                     updateRulesForGlobalChangeAL(false);
                     updateNotificationsNL();
@@ -1135,11 +1122,8 @@
                 final int changes = ActivityManager.UID_OBSERVER_PROCSTATE
                         | ActivityManager.UID_OBSERVER_GONE
                         | ActivityManager.UID_OBSERVER_CAPABILITY;
-
-                final int cutpoint = mBackgroundNetworkRestricted ? PROCESS_STATE_UNKNOWN
-                        : NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE;
                 mActivityManagerInternal.registerNetworkPolicyUidObserver(mUidObserver, changes,
-                        cutpoint, "android");
+                        PROCESS_STATE_UNKNOWN, "android");
                 mNetworkManager.registerObserver(mAlertObserver);
             } catch (RemoteException e) {
                 // ignored; both services live in system_server
@@ -1280,21 +1264,19 @@
                 // different chains may change.
                 return true;
             }
-            if (mBackgroundNetworkRestricted) {
-                if ((previousProcState >= BACKGROUND_THRESHOLD_STATE)
+            if ((previousProcState >= BACKGROUND_THRESHOLD_STATE)
                     != (newProcState >= BACKGROUND_THRESHOLD_STATE)) {
-                    // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: The network rules will
-                    // need to be re-evaluated for the background chain.
-                    return true;
-                }
-                if (mUseDifferentDelaysForBackgroundChain
-                        && newProcState >= BACKGROUND_THRESHOLD_STATE
-                        && getBackgroundTransitioningDelay(newProcState)
-                        < getBackgroundTransitioningDelay(previousProcState)) {
-                    // The old and new proc-state both are in the blocked state but the background
-                    // transition delay is reduced, so we may have to update the rules sooner.
-                    return true;
-                }
+                // Proc-state change crossed BACKGROUND_THRESHOLD_STATE: The network rules will
+                // need to be re-evaluated for the background chain.
+                return true;
+            }
+            if (mUseDifferentDelaysForBackgroundChain
+                    && newProcState >= BACKGROUND_THRESHOLD_STATE
+                    && getBackgroundTransitioningDelay(newProcState)
+                    < getBackgroundTransitioningDelay(previousProcState)) {
+                // The old and new proc-state both are in the blocked state but the background
+                // transition delay is reduced, so we may have to update the rules sooner.
+                return true;
             }
             final int networkCapabilities = PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
                     | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
@@ -1367,9 +1349,7 @@
             // on background handler thread, and POWER_SAVE_WHITELIST_CHANGED is protected
             synchronized (mUidRulesFirstLock) {
                 updatePowerSaveAllowlistUL();
-                if (mBackgroundNetworkRestricted) {
-                    updateRulesForBackgroundChainUL();
-                }
+                updateRulesForBackgroundChainUL();
                 updateRulesForRestrictPowerUL();
                 updateRulesForAppIdleUL();
             }
@@ -4100,8 +4080,6 @@
 
                 fout.println();
                 fout.println("Flags:");
-                fout.println(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE + ": "
-                        + mBackgroundNetworkRestricted);
                 fout.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": "
                         + mUseMeteredFirewallChains);
                 fout.println(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN + ": "
@@ -4251,36 +4229,34 @@
                     fout.decreaseIndent();
                 }
 
-                if (mBackgroundNetworkRestricted) {
+                fout.println();
+                if (mUseDifferentDelaysForBackgroundChain) {
+                    fout.print("Background restrictions short delay: ");
+                    TimeUtils.formatDuration(mBackgroundRestrictionShortDelayMs, fout);
                     fout.println();
-                    if (mUseDifferentDelaysForBackgroundChain) {
-                        fout.print("Background restrictions short delay: ");
-                        TimeUtils.formatDuration(mBackgroundRestrictionShortDelayMs, fout);
-                        fout.println();
 
-                        fout.print("Background restrictions long delay: ");
-                        TimeUtils.formatDuration(mBackgroundRestrictionLongDelayMs, fout);
-                        fout.println();
-                    }
-
-                    size = mBackgroundTransitioningUids.size();
-                    if (size > 0) {
-                        final long nowUptime = SystemClock.uptimeMillis();
-                        fout.println("Uids transitioning to background:");
-                        fout.increaseIndent();
-                        for (int i = 0; i < size; i++) {
-                            fout.print("UID=");
-                            fout.print(mBackgroundTransitioningUids.keyAt(i));
-                            fout.print(", ");
-                            TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i),
-                                    nowUptime, fout);
-                            fout.println();
-                        }
-                        fout.decreaseIndent();
-                    }
+                    fout.print("Background restrictions long delay: ");
+                    TimeUtils.formatDuration(mBackgroundRestrictionLongDelayMs, fout);
                     fout.println();
                 }
 
+                size = mBackgroundTransitioningUids.size();
+                if (size > 0) {
+                    final long nowUptime = SystemClock.uptimeMillis();
+                    fout.println("Uids transitioning to background:");
+                    fout.increaseIndent();
+                    for (int i = 0; i < size; i++) {
+                        fout.print("UID=");
+                        fout.print(mBackgroundTransitioningUids.keyAt(i));
+                        fout.print(", ");
+                        TimeUtils.formatDuration(mBackgroundTransitioningUids.valueAt(i),
+                                nowUptime, fout);
+                        fout.println();
+                    }
+                    fout.decreaseIndent();
+                }
+                fout.println();
+
                 final SparseBooleanArray knownUids = new SparseBooleanArray();
                 collectKeys(mUidState, knownUids);
                 synchronized (mUidBlockedState) {
@@ -4465,51 +4441,49 @@
                     }
                     updatePowerRestrictionRules = true;
                 }
-                if (mBackgroundNetworkRestricted) {
-                    final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground(
-                            oldUidState);
-                    final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState);
-                    if (!wasAllowed && isAllowed) {
-                        mBackgroundTransitioningUids.delete(uid);
-                        updateRuleForBackgroundUL(uid);
-                        updatePowerRestrictionRules = true;
-                    } else if (!isAllowed) {
-                        final int transitionIdx = mBackgroundTransitioningUids.indexOfKey(uid);
-                        final long completionTimeMs = SystemClock.uptimeMillis()
-                                + getBackgroundTransitioningDelay(procState);
-                        boolean completionTimeUpdated = false;
-                        if (wasAllowed) {
-                            // Rules need to transition from allowed to blocked after the respective
-                            // delay.
-                            if (transitionIdx < 0) {
-                                // This is just a defensive check in case the upstream code ever
-                                // makes multiple calls for the same process state change.
-                                mBackgroundTransitioningUids.put(uid, completionTimeMs);
-                                completionTimeUpdated = true;
-                            }
-                        } else if (mUseDifferentDelaysForBackgroundChain) {
-                            // wasAllowed was false, but the transition delay may have reduced.
-                            // Currently, this can happen when the uid transitions from
-                            // LAST_ACTIVITY to CACHED_ACTIVITY, for example.
-                            if (transitionIdx >= 0
-                                    && completionTimeMs < mBackgroundTransitioningUids.valueAt(
-                                    transitionIdx)) {
-                                mBackgroundTransitioningUids.setValueAt(transitionIdx,
-                                        completionTimeMs);
-                                completionTimeUpdated = true;
-                            }
+                final boolean wasAllowed = isProcStateAllowedNetworkWhileBackground(
+                        oldUidState);
+                final boolean isAllowed = isProcStateAllowedNetworkWhileBackground(newUidState);
+                if (!wasAllowed && isAllowed) {
+                    mBackgroundTransitioningUids.delete(uid);
+                    updateRuleForBackgroundUL(uid);
+                    updatePowerRestrictionRules = true;
+                } else if (!isAllowed) {
+                    final int transitionIdx = mBackgroundTransitioningUids.indexOfKey(uid);
+                    final long completionTimeMs = SystemClock.uptimeMillis()
+                            + getBackgroundTransitioningDelay(procState);
+                    boolean completionTimeUpdated = false;
+                    if (wasAllowed) {
+                        // Rules need to transition from allowed to blocked after the respective
+                        // delay.
+                        if (transitionIdx < 0) {
+                            // This is just a defensive check in case the upstream code ever
+                            // makes multiple calls for the same process state change.
+                            mBackgroundTransitioningUids.put(uid, completionTimeMs);
+                            completionTimeUpdated = true;
                         }
-                        if (completionTimeUpdated
-                                && completionTimeMs < mNextProcessBackgroundUidsTime) {
-                            // Many uids may be in this "transitioning" state at the same time,
-                            // so we always keep one message to process transition completion at
-                            // the earliest time.
-                            mHandler.removeMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS);
-                            mHandler.sendEmptyMessageAtTime(
-                                    MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs);
-                            mNextProcessBackgroundUidsTime = completionTimeMs;
+                    } else if (mUseDifferentDelaysForBackgroundChain) {
+                        // wasAllowed was false, but the transition delay may have reduced.
+                        // Currently, this can happen when the uid transitions from
+                        // LAST_ACTIVITY to CACHED_ACTIVITY, for example.
+                        if (transitionIdx >= 0
+                                && completionTimeMs < mBackgroundTransitioningUids.valueAt(
+                                transitionIdx)) {
+                            mBackgroundTransitioningUids.setValueAt(transitionIdx,
+                                    completionTimeMs);
+                            completionTimeUpdated = true;
                         }
                     }
+                    if (completionTimeUpdated
+                            && completionTimeMs < mNextProcessBackgroundUidsTime) {
+                        // Many uids may be in this "transitioning" state at the same time,
+                        // so we always keep one message to process transition completion at
+                        // the earliest time.
+                        mHandler.removeMessages(MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS);
+                        mHandler.sendEmptyMessageAtTime(
+                                MSG_PROCESS_BACKGROUND_TRANSITIONING_UIDS, completionTimeMs);
+                        mNextProcessBackgroundUidsTime = completionTimeMs;
+                    }
                 }
                 if (mLowPowerStandbyActive) {
                     boolean allowedInLpsChanged =
@@ -4545,12 +4519,10 @@
                 if (mRestrictPower) {
                     updateRuleForRestrictPowerUL(uid);
                 }
-                if (mBackgroundNetworkRestricted) {
-                    // Uid is no longer running, there is no point in any grace period of network
-                    // access during transitions to lower importance proc-states.
-                    mBackgroundTransitioningUids.delete(uid);
-                    updateRuleForBackgroundUL(uid);
-                }
+                // Uid is no longer running, there is no point in any grace period of network
+                // access during transitions to lower importance proc-states.
+                mBackgroundTransitioningUids.delete(uid);
+                updateRuleForBackgroundUL(uid);
                 updateRulesForPowerRestrictionsUL(uid);
                 if (mLowPowerStandbyActive) {
                     updateRuleForLowPowerStandbyUL(uid);
@@ -5021,9 +4993,7 @@
                     "updateRulesForGlobalChangeAL: " + (restrictedNetworksChanged ? "R" : "-"));
         }
         try {
-            if (mBackgroundNetworkRestricted) {
-                updateRulesForBackgroundChainUL();
-            }
+            updateRulesForBackgroundChainUL();
             updateRulesForAppIdleUL();
             updateRulesForRestrictPowerUL();
             updateRulesForRestrictBackgroundUL();
@@ -5183,9 +5153,7 @@
             updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
             updateRuleForDeviceIdleUL(uid);
             updateRuleForRestrictPowerUL(uid);
-            if (mBackgroundNetworkRestricted) {
-                updateRuleForBackgroundUL(uid);
-            }
+            updateRuleForBackgroundUL(uid);
             // Update internal rules.
             updateRulesForPowerRestrictionsUL(uid);
         }
@@ -5358,9 +5326,7 @@
         updateRuleForDeviceIdleUL(uid);
         updateRuleForAppIdleUL(uid, PROCESS_STATE_UNKNOWN);
         updateRuleForRestrictPowerUL(uid);
-        if (mBackgroundNetworkRestricted) {
-            updateRuleForBackgroundUL(uid);
-        }
+        updateRuleForBackgroundUL(uid);
 
         // If the uid has the necessary permissions, then it should be added to the restricted mode
         // firewall allowlist.
@@ -5611,7 +5577,7 @@
             newBlockedReasons |= (mLowPowerStandbyActive ? BLOCKED_REASON_LOW_POWER_STANDBY : 0);
             newBlockedReasons |= (isUidIdle ? BLOCKED_REASON_APP_STANDBY : 0);
             newBlockedReasons |= (uidBlockedState.blockedReasons & BLOCKED_REASON_RESTRICTED_MODE);
-            newBlockedReasons |= mBackgroundNetworkRestricted ? BLOCKED_REASON_APP_BACKGROUND : 0;
+            newBlockedReasons |= BLOCKED_REASON_APP_BACKGROUND;
 
             newAllowedReasons |= (isSystem(uid) ? ALLOWED_REASON_SYSTEM : 0);
             newAllowedReasons |= (isForeground ? ALLOWED_REASON_FOREGROUND : 0);
@@ -5624,8 +5590,7 @@
                     & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
             newAllowedReasons |= (isAllowlistedFromLowPowerStandbyUL(uid))
                     ? ALLOWED_REASON_LOW_POWER_STANDBY_ALLOWLIST : 0;
-            newAllowedReasons |= (mBackgroundNetworkRestricted
-                    && isUidExemptFromBackgroundRestrictions(uid))
+            newAllowedReasons |= isUidExemptFromBackgroundRestrictions(uid)
                     ? ALLOWED_REASON_NOT_IN_BACKGROUND : 0;
 
             uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig
index 7f04e66..3c0ff61 100644
--- a/services/core/java/com/android/server/net/flags.aconfig
+++ b/services/core/java/com/android/server/net/flags.aconfig
@@ -2,13 +2,6 @@
 container: "system"
 
 flag {
-    name: "network_blocked_for_top_sleeping_and_above"
-    namespace: "backstage_power"
-    description: "Block network access for apps in a low importance background state"
-    bug: "304347838"
-}
-
-flag {
     name: "use_metered_firewall_chains"
     namespace: "backstage_power"
     description: "Use metered firewall chains to control access to metered networks"
diff --git a/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
new file mode 100644
index 0000000..8ec7160
--- /dev/null
+++ b/services/core/java/com/android/server/os/instrumentation/DynamicInstrumentationManagerService.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.os.instrumentation;
+
+import static android.Manifest.permission.DYNAMIC_INSTRUMENTATION;
+import static android.content.Context.DYNAMIC_INSTRUMENTATION_SERVICE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.PermissionManuallyEnforced;
+import android.content.Context;
+import android.os.instrumentation.ExecutableMethodFileOffsets;
+import android.os.instrumentation.IDynamicInstrumentationManager;
+import android.os.instrumentation.MethodDescriptor;
+import android.os.instrumentation.TargetProcess;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import dalvik.system.VMDebug;
+
+import java.lang.reflect.Method;
+
+/**
+ * System private implementation of the {@link IDynamicInstrumentationManager interface}.
+ */
+public class DynamicInstrumentationManagerService extends SystemService {
+    public DynamicInstrumentationManagerService(@NonNull Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(DYNAMIC_INSTRUMENTATION_SERVICE, new BinderService());
+    }
+
+    private final class BinderService extends IDynamicInstrumentationManager.Stub {
+        @Override
+        @PermissionManuallyEnforced
+        public @Nullable ExecutableMethodFileOffsets getExecutableMethodFileOffsets(
+                @NonNull TargetProcess targetProcess, @NonNull MethodDescriptor methodDescriptor) {
+            if (!com.android.art.flags.Flags.executableMethodFileOffsets()) {
+                throw new UnsupportedOperationException();
+            }
+            getContext().enforceCallingOrSelfPermission(
+                    DYNAMIC_INSTRUMENTATION, "Caller must have DYNAMIC_INSTRUMENTATION permission");
+
+            if (targetProcess.processName == null
+                    || !targetProcess.processName.equals("system_server")) {
+                throw new UnsupportedOperationException(
+                        "system_server is the only supported target process");
+            }
+
+            Method method = parseMethodDescriptor(
+                    getClass().getClassLoader(), methodDescriptor);
+            VMDebug.ExecutableMethodFileOffsets location =
+                    VMDebug.getExecutableMethodFileOffsets(method);
+
+            if (location == null) {
+                return null;
+            }
+
+            ExecutableMethodFileOffsets ret = new ExecutableMethodFileOffsets();
+            ret.containerPath = location.getContainerPath();
+            ret.containerOffset = location.getContainerOffset();
+            ret.methodOffset = location.getMethodOffset();
+            return ret;
+        }
+    }
+
+    @VisibleForTesting
+    static Method parseMethodDescriptor(ClassLoader classLoader,
+            @NonNull MethodDescriptor descriptor) {
+        try {
+            Class<?> javaClass = classLoader.loadClass(descriptor.fullyQualifiedClassName);
+            Class<?>[] parameters = new Class[descriptor.fullyQualifiedParameters.length];
+            for (int i = 0; i < descriptor.fullyQualifiedParameters.length; i++) {
+                String typeName = descriptor.fullyQualifiedParameters[i];
+                boolean isArrayType = typeName.endsWith("[]");
+                if (isArrayType) {
+                    typeName = typeName.substring(0, typeName.length() - 2);
+                }
+                switch (typeName) {
+                    case "boolean":
+                        parameters[i] = isArrayType ? boolean.class.arrayType() : boolean.class;
+                        break;
+                    case "byte":
+                        parameters[i] = isArrayType ? byte.class.arrayType() : byte.class;
+                        break;
+                    case "char":
+                        parameters[i] = isArrayType ? char.class.arrayType() : char.class;
+                        break;
+                    case "short":
+                        parameters[i] = isArrayType ? short.class.arrayType() : short.class;
+                        break;
+                    case "int":
+                        parameters[i] = isArrayType ? int.class.arrayType() : int.class;
+                        break;
+                    case "long":
+                        parameters[i] = isArrayType ? long.class.arrayType() : long.class;
+                        break;
+                    case "float":
+                        parameters[i] = isArrayType ? float.class.arrayType() : float.class;
+                        break;
+                    case "double":
+                        parameters[i] = isArrayType ? double.class.arrayType() : double.class;
+                        break;
+                    default:
+                        parameters[i] = isArrayType ? classLoader.loadClass(typeName).arrayType()
+                                : classLoader.loadClass(typeName);
+                }
+            }
+
+            return javaClass.getDeclaredMethod(descriptor.methodName, parameters);
+        } catch (ClassNotFoundException | NoSuchMethodException e) {
+            throw new IllegalArgumentException(
+                    "The specified method cannot be found. Is this descriptor valid? "
+                            + descriptor, e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index af2bb17..d538bb8 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -265,6 +265,7 @@
 
         @Override
         public void handleMessage(Message msg) {
+            Slog.d(TAG, "Package event received: " + msg.what);
             switch (msg.what) {
                 case MSG_USAGE_EVENT_RECEIVED:
                     mService.handleUsageEvent(
@@ -326,6 +327,8 @@
             return;
         }
 
+        Slog.d(TAG, "handlePackageAdd: adding " + packageName + " from "
+            + userId + " and notifying callbacks");
         initBackgroundInstalledPackages();
         mBackgroundInstalledPackages.add(userId, packageName);
         mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_INSTALL);
@@ -364,7 +367,11 @@
     // ADB sets installerPackageName to null, this creates a loophole to bypass BIC which will be
     // addressed with b/265203007
     private boolean installedByAdb(String initiatingPackageName) {
-        return PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName);
+        if(PackageManagerServiceUtils.isInstalledByAdb(initiatingPackageName)) {
+            Slog.d(TAG, "handlePackageAdd: is installed by ADB, skipping");
+            return true;
+        }
+        return false;
     }
 
     private boolean wasForegroundInstallation(
@@ -407,6 +414,7 @@
         if (mBackgroundInstalledPackages.contains(userId, packageName)) {
             mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
         }
+        Slog.d(TAG, "handlePackageRemove: removing " + packageName + " from " + userId);
         mBackgroundInstalledPackages.remove(userId, packageName);
         writeBackgroundInstalledPackagesToDisk();
     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 5fc3e33..05bc69a 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1015,7 +1015,8 @@
                     permission, attributionSource, message, forDataDelivery, startDataDelivery,
                     fromDatasource, attributedOp);
             // Finish any started op if some step in the attribution chain failed.
-            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+            if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
+                    && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
                 if (attributedOp == AppOpsManager.OP_NONE) {
                     finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
                             attributionSource.asState(), fromDatasource);
@@ -1244,6 +1245,7 @@
             final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE;
             AttributionSource current = attributionSource;
             AttributionSource next = null;
+            AttributionSource prev = null;
             // We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
             // every attributionSource in the chain is registered with the system.
             final boolean isChainStartTrusted = !hasChain || checkPermission(context,
@@ -1310,6 +1312,22 @@
                         selfAccess, singleReceiverFromDatasource, attributedOp,
                         proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
 
+                if (startDataDelivery && opMode != AppOpsManager.MODE_ALLOWED) {
+                    // Current failed the perm check, so if we are part-way through an attr chain,
+                    // we need to clean up the already started proxy op higher up the chain.  Note,
+                    // proxy ops are verified two by two, which means we have to clear the 2nd next
+                    // from the previous iteration (since it is actually curr.next which failed
+                    // to pass the perm check).
+                    if (prev != null) {
+                        final var cutAttrSourceState = prev.asState();
+                        if (cutAttrSourceState.next.length > 0) {
+                            cutAttrSourceState.next[0].next = new AttributionSourceState[0];
+                        }
+                        finishDataDelivery(context, attributedOp,
+                                cutAttrSourceState, fromDatasource);
+                    }
+                }
+
                 switch (opMode) {
                     case AppOpsManager.MODE_ERRORED: {
                         if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
@@ -1335,6 +1353,8 @@
                     return PermissionChecker.PERMISSION_GRANTED;
                 }
 
+                // an attribution we have already possibly started an op for
+                prev = current;
                 current = next;
             }
         }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 19406b4..845798f 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -83,7 +83,7 @@
 import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
 import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
 
-import static com.android.hardware.input.Flags.emojiAndScreenshotKeycodesAvailable;
+import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
 import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
 import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
 import static com.android.hardware.input.Flags.modifierShortcutDump;
@@ -3993,10 +3993,14 @@
                     return true;
                 }
             case KeyEvent.KEYCODE_SCREENSHOT:
-                if (emojiAndScreenshotKeycodesAvailable() && down && repeatCount == 0) {
+                if (firstDown) {
                     interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
                 }
                 return true;
+            case KeyEvent.KEYCODE_DO_NOT_DISTURB:
+            case KeyEvent.KEYCODE_LOCK:
+            case KeyEvent.KEYCODE_FULLSCREEN:
+                return true;
         }
         if (isValidGlobalKey(keyCode)
                 && mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -5666,9 +5670,23 @@
             case KeyEvent.KEYCODE_MACRO_4:
                 result &= ~ACTION_PASS_TO_USER;
                 break;
-            case KeyEvent.KEYCODE_EMOJI_PICKER:
-                if (!emojiAndScreenshotKeycodesAvailable()) {
-                    // Don't allow EMOJI_PICKER key to be dispatched until flag is released.
+            case KeyEvent.KEYCODE_DICTATE:
+            case KeyEvent.KEYCODE_NEW:
+            case KeyEvent.KEYCODE_CLOSE:
+            case KeyEvent.KEYCODE_PRINT:
+            case KeyEvent.KEYCODE_F13:
+            case KeyEvent.KEYCODE_F14:
+            case KeyEvent.KEYCODE_F15:
+            case KeyEvent.KEYCODE_F16:
+            case KeyEvent.KEYCODE_F17:
+            case KeyEvent.KEYCODE_F18:
+            case KeyEvent.KEYCODE_F19:
+            case KeyEvent.KEYCODE_F20:
+            case KeyEvent.KEYCODE_F21:
+            case KeyEvent.KEYCODE_F22:
+            case KeyEvent.KEYCODE_F23:
+            case KeyEvent.KEYCODE_F24:
+                if (!enableNew25q2Keycodes()) {
                     result &= ~ACTION_PASS_TO_USER;
                 }
                 break;
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 677a2de..028ac57 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -303,6 +303,8 @@
     private final GnssPowerStatsCollector mGnssPowerStatsCollector;
     private final CustomEnergyConsumerPowerStatsCollector mCustomEnergyConsumerPowerStatsCollector;
     private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray();
+    private boolean mMoveWscLoggingToNotifierEnabled = false;
+
     private ScreenPowerStatsCollector.ScreenUsageTimeRetriever mScreenUsageTimeRetriever =
             new ScreenPowerStatsCollector.ScreenUsageTimeRetriever() {
 
@@ -5155,7 +5157,7 @@
 
             Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
             uidStats.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
-            if (!mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()) {
+            if (!mMoveWscLoggingToNotifierEnabled) {
                 mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
                         uidStats.mProcessState, true /* acquired */,
                         getPowerManagerWakeLockLevel(type));
@@ -5206,7 +5208,7 @@
             Uid uidStats = getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs);
             uidStats.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
 
-            if (!mPowerManagerFlags.isMoveWscLoggingToNotifierEnabled()) {
+            if (!mMoveWscLoggingToNotifierEnabled) {
                 mFrameworkStatsLogger.wakelockStateChanged(mapIsolatedUid(uid), wc, name,
                         uidStats.mProcessState, false/* acquired */,
                         getPowerManagerWakeLockLevel(type));
@@ -15975,6 +15977,15 @@
         }
     }
 
+    /**
+     * Controls where the logging of the WakelockStateChanged atom occurs:
+     *   true = Notifier, false = BatteryStatsImpl.
+     */
+    public void setMoveWscLoggingToNotifierEnabled(boolean enabled) {
+        synchronized (this) {
+            mMoveWscLoggingToNotifierEnabled = enabled;
+        }
+    }
     @GuardedBy("this")
     public void systemServicesReady(Context context) {
         mConstants.startObserving(context.getContentResolver());
diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
similarity index 96%
rename from services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
rename to services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
index 9398c7a..b129fdc 100644
--- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
+++ b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.adaptiveauth;
+package com.android.server.security.adaptiveauthentication;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
 
@@ -55,8 +55,8 @@
 /**
  * @hide
  */
-public class AdaptiveAuthService extends SystemService {
-    private static final String TAG = "AdaptiveAuthService";
+public class AdaptiveAuthenticationService extends SystemService {
+    private static final String TAG = "AdaptiveAuthenticationService";
     private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
 
     @VisibleForTesting
@@ -78,12 +78,12 @@
     final SparseIntArray mFailedAttemptsForUser = new SparseIntArray();
     private final SparseLongArray mLastLockedTimestamp = new SparseLongArray();
 
-    public AdaptiveAuthService(Context context) {
+    public AdaptiveAuthenticationService(Context context) {
         this(context, new LockPatternUtils(context));
     }
 
     @VisibleForTesting
-    public AdaptiveAuthService(Context context, LockPatternUtils lockPatternUtils) {
+    public AdaptiveAuthenticationService(Context context, LockPatternUtils lockPatternUtils) {
         super(context);
         mLockPatternUtils = lockPatternUtils;
         mLockSettings = Objects.requireNonNull(
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
similarity index 100%
rename from services/core/java/com/android/server/adaptiveauth/OWNERS
rename to services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index 6ce8685..9213d96 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -74,10 +74,6 @@
         return mFeatureFlags;
     }
 
-    public boolean isFlagSafeModeTimeoutConfigEnabled() {
-        return mFeatureFlags.safeModeTimeoutConfig();
-    }
-
     /**
      * Verifies that the caller is running on the VcnContext Thread.
      *
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 2d3bc84..2325f35 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1263,7 +1263,7 @@
         final PersistableBundleWrapper carrierConfig = snapshot.getCarrierConfigForSubGrp(subGrp);
         int resultSeconds = defaultSeconds;
 
-        if (vcnContext.isFlagSafeModeTimeoutConfigEnabled() && carrierConfig != null) {
+        if (carrierConfig != null) {
             resultSeconds =
                     carrierConfig.getInt(
                             VcnManager.VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY, defaultSeconds);
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
index df44e50..a92ac67 100644
--- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java
@@ -45,6 +45,7 @@
         void onExternalVibrationReleased(long vibrationId);
     }
 
+    private final long mSessionId = VibrationSession.nextSessionId();
     private final ExternalVibration mExternalVibration;
     private final ExternalVibrationScale mScale = new ExternalVibrationScale();
     private final VibratorManagerHooks mManagerHooks;
@@ -65,6 +66,11 @@
     }
 
     @Override
+    public long getSessionId() {
+        return mSessionId;
+    }
+
+    @Override
     public long getCreateUptimeMillis() {
         return stats.getCreateUptimeMillis();
     }
@@ -148,7 +154,12 @@
 
     @Override
     public void notifySyncedVibratorsCallback(long vibrationId) {
-        // ignored, external control does not expect callbacks from the vibrator manager
+        // ignored, external control does not expect callbacks from the vibrator manager for sync
+    }
+
+    @Override
+    public void notifySessionCallback() {
+        // ignored, external control does not expect callbacks from the vibrator manager for session
     }
 
     boolean isHoldingSameVibration(ExternalVibration vib) {
@@ -174,7 +185,8 @@
     @Override
     public String toString() {
         return "ExternalVibrationSession{"
-                + "id=" + id
+                + "sessionId=" + mSessionId
+                + ", vibrationId=" + id
                 + ", callerInfo=" + callerInfo
                 + ", externalVibration=" + mExternalVibration
                 + ", scale=" + mScale
diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
index 67ba25f..628221b 100644
--- a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java
@@ -35,6 +35,7 @@
     private static final String TAG = "SingleVibrationSession";
 
     private final Object mLock = new Object();
+    private final long mSessionId = VibrationSession.nextSessionId();
     private final IBinder mCallerToken;
     private final HalVibration mVibration;
 
@@ -58,6 +59,11 @@
     }
 
     @Override
+    public long getSessionId() {
+        return mSessionId;
+    }
+
+    @Override
     public long getCreateUptimeMillis() {
         return mVibration.stats.getCreateUptimeMillis();
     }
@@ -155,9 +161,15 @@
     }
 
     @Override
+    public void notifySessionCallback() {
+        // ignored, external control does not expect callbacks from the vibrator manager for session
+    }
+
+    @Override
     public String toString() {
         return "SingleVibrationSession{"
-                + "callerToken= " + mCallerToken
+                + "sessionId= " + mSessionId
+                + ", callerToken= " + mCallerToken
                 + ", vibration=" + mVibration
                 + '}';
     }
diff --git a/services/core/java/com/android/server/vibrator/VendorVibrationSession.java b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
new file mode 100644
index 0000000..07478e3
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VendorVibrationSession.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.android.server.vibrator.VibrationSession.DebugInfo.formatTime;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
+import android.os.CancellationSignal;
+import android.os.CombinedVibration;
+import android.os.ExternalVibration;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.VibrationAttributes;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+/**
+ * A vibration session started by a vendor request that can trigger {@link CombinedVibration}.
+ */
+final class VendorVibrationSession extends IVibrationSession.Stub
+        implements VibrationSession, CancellationSignal.OnCancelListener, IBinder.DeathRecipient {
+    private static final String TAG = "VendorVibrationSession";
+
+    /** Calls into VibratorManager functionality needed for playing an {@link ExternalVibration}. */
+    interface VibratorManagerHooks {
+
+        /** Tells the manager to end the vibration session. */
+        void endSession(long sessionId, boolean shouldAbort);
+
+        /**
+         * Tells the manager that the vibration session is finished and the vibrators can now be
+         * used for another vibration.
+         */
+        void onSessionReleased(long sessionId);
+    }
+
+    private final Object mLock = new Object();
+    private final long mSessionId = VibrationSession.nextSessionId();
+    private final ICancellationSignal mCancellationSignal = CancellationSignal.createTransport();
+    private final int[] mVibratorIds;
+    private final long mCreateUptime;
+    private final long mCreateTime; // for debugging
+    private final IVibrationSessionCallback mCallback;
+    private final CallerInfo mCallerInfo;
+    private final VibratorManagerHooks mManagerHooks;
+    private final Handler mHandler;
+
+    @GuardedBy("mLock")
+    private Status mStatus = Status.RUNNING;
+    @GuardedBy("mLock")
+    private Status mEndStatusRequest;
+    @GuardedBy("mLock")
+    private long mStartTime; // for debugging
+    @GuardedBy("mLock")
+    private long mEndUptime;
+    @GuardedBy("mLock")
+    private long mEndTime; // for debugging
+
+    VendorVibrationSession(@NonNull CallerInfo callerInfo, @NonNull Handler handler,
+            @NonNull VibratorManagerHooks managerHooks, @NonNull int[] vibratorIds,
+            @NonNull IVibrationSessionCallback callback) {
+        mCreateUptime = SystemClock.uptimeMillis();
+        mCreateTime = System.currentTimeMillis();
+        mVibratorIds = vibratorIds;
+        mHandler = handler;
+        mCallback = callback;
+        mCallerInfo = callerInfo;
+        mManagerHooks = managerHooks;
+        CancellationSignal.fromTransport(mCancellationSignal).setOnCancelListener(this);
+    }
+
+    @Override
+    public void vibrate(CombinedVibration vibration, String reason) {
+        // TODO(b/345414356): implement vibration support
+        throw new UnsupportedOperationException("Vendor session vibrations not yet implemented");
+    }
+
+    @Override
+    public void finishSession() {
+        // Do not abort session in HAL, wait for ongoing vibration requests to complete.
+        // This might take a while to end the session, but it can be aborted by cancelSession.
+        requestEndSession(Status.FINISHED, /* shouldAbort= */ false);
+    }
+
+    @Override
+    public void cancelSession() {
+        // Always abort session in HAL while cancelling it.
+        // This might be triggered after finishSession was already called.
+        requestEndSession(Status.CANCELLED_BY_USER, /* shouldAbort= */ true);
+    }
+
+    @Override
+    public long getSessionId() {
+        return mSessionId;
+    }
+
+    @Override
+    public long getCreateUptimeMillis() {
+        return mCreateUptime;
+    }
+
+    @Override
+    public boolean isRepeating() {
+        return false;
+    }
+
+    @Override
+    public CallerInfo getCallerInfo() {
+        return mCallerInfo;
+    }
+
+    @Override
+    public IBinder getCallerToken() {
+        return mCallback.asBinder();
+    }
+
+    @Override
+    public DebugInfo getDebugInfo() {
+        synchronized (mLock) {
+            return new DebugInfoImpl(mStatus, mCallerInfo, mCreateUptime, mCreateTime, mStartTime,
+                    mEndUptime, mEndTime);
+        }
+    }
+
+    @Override
+    public boolean wasEndRequested() {
+        synchronized (mLock) {
+            return mEndStatusRequest != null;
+        }
+    }
+
+    @Override
+    public void onCancel() {
+        Slog.d(TAG, "Cancellation signal received, cancelling vibration session...");
+        requestEnd(Status.CANCELLED_BY_USER, /* endedBy= */ null, /* immediate= */ false);
+    }
+
+    @Override
+    public void binderDied() {
+        Slog.d(TAG, "Binder died, cancelling vibration session...");
+        requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false);
+    }
+
+    @Override
+    public boolean linkToDeath() {
+        try {
+            mCallback.asBinder().linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error linking session to token death", e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void unlinkToDeath() {
+        try {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        } catch (NoSuchElementException e) {
+            Slog.wtf(TAG, "Failed to unlink session to token death", e);
+        }
+    }
+
+    @Override
+    public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy,
+            boolean immediate) {
+        // All requests to end a session should abort it to stop ongoing vibrations, even if
+        // immediate flag is false. Only the #finishSession API will not abort and wait for
+        // session vibrations to complete, which might take a long time.
+        requestEndSession(status, /* shouldAbort= */ true);
+    }
+
+    @Override
+    public void notifyVibratorCallback(int vibratorId, long vibrationId) {
+        // TODO(b/345414356): implement vibration support
+    }
+
+    @Override
+    public void notifySyncedVibratorsCallback(long vibrationId) {
+        // TODO(b/345414356): implement vibration support
+    }
+
+    @Override
+    public void notifySessionCallback() {
+        synchronized (mLock) {
+            // If end was not requested then the HAL has cancelled the session.
+            maybeSetEndRequestLocked(Status.CANCELLED_BY_UNKNOWN_REASON);
+            maybeSetStatusToRequestedLocked();
+        }
+        mManagerHooks.onSessionReleased(mSessionId);
+    }
+
+    @Override
+    public String toString() {
+        synchronized (mLock) {
+            return "createTime: " + formatTime(mCreateTime, /*includeDate=*/ true)
+                    + ", startTime: " + (mStartTime == 0 ? null : formatTime(mStartTime,
+                    /* includeDate= */ true))
+                    + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime,
+                    /* includeDate= */ true))
+                    + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+                    + ", callerInfo: " + mCallerInfo
+                    + ", vibratorIds: " + Arrays.toString(mVibratorIds);
+        }
+    }
+
+    public Status getStatus() {
+        synchronized (mLock) {
+            return mStatus;
+        }
+    }
+
+    public boolean isStarted() {
+        synchronized (mLock) {
+            return mStartTime > 0;
+        }
+    }
+
+    public boolean isEnded() {
+        synchronized (mLock) {
+            return mStatus != Status.RUNNING;
+        }
+    }
+
+    public int[] getVibratorIds() {
+        return mVibratorIds;
+    }
+
+    public ICancellationSignal getCancellationSignal() {
+        return mCancellationSignal;
+    }
+
+    public void notifyStart() {
+        boolean isAlreadyEnded = false;
+        synchronized (mLock) {
+            if (isEnded()) {
+                // Session already ended, skip start callbacks.
+                isAlreadyEnded = true;
+            } else {
+                mStartTime = System.currentTimeMillis();
+                // Run client callback in separate thread.
+                mHandler.post(() -> {
+                    try {
+                        mCallback.onStarted(this);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Error notifying vendor session started", e);
+                    }
+                });
+            }
+        }
+        if (isAlreadyEnded) {
+            // Session already ended, make sure we end it in the HAL.
+            mManagerHooks.endSession(mSessionId, /* shouldAbort= */ true);
+        }
+    }
+
+    private void requestEndSession(Status status, boolean shouldAbort) {
+        boolean shouldTriggerSessionHook = false;
+        synchronized (mLock) {
+            maybeSetEndRequestLocked(status);
+            if (isStarted()) {
+                // Always trigger session hook after it has started, in case new request aborts an
+                // already finishing session. Wait for HAL callback before actually ending here.
+                shouldTriggerSessionHook = true;
+            } else {
+                // Session did not start in the HAL, end it right away.
+                maybeSetStatusToRequestedLocked();
+            }
+        }
+        if (shouldTriggerSessionHook) {
+            mManagerHooks.endSession(mSessionId, shouldAbort);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void maybeSetEndRequestLocked(Status status) {
+        if (mEndStatusRequest != null) {
+            // End already requested, keep first requested status and time.
+            return;
+        }
+        mEndStatusRequest = status;
+        mEndTime = System.currentTimeMillis();
+        mEndUptime = SystemClock.uptimeMillis();
+        if (isStarted()) {
+            // Only trigger "finishing" callback if session started.
+            // Run client callback in separate thread.
+            mHandler.post(() -> {
+                try {
+                    mCallback.onFinishing();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error notifying vendor session is finishing", e);
+                }
+            });
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void maybeSetStatusToRequestedLocked() {
+        if (isEnded()) {
+            // End already set, keep first requested status and time.
+            return;
+        }
+        if (mEndStatusRequest == null) {
+            // No end status was requested, nothing to set.
+            return;
+        }
+        mStatus = mEndStatusRequest;
+        // Run client callback in separate thread.
+        final Status endStatus = mStatus;
+        mHandler.post(() -> {
+            try {
+                mCallback.onFinished(toSessionStatus(endStatus));
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error notifying vendor session is finishing", e);
+            }
+        });
+    }
+
+    @android.os.vibrator.VendorVibrationSession.Status
+    private static int toSessionStatus(Status status) {
+        // Exhaustive switch to cover all possible internal status.
+        return switch (status) {
+            case FINISHED
+                    -> android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS;
+            case IGNORED_UNSUPPORTED
+                    -> STATUS_UNSUPPORTED;
+            case CANCELLED_BINDER_DIED, CANCELLED_BY_APP_OPS, CANCELLED_BY_USER,
+                 CANCELLED_SUPERSEDED, CANCELLED_BY_FOREGROUND_USER, CANCELLED_BY_SCREEN_OFF,
+                 CANCELLED_BY_SETTINGS_UPDATE, CANCELLED_BY_UNKNOWN_REASON
+                    -> android.os.vibrator.VendorVibrationSession.STATUS_CANCELED;
+            case IGNORED_APP_OPS, IGNORED_BACKGROUND, IGNORED_FOR_EXTERNAL, IGNORED_FOR_ONGOING,
+                 IGNORED_FOR_POWER, IGNORED_FOR_SETTINGS, IGNORED_FOR_HIGHER_IMPORTANCE,
+                 IGNORED_FOR_RINGER_MODE, IGNORED_FROM_VIRTUAL_DEVICE, IGNORED_SUPERSEDED,
+                 IGNORED_MISSING_PERMISSION, IGNORED_ON_WIRELESS_CHARGER
+                    -> android.os.vibrator.VendorVibrationSession.STATUS_IGNORED;
+            case UNKNOWN, IGNORED_ERROR_APP_OPS, IGNORED_ERROR_CANCELLING, IGNORED_ERROR_SCHEDULING,
+                 IGNORED_ERROR_TOKEN, FORWARDED_TO_INPUT_DEVICES, FINISHED_UNEXPECTED, RUNNING
+                    -> android.os.vibrator.VendorVibrationSession.STATUS_UNKNOWN_ERROR;
+        };
+    }
+
+    /**
+     * Holds lightweight debug information about the session that could potentially be kept in
+     * memory for a long time for bugreport dumpsys operations.
+     *
+     * Since DebugInfo can be kept in memory for a long time, it shouldn't hold any references to
+     * potentially expensive or resource-linked objects, such as {@link IBinder}.
+     */
+    static final class DebugInfoImpl implements VibrationSession.DebugInfo {
+        private final Status mStatus;
+        private final CallerInfo mCallerInfo;
+
+        private final long mCreateUptime;
+        private final long mCreateTime;
+        private final long mStartTime;
+        private final long mEndTime;
+        private final long mDurationMs;
+
+        DebugInfoImpl(Status status, CallerInfo callerInfo, long createUptime, long createTime,
+                long startTime, long endUptime, long endTime) {
+            mStatus = status;
+            mCallerInfo = callerInfo;
+            mCreateUptime = createUptime;
+            mCreateTime = createTime;
+            mStartTime = startTime;
+            mEndTime = endTime;
+            mDurationMs = endUptime > 0 ? endUptime - createUptime : -1;
+        }
+
+        @Override
+        public Status getStatus() {
+            return mStatus;
+        }
+
+        @Override
+        public long getCreateUptimeMillis() {
+            return mCreateUptime;
+        }
+
+        @Override
+        public CallerInfo getCallerInfo() {
+            return mCallerInfo;
+        }
+
+        @Nullable
+        @Override
+        public Object getDumpAggregationKey() {
+            return null; // No aggregation.
+        }
+
+        @Override
+        public void logMetrics(VibratorFrameworkStatsLogger statsLogger) {
+        }
+
+        @Override
+        public void dump(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            proto.write(VibrationProto.END_TIME, mEndTime);
+            proto.write(VibrationProto.DURATION_MS, mDurationMs);
+            proto.write(VibrationProto.STATUS, mStatus.ordinal());
+
+            final long attrsToken = proto.start(VibrationProto.ATTRIBUTES);
+            final VibrationAttributes attrs = mCallerInfo.attrs;
+            proto.write(VibrationAttributesProto.USAGE, attrs.getUsage());
+            proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage());
+            proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags());
+            proto.end(attrsToken);
+
+            proto.end(token);
+        }
+
+        @Override
+        public void dump(IndentingPrintWriter pw) {
+            pw.println("VibrationSession:");
+            pw.increaseIndent();
+            pw.println("status = " + mStatus.name().toLowerCase(Locale.ROOT));
+            pw.println("durationMs = " + mDurationMs);
+            pw.println("createTime = " + formatTime(mCreateTime, /*includeDate=*/ true));
+            pw.println("startTime = " + formatTime(mStartTime, /*includeDate=*/ true));
+            pw.println("endTime = " + (mEndTime == 0 ? null
+                    : formatTime(mEndTime, /*includeDate=*/ true)));
+            pw.println("callerInfo = " + mCallerInfo);
+            pw.decreaseIndent();
+        }
+
+        @Override
+        public void dumpCompact(IndentingPrintWriter pw) {
+            // Follow pattern from Vibration.DebugInfoImpl for better debugging from dumpsys.
+            String timingsStr = String.format(Locale.ROOT,
+                    "%s | %8s | %20s | duration: %5dms | start: %12s | end: %12s",
+                    formatTime(mCreateTime, /*includeDate=*/ true),
+                    "session",
+                    mStatus.name().toLowerCase(Locale.ROOT),
+                    mDurationMs,
+                    mStartTime == 0 ? "" : formatTime(mStartTime, /*includeDate=*/ false),
+                    mEndTime == 0 ? "" : formatTime(mEndTime, /*includeDate=*/ false));
+            String paramStr = String.format(Locale.ROOT,
+                    " | flags: %4s | usage: %s",
+                    Long.toBinaryString(mCallerInfo.attrs.getFlags()),
+                    mCallerInfo.attrs.usageToString());
+            // Optional, most vibrations should not be defined via AudioAttributes
+            // so skip them to simplify the logs
+            String audioUsageStr =
+                    mCallerInfo.attrs.getOriginalAudioUsage() != AudioAttributes.USAGE_UNKNOWN
+                            ? " | audioUsage=" + AudioAttributes.usageToString(
+                            mCallerInfo.attrs.getOriginalAudioUsage())
+                            : "";
+            String callerStr = String.format(Locale.ROOT,
+                    " | %s (uid=%d, deviceId=%d) | reason: %s",
+                    mCallerInfo.opPkg, mCallerInfo.uid, mCallerInfo.deviceId, mCallerInfo.reason);
+            pw.println(timingsStr + paramStr + audioUsageStr + callerStr);
+        }
+
+        @Override
+        public String toString() {
+            return "createTime: " + formatTime(mCreateTime, /* includeDate= */ true)
+                    + ", startTime: " + formatTime(mStartTime, /* includeDate= */ true)
+                    + ", endTime: " + (mEndTime == 0 ? null : formatTime(mEndTime,
+                    /* includeDate= */ true))
+                    + ", durationMs: " + mDurationMs
+                    + ", status: " + mStatus.name().toLowerCase(Locale.ROOT)
+                    + ", callerInfo: " + mCallerInfo;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index bb2a17c..27f92b2 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -16,6 +16,8 @@
 
 package com.android.server.vibrator;
 
+import static com.android.server.vibrator.VibrationSession.DebugInfo.formatTime;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.media.AudioAttributes;
@@ -31,9 +33,6 @@
 import android.util.IndentingPrintWriter;
 import android.util.proto.ProtoOutputStream;
 
-import java.time.Instant;
-import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -42,11 +41,6 @@
  * The base class for all vibrations.
  */
 abstract class Vibration {
-    private static final DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern(
-            "HH:mm:ss.SSS");
-    private static final DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
-            "MM-dd HH:mm:ss.SSS");
-
     // Used to generate globally unique vibration ids.
     private static final AtomicInteger sNextVibrationId = new AtomicInteger(1); // 0 = no callback
 
@@ -399,12 +393,5 @@
             proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay());
             proto.end(token);
         }
-
-        private String formatTime(long timeInMillis, boolean includeDate) {
-            return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER)
-                    // Ensure timezone is retrieved at formatting time
-                    .withZone(ZoneId.systemDefault())
-                    .format(Instant.ofEpochMilli(timeInMillis));
-        }
     }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java
index b511ba8..ae95a70 100644
--- a/services/core/java/com/android/server/vibrator/VibrationSession.java
+++ b/services/core/java/com/android/server/vibrator/VibrationSession.java
@@ -25,7 +25,11 @@
 import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Represents a generic vibration session that plays one or more vibration requests.
@@ -39,6 +43,16 @@
  */
 interface VibrationSession {
 
+    // Used to generate globally unique session ids.
+    AtomicInteger sNextSessionId = new AtomicInteger(1); // 0 = no callback
+
+    static long nextSessionId() {
+        return sNextSessionId.getAndIncrement();
+    }
+
+    /** Returns the session id. */
+    long getSessionId();
+
     /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */
     long getCreateUptimeMillis();
 
@@ -105,6 +119,14 @@
     void notifySyncedVibratorsCallback(long vibrationId);
 
     /**
+     * Notify vibrator manager have completed the vibration session.
+     *
+     * <p>This will be called by the vibrator manager hardware callback indicating the session
+     * is complete, either because it was ended or cancelled by the service or the vendor.
+     */
+    void notifySessionCallback();
+
+    /**
      * Session status with reference to values from vibratormanagerservice.proto for logging.
      */
     enum Status {
@@ -212,6 +234,17 @@
      */
     interface DebugInfo {
 
+        DateTimeFormatter DEBUG_TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
+        DateTimeFormatter DEBUG_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(
+                "MM-dd HH:mm:ss.SSS");
+
+        static String formatTime(long timeInMillis, boolean includeDate) {
+            return (includeDate ? DEBUG_DATE_TIME_FORMATTER : DEBUG_TIME_FORMATTER)
+                    // Ensure timezone is retrieved at formatting time
+                    .withZone(ZoneId.systemDefault())
+                    .format(Instant.ofEpochMilli(timeInMillis));
+        }
+
         /** Return the vibration session status. */
         Status getStatus();
 
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index ff34911..4764481 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.hardware.vibrator.IVibrator;
+import android.hardware.vibrator.IVibratorManager;
 import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Build;
@@ -40,6 +41,7 @@
 import android.os.ExternalVibrationScale;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.ICancellationSignal;
 import android.os.IExternalVibratorService;
 import android.os.IVibratorManagerService;
 import android.os.IVibratorStateListener;
@@ -57,6 +59,7 @@
 import android.os.VibrationEffect;
 import android.os.VibratorInfo;
 import android.os.vibrator.Flags;
+import android.os.vibrator.IVibrationSessionCallback;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
@@ -103,7 +106,7 @@
     private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
     private static final String VIBRATOR_CONTROL_SERVICE =
             "android.frameworks.vibrator.IVibratorControlService/default";
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
     private static final int ATTRIBUTES_ALL_BYPASS_FLAGS =
@@ -159,12 +162,14 @@
             new VibrationThreadCallbacks();
     private final ExternalVibrationCallbacks mExternalVibrationCallbacks =
             new ExternalVibrationCallbacks();
+    private final VendorVibrationSessionCallbacks mVendorVibrationSessionCallbacks =
+            new VendorVibrationSessionCallbacks();
     @GuardedBy("mLock")
     private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>();
     @GuardedBy("mLock")
-    private VibrationSession mCurrentVibration;
+    private VibrationSession mCurrentSession;
     @GuardedBy("mLock")
-    private VibrationSession mNextVibration;
+    private VibrationSession mNextSession;
     @GuardedBy("mLock")
     private boolean mServiceReady;
 
@@ -191,14 +196,14 @@
                 // When the system is entering a non-interactive state, we want to cancel
                 // vibrations in case a misbehaving app has abandoned them.
                 synchronized (mLock) {
-                    maybeClearCurrentAndNextVibrationsLocked(
+                    maybeClearCurrentAndNextSessionsLocked(
                             VibratorManagerService.this::shouldCancelOnScreenOffLocked,
                             Status.CANCELLED_BY_SCREEN_OFF);
                 }
             } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
                     && intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
                 synchronized (mLock) {
-                    maybeClearCurrentAndNextVibrationsLocked(
+                    maybeClearCurrentAndNextSessionsLocked(
                             VibratorManagerService.this::shouldCancelOnFgUserRequest,
                             Status.CANCELLED_BY_FOREGROUND_USER);
                 }
@@ -215,14 +220,14 @@
                         return;
                     }
                     synchronized (mLock) {
-                        maybeClearCurrentAndNextVibrationsLocked(
+                        maybeClearCurrentAndNextSessionsLocked(
                                 VibratorManagerService.this::shouldCancelAppOpModeChangedLocked,
                                 Status.CANCELLED_BY_APP_OPS);
                     }
                 }
             };
 
-    static native long nativeInit(OnSyncedVibrationCompleteListener listener);
+    static native long nativeInit(VibratorManagerNativeCallbacks listener);
 
     static native long nativeGetFinalizer();
 
@@ -236,6 +241,13 @@
 
     static native void nativeCancelSynced(long nativeServicePtr);
 
+    static native boolean nativeStartSession(long nativeServicePtr, long sessionId,
+            int[] vibratorIds);
+
+    static native void nativeEndSession(long nativeServicePtr, long sessionId, boolean shouldAbort);
+
+    static native void nativeClearSessions(long nativeServicePtr);
+
     @VisibleForTesting
     VibratorManagerService(Context context, Injector injector) {
         mContext = context;
@@ -303,6 +315,9 @@
         // Reset the hardware to a default state, in case this is a runtime restart instead of a
         // fresh boot.
         mNativeWrapper.cancelSynced();
+        if (Flags.vendorVibrationEffects()) {
+            mNativeWrapper.clearSessions();
+        }
         for (int i = 0; i < mVibrators.size(); i++) {
             mVibrators.valueAt(i).reset();
         }
@@ -363,6 +378,11 @@
     }
 
     @Override // Binder call
+    public int getCapabilities() {
+        return (int) mCapabilities;
+    }
+
+    @Override // Binder call
     @Nullable
     public VibratorInfo getVibratorInfo(int vibratorId) {
         final VibratorController controller = mVibrators.get(vibratorId);
@@ -590,11 +610,17 @@
             logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_ERROR_TOKEN);
             return null;
         }
-        if (effect.hasVendorEffects()
-                && !hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
-            Slog.e(TAG, "vibrate; no permission for vendor effects");
-            logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION);
-            return null;
+        if (effect.hasVendorEffects()) {
+            if (!Flags.vendorVibrationEffects()) {
+                Slog.e(TAG, "vibrate; vendor effects feature disabled");
+                logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_UNSUPPORTED);
+                return null;
+            }
+            if (!hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
+                Slog.e(TAG, "vibrate; no permission for vendor effects");
+                logAndRecordVibrationAttempt(effect, callerInfo, Status.IGNORED_MISSING_PERMISSION);
+                return null;
+            }
         }
         enforceUpdateAppOpsStatsPermission(uid);
         if (!isEffectValid(effect)) {
@@ -623,7 +649,7 @@
 
             // Check if ongoing vibration is more important than this vibration.
             if (ignoreStatus == null) {
-                Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(session);
+                Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoingLocked(session);
                 if (vibrationEndInfo != null) {
                     ignoreStatus = vibrationEndInfo.status;
                     ignoredBy = vibrationEndInfo.endedBy;
@@ -634,8 +660,8 @@
             if (ignoreStatus == null) {
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    if (mCurrentVibration != null) {
-                        if (shouldPipelineVibrationLocked(mCurrentVibration, vib)) {
+                    if (mCurrentSession != null) {
+                        if (shouldPipelineVibrationLocked(mCurrentSession, vib)) {
                             // Don't cancel the current vibration if it's pipeline-able.
                             // Note that if there is a pending next vibration that can't be
                             // pipelined, it will have already cancelled the current one, so we
@@ -645,12 +671,12 @@
                             }
                         } else {
                             vib.stats.reportInterruptedAnotherVibration(
-                                    mCurrentVibration.getCallerInfo());
-                            mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
+                                    mCurrentSession.getCallerInfo());
+                            mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
                                     /* immediate= */ false);
                         }
                     }
-                    clearNextVibrationLocked(Status.CANCELLED_SUPERSEDED, callerInfo);
+                    clearNextSessionLocked(Status.CANCELLED_SUPERSEDED, callerInfo);
                     ignoreStatus = startVibrationLocked(session);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -659,7 +685,7 @@
 
             // Ignored or failed to start the vibration, end it and report metrics right away.
             if (ignoreStatus != null) {
-                endVibrationLocked(session, ignoreStatus, ignoredBy);
+                endSessionLocked(session, ignoreStatus, ignoredBy);
             }
             return vib;
         }
@@ -681,14 +707,14 @@
                 try {
                     // TODO(b/370948466): investigate why token not checked on external vibrations.
                     IBinder cancelToken =
-                            (mNextVibration instanceof ExternalVibrationSession) ? null : token;
-                    if (shouldCancelVibration(mNextVibration, usageFilter, cancelToken)) {
-                        clearNextVibrationLocked(Status.CANCELLED_BY_USER);
+                            (mNextSession instanceof ExternalVibrationSession) ? null : token;
+                    if (shouldCancelSession(mNextSession, usageFilter, cancelToken)) {
+                        clearNextSessionLocked(Status.CANCELLED_BY_USER);
                     }
                     cancelToken =
-                            (mCurrentVibration instanceof ExternalVibrationSession) ? null : token;
-                    if (shouldCancelVibration(mCurrentVibration, usageFilter, cancelToken)) {
-                        mCurrentVibration.requestEnd(Status.CANCELLED_BY_USER);
+                            (mCurrentSession instanceof ExternalVibrationSession) ? null : token;
+                    if (shouldCancelSession(mCurrentSession, usageFilter, cancelToken)) {
+                        mCurrentSession.requestEnd(Status.CANCELLED_BY_USER);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -699,6 +725,141 @@
         }
     }
 
+    @android.annotation.EnforcePermission(allOf = {
+            android.Manifest.permission.VIBRATE,
+            android.Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+            android.Manifest.permission.START_VIBRATION_SESSIONS,
+    })
+    @Override // Binder call
+    public ICancellationSignal startVendorVibrationSession(int uid, int deviceId, String opPkg,
+            int[] vibratorIds, VibrationAttributes attrs, String reason,
+            IVibrationSessionCallback callback) {
+        startVendorVibrationSession_enforcePermission();
+        Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationSession");
+        try {
+            VendorVibrationSession session = startVendorVibrationSessionInternal(
+                    uid, deviceId, opPkg, vibratorIds, attrs, reason, callback);
+            return session == null ? null : session.getCancellationSignal();
+        } finally {
+            Trace.traceEnd(TRACE_TAG_VIBRATOR);
+        }
+    }
+
+    VendorVibrationSession startVendorVibrationSessionInternal(int uid, int deviceId, String opPkg,
+            int[] vibratorIds, VibrationAttributes attrs, String reason,
+            IVibrationSessionCallback callback) {
+        if (!Flags.vendorVibrationEffects()) {
+            throw new UnsupportedOperationException("Vibration sessions not supported");
+        }
+        attrs = fixupVibrationAttributes(attrs, /* effect= */ null);
+        CallerInfo callerInfo = new CallerInfo(attrs, uid, deviceId, opPkg, reason);
+        if (callback == null) {
+            Slog.e(TAG, "session callback must not be null");
+            logAndRecordSessionAttempt(callerInfo, Status.IGNORED_ERROR_TOKEN);
+            return null;
+        }
+        if (vibratorIds == null) {
+            vibratorIds = new int[0];
+        }
+        enforceUpdateAppOpsStatsPermission(uid);
+        VendorVibrationSession session = new VendorVibrationSession(callerInfo, mHandler,
+                mVendorVibrationSessionCallbacks, vibratorIds, callback);
+
+        if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
+            // Force update of user settings before checking if this vibration effect should
+            // be ignored or scaled.
+            mVibrationSettings.update();
+        }
+
+        synchronized (mLock) {
+            if (DEBUG) {
+                Slog.d(TAG, "Starting session " + session.getSessionId());
+            }
+
+            Status ignoreStatus = null;
+            CallerInfo ignoredBy = null;
+
+            // Check if HAL has capability to start sessions.
+            if ((mCapabilities & IVibratorManager.CAP_START_SESSIONS) == 0) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Missing capability to start sessions, ignoring request");
+                }
+                ignoreStatus = Status.IGNORED_UNSUPPORTED;
+            }
+
+            // Check if any vibrator ID was requested.
+            if (ignoreStatus == null && vibratorIds.length == 0) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Empty vibrator ids to start session, ignoring request");
+                }
+                ignoreStatus = Status.IGNORED_UNSUPPORTED;
+            }
+
+            // Check if user settings or DnD is set to ignore this session.
+            if (ignoreStatus == null) {
+                ignoreStatus = shouldIgnoreVibrationLocked(callerInfo);
+            }
+
+            // Check if ongoing vibration is more important than this session.
+            if (ignoreStatus == null) {
+                Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoingLocked(session);
+                if (vibrationEndInfo != null) {
+                    ignoreStatus = vibrationEndInfo.status;
+                    ignoredBy = vibrationEndInfo.endedBy;
+                }
+            }
+
+            if (ignoreStatus == null) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    // If not ignored so far then stop ongoing sessions before starting this one.
+                    clearNextSessionLocked(Status.CANCELLED_SUPERSEDED, callerInfo);
+                    if (mCurrentSession != null) {
+                        mNextSession = session;
+                        mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo,
+                                /* immediate= */ false);
+                    } else {
+                        ignoreStatus = startVendorSessionLocked(session);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+
+            // Ignored or failed to start the session, end it and report metrics right away.
+            if (ignoreStatus != null) {
+                endSessionLocked(session, ignoreStatus, ignoredBy);
+            }
+            return session;
+        }
+    }
+
+    @GuardedBy("mLock")
+    @Nullable
+    private Status startVendorSessionLocked(VendorVibrationSession session) {
+        Trace.traceBegin(TRACE_TAG_VIBRATOR, "startSessionLocked");
+        try {
+            if (session.isEnded()) {
+                // Session already ended, possibly cancelled by app cancellation signal.
+                return session.getStatus();
+            }
+            if (!session.linkToDeath()) {
+                return Status.IGNORED_ERROR_TOKEN;
+            }
+            if (!mNativeWrapper.startSession(session.getSessionId(), session.getVibratorIds())) {
+                Slog.e(TAG, "Error starting session " + session.getSessionId()
+                        + " on vibrators " + Arrays.toString(session.getVibratorIds()));
+                session.unlinkToDeath();
+                return Status.IGNORED_UNSUPPORTED;
+            }
+            session.notifyStart();
+            mCurrentSession = session;
+            return null;
+        } finally {
+            Trace.traceEnd(TRACE_TAG_VIBRATOR);
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -747,8 +908,8 @@
 
             pw.println("CurrentVibration:");
             pw.increaseIndent();
-            if (mCurrentVibration != null) {
-                mCurrentVibration.getDebugInfo().dump(pw);
+            if (mCurrentSession != null) {
+                mCurrentSession.getDebugInfo().dump(pw);
             } else {
                 pw.println("null");
             }
@@ -757,8 +918,8 @@
 
             pw.println("NextVibration:");
             pw.increaseIndent();
-            if (mNextVibration != null) {
-                mNextVibration.getDebugInfo().dump(pw);
+            if (mNextSession != null) {
+                mNextSession.getDebugInfo().dump(pw);
             } else {
                 pw.println("null");
             }
@@ -782,8 +943,8 @@
         synchronized (mLock) {
             mVibrationSettings.dump(proto);
             mVibrationScaler.dump(proto);
-            if (mCurrentVibration != null) {
-                mCurrentVibration.getDebugInfo().dump(proto,
+            if (mCurrentSession != null) {
+                mCurrentSession.getDebugInfo().dump(proto,
                         VibratorManagerServiceDumpProto.CURRENT_VIBRATION);
             }
             for (int i = 0; i < mVibrators.size(); i++) {
@@ -816,18 +977,18 @@
             }
 
             // TODO(b/372241975): investigate why external vibrations were not handled here before
-            if (mCurrentVibration == null
-                    || (mCurrentVibration instanceof ExternalVibrationSession)) {
+            if (mCurrentSession == null
+                    || (mCurrentSession instanceof ExternalVibrationSession)) {
                 return;
             }
 
-            Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentVibration.getCallerInfo());
+            Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentSession.getCallerInfo());
             if (inputDevicesChanged || (ignoreStatus != null)) {
                 if (DEBUG) {
                     Slog.d(TAG, "Canceling vibration because settings changed: "
                             + (inputDevicesChanged ? "input devices changed" : ignoreStatus));
                 }
-                mCurrentVibration.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
+                mCurrentSession.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE);
             }
         }
     }
@@ -866,15 +1027,15 @@
             if (mInputDeviceDelegate.isAvailable()) {
                 return startVibrationOnInputDevicesLocked(session.getVibration());
             }
-            if (mCurrentVibration == null) {
+            if (mCurrentSession == null) {
                 return startVibrationOnThreadLocked(session);
             }
             // If there's already a vibration queued (waiting for the previous one to finish
             // cancelling), end it cleanly and replace it with the new one.
             // Note that we don't consider pipelining here, because new pipelined ones should
             // replace pending non-executing pipelined ones anyway.
-            clearNextVibrationLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo());
-            mNextVibration = session;
+            clearNextSessionLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo());
+            mNextSession = session;
             return null;
         } finally {
             Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -891,16 +1052,16 @@
             case AppOpsManager.MODE_ALLOWED:
                 Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0);
                 // Make sure mCurrentVibration is set while triggering the VibrationThread.
-                mCurrentVibration = session;
-                if (!mCurrentVibration.linkToDeath()) {
+                mCurrentSession = session;
+                if (!mCurrentSession.linkToDeath()) {
                     // Shouldn't happen. The method call already logs.
-                    mCurrentVibration = null;  // Aborted.
+                    mCurrentSession = null;  // Aborted.
                     return Status.IGNORED_ERROR_TOKEN;
                 }
                 if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) {
                     // Shouldn't happen. The method call already logs.
                     session.setVibrationConductor(null); // Rejected by thread, clear it in session.
-                    mCurrentVibration = null;  // Aborted.
+                    mCurrentSession = null;  // Aborted.
                     return Status.IGNORED_ERROR_SCHEDULING;
                 }
                 return null;
@@ -914,23 +1075,29 @@
     }
 
     @GuardedBy("mLock")
-    private void maybeStartNextSingleVibrationLocked() {
-        if (mNextVibration instanceof SingleVibrationSession session) {
-            mNextVibration = null;
+    private void maybeStartNextSessionLocked() {
+        if (mNextSession instanceof SingleVibrationSession session) {
+            mNextSession = null;
             Status errorStatus = startVibrationOnThreadLocked(session);
             if (errorStatus != null) {
-                endVibrationLocked(session, errorStatus);
+                endSessionLocked(session, errorStatus);
             }
-        }
+        } else if (mNextSession instanceof VendorVibrationSession session) {
+            mNextSession = null;
+            Status errorStatus = startVendorSessionLocked(session);
+            if (errorStatus != null) {
+                endSessionLocked(session, errorStatus);
+            }
+        } // External vibrations cannot be started asynchronously.
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(VibrationSession session, Status status) {
-        endVibrationLocked(session, status, /* endedBy= */ null);
+    private void endSessionLocked(VibrationSession session, Status status) {
+        endSessionLocked(session, status, /* endedBy= */ null);
     }
 
     @GuardedBy("mLock")
-    private void endVibrationLocked(VibrationSession session, Status status, CallerInfo endedBy) {
+    private void endSessionLocked(VibrationSession session, Status status, CallerInfo endedBy) {
         session.requestEnd(status, endedBy, /* immediate= */ false);
         logAndRecordVibration(session.getDebugInfo());
     }
@@ -975,6 +1142,13 @@
                         VibrationScaler.ADAPTIVE_SCALE_NONE));
     }
 
+    private void logAndRecordSessionAttempt(CallerInfo callerInfo, Status status) {
+        logAndRecordVibration(
+                new VendorVibrationSession.DebugInfoImpl(status, callerInfo,
+                        SystemClock.uptimeMillis(), System.currentTimeMillis(),
+                        /* startTime= */ 0, /* endUptime= */ 0, /* endTime= */ 0));
+    }
+
     private void logAndRecordVibration(DebugInfo info) {
         info.logMetrics(mFrameworkStatsLogger);
         logVibrationStatus(info.getCallerInfo().uid, info.getCallerInfo().attrs, info.getStatus());
@@ -1026,25 +1200,40 @@
         }
     }
 
+    private void onVibrationSessionComplete(long sessionId) {
+        synchronized (mLock) {
+            if (mCurrentSession == null || mCurrentSession.getSessionId() != sessionId) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Vibration session " + sessionId + " callback ignored");
+                }
+                return;
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Vibration session " + sessionId + " complete, notifying session");
+            }
+            mCurrentSession.notifySessionCallback();
+        }
+    }
+
     private void onSyncedVibrationComplete(long vibrationId) {
         synchronized (mLock) {
-            if (mCurrentVibration != null) {
+            if (mCurrentSession != null) {
                 if (DEBUG) {
                     Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread");
                 }
-                mCurrentVibration.notifySyncedVibratorsCallback(vibrationId);
+                mCurrentSession.notifySyncedVibratorsCallback(vibrationId);
             }
         }
     }
 
     private void onVibrationComplete(int vibratorId, long vibrationId) {
         synchronized (mLock) {
-            if (mCurrentVibration != null) {
+            if (mCurrentSession != null) {
                 if (DEBUG) {
                     Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId
                             + " complete, notifying thread");
                 }
-                mCurrentVibration.notifyVibratorCallback(vibratorId, vibrationId);
+                mCurrentSession.notifyVibratorCallback(vibratorId, vibrationId);
             }
         }
     }
@@ -1056,10 +1245,10 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(VibrationSession session) {
-        if (mNextVibration != null) {
-            Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(session,
-                    mNextVibration);
+    private Vibration.EndInfo shouldIgnoreForOngoingLocked(VibrationSession session) {
+        if (mNextSession != null) {
+            Vibration.EndInfo vibrationEndInfo = shouldIgnoreForOngoing(session,
+                    mNextSession);
             if (vibrationEndInfo != null) {
                 // Next vibration has higher importance than the new one, so the new vibration
                 // should be ignored.
@@ -1067,13 +1256,13 @@
             }
         }
 
-        if (mCurrentVibration != null) {
-            if (mCurrentVibration.wasEndRequested()) {
+        if (mCurrentSession != null) {
+            if (mCurrentSession.wasEndRequested()) {
                 // Current session has ended or is cancelling, should not block incoming vibrations.
                 return null;
             }
 
-            return shouldIgnoreVibrationForOngoing(session, mCurrentVibration);
+            return shouldIgnoreForOngoing(session, mCurrentSession);
         }
 
         return null;
@@ -1086,7 +1275,7 @@
      * @return a Vibration.EndInfo if the vibration should be ignored, null otherwise.
      */
     @Nullable
-    private static Vibration.EndInfo shouldIgnoreVibrationForOngoing(
+    private static Vibration.EndInfo shouldIgnoreForOngoing(
             @NonNull VibrationSession newSession, @NonNull VibrationSession ongoingSession) {
 
         int newSessionImportance = getVibrationImportance(newSession);
@@ -1214,11 +1403,15 @@
      * @param tokenFilter The binder token to identify the vibration origin. Only vibrations
      *                    started with the same token can be cancelled with it.
      */
-    private boolean shouldCancelVibration(@Nullable VibrationSession session, int usageFilter,
+    private boolean shouldCancelSession(@Nullable VibrationSession session, int usageFilter,
             @Nullable IBinder tokenFilter) {
         if (session == null) {
             return false;
         }
+        if (session instanceof VendorVibrationSession) {
+            // Vendor sessions should not be cancelled by Vibrator.cancel API.
+            return false;
+        }
         if ((tokenFilter != null) && (tokenFilter != session.getCallerToken())) {
             // Vibration from a different app, this should not cancel it.
             return false;
@@ -1572,10 +1765,10 @@
             Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVibrationThreadReleased");
             try {
                 synchronized (mLock) {
-                    if (!(mCurrentVibration instanceof SingleVibrationSession session)) {
+                    if (!(mCurrentSession instanceof SingleVibrationSession session)) {
                         if (Build.IS_DEBUGGABLE) {
                             Slog.wtf(TAG, "VibrationSession invalid on vibration thread release."
-                                    + " currentSession=" + mCurrentVibration);
+                                    + " currentSession=" + mCurrentSession);
                         }
                         // Only single vibration sessions are ended by thread being released. Abort.
                         return;
@@ -1586,11 +1779,11 @@
                                         + " expected=%d, released=%d",
                                 session.getVibration().id, vibrationId));
                     }
-                    finishAppOpModeLocked(mCurrentVibration.getCallerInfo());
-                    clearCurrentVibrationLocked();
+                    finishAppOpModeLocked(mCurrentSession.getCallerInfo());
+                    clearCurrentSessionLocked();
                     Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
-                    // Start next vibration if it's a single vibration waiting for the thread.
-                    maybeStartNextSingleVibrationLocked();
+                    // Start next vibration if it's waiting for the thread.
+                    maybeStartNextSessionLocked();
                 }
             } finally {
                 Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -1613,10 +1806,10 @@
             Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationReleased");
             try {
                 synchronized (mLock) {
-                    if (!(mCurrentVibration instanceof ExternalVibrationSession session)) {
+                    if (!(mCurrentSession instanceof ExternalVibrationSession session)) {
                         if (Build.IS_DEBUGGABLE) {
                             Slog.wtf(TAG, "VibrationSession invalid on external vibration release."
-                                    + " currentSession=" + mCurrentVibration);
+                                    + " currentSession=" + mCurrentSession);
                         }
                         // Only external vibration sessions are ended by this callback. Abort.
                         return;
@@ -1627,10 +1820,9 @@
                                         + " expected=%d, released=%d", session.id, vibrationId));
                     }
                     setExternalControl(false, session.stats);
-                    clearCurrentVibrationLocked();
-                    // Start next vibration if it's a single vibration waiting for the external
-                    // control to be over.
-                    maybeStartNextSingleVibrationLocked();
+                    clearCurrentSessionLocked();
+                    // Start next vibration if it's waiting for the external control to be over.
+                    maybeStartNextSessionLocked();
                 }
             } finally {
                 Trace.traceEnd(TRACE_TAG_VIBRATOR);
@@ -1638,19 +1830,75 @@
         }
     }
 
-    /** Listener for synced vibration completion callbacks from native. */
+    /**
+     * Implementation of {@link ExternalVibrationSession.VibratorManagerHooks} that controls
+     * external vibrations and reports them when finished.
+     */
+    private final class VendorVibrationSessionCallbacks
+            implements VendorVibrationSession.VibratorManagerHooks {
+
+        @Override
+        public void endSession(long sessionId, boolean shouldAbort) {
+            if (DEBUG) {
+                Slog.d(TAG, "Vibration session " + sessionId
+                        + (shouldAbort ? " aborting" : " ending"));
+            }
+            Trace.traceBegin(TRACE_TAG_VIBRATOR, "endSession");
+            try {
+                mNativeWrapper.endSession(sessionId, shouldAbort);
+            } finally {
+                Trace.traceEnd(TRACE_TAG_VIBRATOR);
+            }
+        }
+
+        @Override
+        public void onSessionReleased(long sessionId) {
+            if (DEBUG) {
+                Slog.d(TAG, "Vibration session " + sessionId + " released");
+            }
+            Trace.traceBegin(TRACE_TAG_VIBRATOR, "onVendorSessionReleased");
+            try {
+                synchronized (mLock) {
+                    if (!(mCurrentSession instanceof VendorVibrationSession session)) {
+                        if (Build.IS_DEBUGGABLE) {
+                            Slog.wtf(TAG, "VibrationSession invalid on vibration session release."
+                                    + " currentSession=" + mCurrentSession);
+                        }
+                        // Only vendor vibration sessions are ended by this callback. Abort.
+                        return;
+                    }
+                    if (Build.IS_DEBUGGABLE && (session.getSessionId() != sessionId)) {
+                        Slog.wtf(TAG, TextUtils.formatSimple(
+                                "SessionId mismatch on vendor vibration session release."
+                                        + " expected=%d, released=%d",
+                                session.getSessionId(), sessionId));
+                    }
+                    clearCurrentSessionLocked();
+                    // Start next vibration if it's waiting for the HAL session to be over.
+                    maybeStartNextSessionLocked();
+                }
+            } finally {
+                Trace.traceEnd(TRACE_TAG_VIBRATOR);
+            }
+        }
+    }
+
+    /** Listener for vibrator manager completion callbacks from native. */
     @VisibleForTesting
-    interface OnSyncedVibrationCompleteListener {
+    interface VibratorManagerNativeCallbacks {
 
         /** Callback triggered when synced vibration is complete. */
-        void onComplete(long vibrationId);
+        void onSyncedVibrationComplete(long vibrationId);
+
+        /** Callback triggered when vibration session is complete. */
+        void onVibrationSessionComplete(long sessionId);
     }
 
     /**
      * Implementation of listeners to native vibrators with a weak reference to this service.
      */
     private static final class VibrationCompleteListener implements
-            VibratorController.OnVibrationCompleteListener, OnSyncedVibrationCompleteListener {
+            VibratorController.OnVibrationCompleteListener, VibratorManagerNativeCallbacks {
         private WeakReference<VibratorManagerService> mServiceRef;
 
         VibrationCompleteListener(VibratorManagerService service) {
@@ -1658,7 +1906,7 @@
         }
 
         @Override
-        public void onComplete(long vibrationId) {
+        public void onSyncedVibrationComplete(long vibrationId) {
             VibratorManagerService service = mServiceRef.get();
             if (service != null) {
                 service.onSyncedVibrationComplete(vibrationId);
@@ -1666,6 +1914,14 @@
         }
 
         @Override
+        public void onVibrationSessionComplete(long sessionId) {
+            VibratorManagerService service = mServiceRef.get();
+            if (service != null) {
+                service.onVibrationSessionComplete(sessionId);
+            }
+        }
+
+        @Override
         public void onComplete(int vibratorId, long vibrationId) {
             VibratorManagerService service = mServiceRef.get();
             if (service != null) {
@@ -1698,7 +1954,7 @@
         private long mNativeServicePtr = 0;
 
         /** Returns native pointer to newly created controller and connects with HAL service. */
-        public void init(OnSyncedVibrationCompleteListener listener) {
+        public void init(VibratorManagerNativeCallbacks listener) {
             mNativeServicePtr = nativeInit(listener);
             long finalizerPtr = nativeGetFinalizer();
 
@@ -1734,6 +1990,21 @@
         public void cancelSynced() {
             nativeCancelSynced(mNativeServicePtr);
         }
+
+        /** Start vibration session. */
+        public boolean startSession(long sessionId, @NonNull int[] vibratorIds) {
+            return nativeStartSession(mNativeServicePtr, sessionId, vibratorIds);
+        }
+
+        /** End vibration session. */
+        public void endSession(long sessionId, boolean shouldAbort) {
+            nativeEndSession(mNativeServicePtr, sessionId, shouldAbort);
+        }
+
+        /** Clear vibration sessions. */
+        public void clearSessions() {
+            nativeClearSessions(mNativeServicePtr);
+        }
     }
 
     /** Keep records of vibrations played and provide debug information for this service. */
@@ -1853,46 +2124,46 @@
 
     /** Clears mNextVibration if set, ending it cleanly */
     @GuardedBy("mLock")
-    private void clearNextVibrationLocked(Status status) {
-        clearNextVibrationLocked(status, /* endedBy= */ null);
+    private void clearNextSessionLocked(Status status) {
+        clearNextSessionLocked(status, /* endedBy= */ null);
     }
 
     /** Clears mNextVibration if set, ending it cleanly */
     @GuardedBy("mLock")
-    private void clearNextVibrationLocked(Status status, CallerInfo endedBy) {
-        if (mNextVibration != null) {
+    private void clearNextSessionLocked(Status status, CallerInfo endedBy) {
+        if (mNextSession != null) {
             if (DEBUG) {
-                Slog.d(TAG, "Dropping pending vibration from " + mNextVibration.getCallerInfo()
+                Slog.d(TAG, "Dropping pending vibration from " + mNextSession.getCallerInfo()
                         + " with status: " + status);
             }
             // Clearing next vibration before playing it, end it and report metrics right away.
-            endVibrationLocked(mNextVibration, status, endedBy);
-            mNextVibration = null;
+            endSessionLocked(mNextSession, status, endedBy);
+            mNextSession = null;
         }
     }
 
     /** Clears mCurrentVibration if set, reporting metrics */
     @GuardedBy("mLock")
-    private void clearCurrentVibrationLocked() {
-        if (mCurrentVibration != null) {
-            mCurrentVibration.unlinkToDeath();
-            logAndRecordVibration(mCurrentVibration.getDebugInfo());
-            mCurrentVibration = null;
+    private void clearCurrentSessionLocked() {
+        if (mCurrentSession != null) {
+            mCurrentSession.unlinkToDeath();
+            logAndRecordVibration(mCurrentSession.getDebugInfo());
+            mCurrentSession = null;
             mLock.notify(); // Notify if waiting for current vibration to end.
         }
     }
 
     @GuardedBy("mLock")
-    private void maybeClearCurrentAndNextVibrationsLocked(
+    private void maybeClearCurrentAndNextSessionsLocked(
             Predicate<VibrationSession> shouldEndSessionPredicate, Status endStatus) {
         // TODO(b/372241975): investigate why external vibrations were not handled here before
-        if (!(mNextVibration instanceof ExternalVibrationSession)
-                && shouldEndSessionPredicate.test(mNextVibration)) {
-            clearNextVibrationLocked(endStatus);
+        if (!(mNextSession instanceof ExternalVibrationSession)
+                && shouldEndSessionPredicate.test(mNextSession)) {
+            clearNextSessionLocked(endStatus);
         }
-        if (!(mCurrentVibration instanceof ExternalVibrationSession)
-                && shouldEndSessionPredicate.test(mCurrentVibration)) {
-            mCurrentVibration.requestEnd(endStatus);
+        if (!(mCurrentSession instanceof ExternalVibrationSession)
+                && shouldEndSessionPredicate.test(mCurrentSession)) {
+            mCurrentSession.requestEnd(endStatus);
         }
     }
 
@@ -1902,12 +2173,12 @@
      *
      * @return true if the vibration completed, or false if waiting timed out.
      */
-    public boolean waitForCurrentVibrationEnd(long maxWaitMillis) {
+    public boolean waitForCurrentSessionEnd(long maxWaitMillis) {
         long now = SystemClock.elapsedRealtime();
         long deadline = now + maxWaitMillis;
         synchronized (mLock) {
             while (true) {
-                if (mCurrentVibration == null) {
+                if (mCurrentSession == null) {
                     return true;  // Done
                 }
                 if (now >= deadline) {  // Note that thread.wait(0) waits indefinitely.
@@ -1965,7 +2236,7 @@
 
                 synchronized (mLock) {
                     if (!hasExternalControlCapability()) {
-                        endVibrationLocked(session, Status.IGNORED_UNSUPPORTED);
+                        endSessionLocked(session, Status.IGNORED_UNSUPPORTED);
                         return session.getScale();
                     }
 
@@ -1976,17 +2247,17 @@
                         Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid()
                                 + " tried to play externally controlled vibration"
                                 + " without VIBRATE permission, ignoring.");
-                        endVibrationLocked(session, Status.IGNORED_MISSING_PERMISSION);
+                        endSessionLocked(session, Status.IGNORED_MISSING_PERMISSION);
                         return session.getScale();
                     }
 
                     Status ignoreStatus = shouldIgnoreVibrationLocked(session.callerInfo);
                     if (ignoreStatus != null) {
-                        endVibrationLocked(session, ignoreStatus);
+                        endSessionLocked(session, ignoreStatus);
                         return session.getScale();
                     }
 
-                    if ((mCurrentVibration instanceof ExternalVibrationSession evs)
+                    if ((mCurrentSession instanceof ExternalVibrationSession evs)
                             && evs.isHoldingSameVibration(vib)) {
                         // We are already playing this external vibration, so we can return the same
                         // scale calculated in the previous call to this method.
@@ -1994,17 +2265,17 @@
                     }
 
                     // Check if ongoing vibration is more important than this vibration.
-                    Vibration.EndInfo ignoreInfo = shouldIgnoreVibrationForOngoingLocked(session);
+                    Vibration.EndInfo ignoreInfo = shouldIgnoreForOngoingLocked(session);
                     if (ignoreInfo != null) {
-                        endVibrationLocked(session, ignoreInfo.status, ignoreInfo.endedBy);
+                        endSessionLocked(session, ignoreInfo.status, ignoreInfo.endedBy);
                         return session.getScale();
                     }
 
                     // First clear next request, so it won't start when the current one ends.
-                    clearNextVibrationLocked(Status.IGNORED_FOR_EXTERNAL, session.callerInfo);
-                    mNextVibration = session;
+                    clearNextSessionLocked(Status.IGNORED_FOR_EXTERNAL, session.callerInfo);
+                    mNextSession = session;
 
-                    if (mCurrentVibration != null) {
+                    if (mCurrentSession != null) {
                         // Cancel any vibration that may be playing and ready the vibrator, even if
                         // we have an externally controlled vibration playing already.
                         // Since the interface defines that only one externally controlled
@@ -2016,36 +2287,36 @@
                         // as we would need to mute the old one still if it came from a different
                         // controller.
                         session.stats.reportInterruptedAnotherVibration(
-                                mCurrentVibration.getCallerInfo());
-                        mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED,
+                                mCurrentSession.getCallerInfo());
+                        mCurrentSession.requestEnd(Status.CANCELLED_SUPERSEDED,
                                 session.callerInfo, /* immediate= */ true);
                         waitForCompletion = true;
                     }
                 }
                 // Wait for lock and interact with HAL to set external control outside main lock.
                 if (waitForCompletion) {
-                    if (!waitForCurrentVibrationEnd(VIBRATION_CANCEL_WAIT_MILLIS)) {
+                    if (!waitForCurrentSessionEnd(VIBRATION_CANCEL_WAIT_MILLIS)) {
                         Slog.e(TAG, "Timed out waiting for vibration to cancel");
                         synchronized (mLock) {
-                            if (mNextVibration == session) {
-                                mNextVibration = null;
+                            if (mNextSession == session) {
+                                mNextSession = null;
                             }
-                            endVibrationLocked(session, Status.IGNORED_ERROR_CANCELLING);
+                            endSessionLocked(session, Status.IGNORED_ERROR_CANCELLING);
                             return session.getScale();
                         }
                     }
                 }
                 synchronized (mLock) {
-                    if (mNextVibration == session) {
+                    if (mNextSession == session) {
                         // This is still the next vibration to be played.
-                        mNextVibration = null;
+                        mNextSession = null;
                     } else {
                         // A new request took the place of this one, maybe with higher importance.
                         // Next vibration already cleared with the right status, just return here.
                         return session.getScale();
                     }
                     if (!session.linkToDeath()) {
-                        endVibrationLocked(session, Status.IGNORED_ERROR_TOKEN);
+                        endSessionLocked(session, Status.IGNORED_ERROR_TOKEN);
                         return session.getScale();
                     }
                     if (DEBUG) {
@@ -2062,7 +2333,7 @@
                         // should be ignored or scaled.
                         mVibrationSettings.update();
                     }
-                    mCurrentVibration = session;
+                    mCurrentSession = session;
                     session.scale(mVibrationScaler, attrs.getUsage());
 
                     // Vibrator will start receiving data from external channels after this point.
@@ -2080,12 +2351,12 @@
             Trace.traceBegin(TRACE_TAG_VIBRATOR, "onExternalVibrationStop");
             try {
                 synchronized (mLock) {
-                    if ((mCurrentVibration instanceof ExternalVibrationSession evs)
+                    if ((mCurrentSession instanceof ExternalVibrationSession evs)
                             && evs.isHoldingSameVibration(vib)) {
                         if (DEBUG) {
                             Slog.d(TAG, "Stopping external vibration: " + vib);
                         }
-                        mCurrentVibration.requestEnd(Status.FINISHED);
+                        mCurrentSession.requestEnd(Status.FINISHED);
                     }
                 }
             } finally {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5cff37a..10f096c 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -858,10 +858,12 @@
                             wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo,
                             wallpaper.getDescription());
                 } else {
+                    WallpaperDescription desc = new WallpaperDescription.Builder().setComponent(
+                            (connection.mInfo != null) ? connection.mInfo.getComponent()
+                                    : null).build();
                     connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
                             wpdData.mWidth, wpdData.mHeight,
-                            wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo,
-                            /* description= */ null);
+                            wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo, desc);
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed attaching wallpaper on display", e);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 73ae51c..14be59f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -254,6 +254,7 @@
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -10308,6 +10309,21 @@
         if (pictureInPictureArgs != null && pictureInPictureArgs.hasSourceBoundsHint()) {
             pictureInPictureArgs.getSourceRectHint().offset(windowBounds.left, windowBounds.top);
         }
+
+        if (android.app.Flags.enableTvImplicitEnterPipRestriction()) {
+            PackageManager pm = mAtmService.mContext.getPackageManager();
+            if (pictureInPictureArgs.isAutoEnterEnabled()
+                    && pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                    && pm.checkPermission(Manifest.permission.TV_IMPLICIT_ENTER_PIP, packageName)
+                    == PackageManager.PERMISSION_DENIED) {
+                Log.i(TAG,
+                        "Auto-enter PiP only allowed on TV if android.permission"
+                                + ".TV_IMPLICIT_ENTER_PIP permission is held by the app.");
+                PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder();
+                builder.setAutoEnterEnabled(false);
+                pictureInPictureArgs.copyOnlySet(builder.build());
+            }
+        }
     }
 
     private void applyLocaleOverrideIfNeeded(Configuration resolvedConfig) {
diff --git a/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
index a47ab9d..46be79e 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorManagerService.cpp
@@ -16,27 +16,32 @@
 
 #define LOG_TAG "VibratorManagerService"
 
+#include "com_android_server_vibrator_VibratorManagerService.h"
+
 #include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+#include <utils/misc.h>
+#include <vibratorservice/VibratorManagerHalController.h>
+
+#include <unordered_map>
+
 #include "android_runtime/AndroidRuntime.h"
 #include "core_jni_helpers.h"
 #include "jni.h"
 
-#include <utils/Log.h>
-#include <utils/misc.h>
-
-#include <vibratorservice/VibratorManagerHalController.h>
-
-#include "com_android_server_vibrator_VibratorManagerService.h"
-
 namespace android {
 
 static JavaVM* sJvm = nullptr;
-static jmethodID sMethodIdOnComplete;
+static jmethodID sMethodIdOnSyncedVibrationComplete;
+static jmethodID sMethodIdOnVibrationSessionComplete;
 static std::mutex gManagerMutex;
 static vibrator::ManagerHalController* gManager GUARDED_BY(gManagerMutex) = nullptr;
 
 class NativeVibratorManagerService {
 public:
+    using IVibrationSession = aidl::android::hardware::vibrator::IVibrationSession;
+    using VibrationSessionConfig = aidl::android::hardware::vibrator::VibrationSessionConfig;
+
     NativeVibratorManagerService(JNIEnv* env, jobject callbackListener)
           : mHal(std::make_unique<vibrator::ManagerHalController>()),
             mCallbackListener(env->NewGlobalRef(callbackListener)) {
@@ -52,15 +57,69 @@
 
     vibrator::ManagerHalController* hal() const { return mHal.get(); }
 
-    std::function<void()> createCallback(jlong vibrationId) {
+    std::function<void()> createSyncedVibrationCallback(jlong vibrationId) {
         return [vibrationId, this]() {
             auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
-            jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnComplete, vibrationId);
+            jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnSyncedVibrationComplete,
+                                   vibrationId);
         };
     }
 
+    std::function<void()> createVibrationSessionCallback(jlong sessionId) {
+        return [sessionId, this]() {
+            auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+            jniEnv->CallVoidMethod(mCallbackListener, sMethodIdOnVibrationSessionComplete,
+                                   sessionId);
+            std::lock_guard<std::mutex> lock(mSessionMutex);
+            auto it = mSessions.find(sessionId);
+            if (it != mSessions.end()) {
+                mSessions.erase(it);
+            }
+        };
+    }
+
+    bool startSession(jlong sessionId, const std::vector<int32_t>& vibratorIds) {
+        VibrationSessionConfig config;
+        auto callback = createVibrationSessionCallback(sessionId);
+        auto result = hal()->startSession(vibratorIds, config, callback);
+        if (!result.isOk()) {
+            return false;
+        }
+
+        std::lock_guard<std::mutex> lock(mSessionMutex);
+        mSessions[sessionId] = std::move(result.value());
+        return true;
+    }
+
+    void closeSession(jlong sessionId) {
+        std::lock_guard<std::mutex> lock(mSessionMutex);
+        auto it = mSessions.find(sessionId);
+        if (it != mSessions.end()) {
+            it->second->close();
+            // Keep session, it can still be aborted.
+        }
+    }
+
+    void abortSession(jlong sessionId) {
+        std::lock_guard<std::mutex> lock(mSessionMutex);
+        auto it = mSessions.find(sessionId);
+        if (it != mSessions.end()) {
+            it->second->abort();
+            mSessions.erase(it);
+        }
+    }
+
+    void clearSessions() {
+        hal()->clearSessions();
+        std::lock_guard<std::mutex> lock(mSessionMutex);
+        mSessions.clear();
+    }
+
 private:
+    std::mutex mSessionMutex;
     const std::unique_ptr<vibrator::ManagerHalController> mHal;
+    std::unordered_map<jlong, std::shared_ptr<IVibrationSession>> mSessions
+            GUARDED_BY(mSessionMutex);
     const jobject mCallbackListener;
 };
 
@@ -142,7 +201,7 @@
         ALOGE("nativeTriggerSynced failed because native service was not initialized");
         return JNI_FALSE;
     }
-    auto callback = service->createCallback(vibrationId);
+    auto callback = service->createSyncedVibrationCallback(vibrationId);
     return service->hal()->triggerSynced(callback).isOk() ? JNI_TRUE : JNI_FALSE;
 }
 
@@ -156,8 +215,47 @@
     service->hal()->cancelSynced();
 }
 
+static jboolean nativeStartSession(JNIEnv* env, jclass /* clazz */, jlong servicePtr,
+                                   jlong sessionId, jintArray vibratorIds) {
+    NativeVibratorManagerService* service =
+            reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("nativeStartSession failed because native service was not initialized");
+        return JNI_FALSE;
+    }
+    jsize size = env->GetArrayLength(vibratorIds);
+    std::vector<int32_t> ids(size);
+    env->GetIntArrayRegion(vibratorIds, 0, size, reinterpret_cast<jint*>(ids.data()));
+    return service->startSession(sessionId, ids) ? JNI_TRUE : JNI_FALSE;
+}
+
+static void nativeEndSession(JNIEnv* env, jclass /* clazz */, jlong servicePtr, jlong sessionId,
+                             jboolean shouldAbort) {
+    NativeVibratorManagerService* service =
+            reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("nativeEndSession failed because native service was not initialized");
+        return;
+    }
+    if (shouldAbort) {
+        service->abortSession(sessionId);
+    } else {
+        service->closeSession(sessionId);
+    }
+}
+
+static void nativeClearSessions(JNIEnv* env, jclass /* clazz */, jlong servicePtr) {
+    NativeVibratorManagerService* service =
+            reinterpret_cast<NativeVibratorManagerService*>(servicePtr);
+    if (service == nullptr) {
+        ALOGE("nativeClearSessions failed because native service was not initialized");
+        return;
+    }
+    service->clearSessions();
+}
+
 inline static constexpr auto sNativeInitMethodSignature =
-        "(Lcom/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener;)J";
+        "(Lcom/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks;)J";
 
 static const JNINativeMethod method_table[] = {
         {"nativeInit", sNativeInitMethodSignature, (void*)nativeInit},
@@ -167,15 +265,20 @@
         {"nativePrepareSynced", "(J[I)Z", (void*)nativePrepareSynced},
         {"nativeTriggerSynced", "(JJ)Z", (void*)nativeTriggerSynced},
         {"nativeCancelSynced", "(J)V", (void*)nativeCancelSynced},
+        {"nativeStartSession", "(JJ[I)Z", (void*)nativeStartSession},
+        {"nativeEndSession", "(JJZ)V", (void*)nativeEndSession},
+        {"nativeClearSessions", "(J)V", (void*)nativeClearSessions},
 };
 
 int register_android_server_vibrator_VibratorManagerService(JavaVM* jvm, JNIEnv* env) {
     sJvm = jvm;
     auto listenerClassName =
-            "com/android/server/vibrator/VibratorManagerService$OnSyncedVibrationCompleteListener";
+            "com/android/server/vibrator/VibratorManagerService$VibratorManagerNativeCallbacks";
     jclass listenerClass = FindClassOrDie(env, listenerClassName);
-    sMethodIdOnComplete = GetMethodIDOrDie(env, listenerClass, "onComplete", "(J)V");
-
+    sMethodIdOnSyncedVibrationComplete =
+            GetMethodIDOrDie(env, listenerClass, "onSyncedVibrationComplete", "(J)V");
+    sMethodIdOnVibrationSessionComplete =
+            GetMethodIDOrDie(env, listenerClass, "onVibrationSessionComplete", "(J)V");
     return jniRegisterNativeMethods(env, "com/android/server/vibrator/VibratorManagerService",
                                     method_table, NELEM(method_table));
 }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3805c02..9759772 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -119,7 +119,6 @@
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accounts.AccountManagerService;
-import com.android.server.adaptiveauth.AdaptiveAuthService;
 import com.android.server.adb.AdbService;
 import com.android.server.alarm.AlarmManagerService;
 import com.android.server.am.ActivityManagerService;
@@ -205,6 +204,7 @@
 import com.android.server.os.DeviceIdentifiersPolicyService;
 import com.android.server.os.NativeTombstoneManagerService;
 import com.android.server.os.SchedulingPolicyService;
+import com.android.server.os.instrumentation.DynamicInstrumentationManagerService;
 import com.android.server.pdb.PersistentDataBlockService;
 import com.android.server.people.PeopleService;
 import com.android.server.permission.access.AccessCheckingService;
@@ -249,6 +249,7 @@
 import com.android.server.security.FileIntegrityService;
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
 import com.android.server.security.KeyChainSystemService;
+import com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService;
 import com.android.server.security.advancedprotection.AdvancedProtectionService;
 import com.android.server.security.rkp.RemoteProvisioningService;
 import com.android.server.selinux.SelinuxAuditLogsService;
@@ -2650,8 +2651,8 @@
             t.traceEnd();
 
             if (android.adaptiveauth.Flags.enableAdaptiveAuth()) {
-                t.traceBegin("StartAdaptiveAuthService");
-                mSystemServiceManager.startService(AdaptiveAuthService.class);
+                t.traceBegin("StartAdaptiveAuthenticationService");
+                mSystemServiceManager.startService(AdaptiveAuthenticationService.class);
                 t.traceEnd();
             }
 
@@ -2890,6 +2891,13 @@
         mSystemServiceManager.startService(TracingServiceProxy.class);
         t.traceEnd();
 
+        // UprobeStats DynamicInstrumentationManager
+        if (com.android.art.flags.Flags.executableMethodFileOffsets()) {
+            t.traceBegin("StartDynamicInstrumentationManager");
+            mSystemServiceManager.startService(DynamicInstrumentationManagerService.class);
+            t.traceEnd();
+        }
+
         // It is now time to start up the app processes...
 
         t.traceBegin("MakeLockSettingsServiceReady");
diff --git a/services/proguard.flags b/services/proguard.flags
index cdd41ab..977bd19 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -82,7 +82,7 @@
 -keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaJackDetector { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.usb.UsbAlsaMidiDevice { *; }
 -keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorController$OnVibrationCompleteListener { *; }
--keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$OnSyncedVibrationCompleteListener { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.vibrator.VibratorManagerService$VibratorManagerNativeCallbacks { *; }
 -keepclasseswithmembers,allowoptimization,allowaccessmodification class com.android.server.** {
   *** *FromNative(...);
 }
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
new file mode 100644
index 0000000..2c2e5fd
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2024 The Android Open 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 {
+    // 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"],
+    default_team: "trendy_team_system_performance",
+}
+
+android_test {
+    name: "DynamicInstrumentationManagerServiceTests",
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "hamcrest-library",
+        "platform-test-annotations",
+        "services.core",
+        "testables",
+        "truth",
+    ],
+
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: [
+        "device-tests",
+    ],
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml
new file mode 100644
index 0000000..4913d70
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open 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.server.os.instrumentation" >
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.server.os.instrumentation"
+        android:label="DynamicInstrumentationmanagerService Unit Tests"/>
+</manifest>
\ No newline at end of file
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING
new file mode 100644
index 0000000..33defed
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+      {
+          "name": "DynamicInstrumentationManagerServiceTests"
+      }
+  ]
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java
new file mode 100644
index 0000000..04073fa
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClass.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public abstract class TestAbstractClass {
+    abstract void abstractMethod();
+
+    void concreteMethod() {
+    }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java
new file mode 100644
index 0000000..2c25e7a5
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestAbstractClassImpl.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public class TestAbstractClassImpl extends TestAbstractClass {
+    @Override
+    void abstractMethod() {
+    }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
new file mode 100644
index 0000000..085f595
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestClass.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public class TestClass {
+    void primitiveParams(boolean a, boolean[] b, byte c, byte[] d, char e, char[] f, short g,
+            short[] h, int i, int[] j, long k, long[] l, float m, float[] n, double o, double[] p) {
+    }
+
+    void classParams(String a, String[] b) {
+    }
+
+    private void privateMethod() {
+    }
+
+    /**
+     * docs!
+     */
+    public void publicMethod() {
+    }
+
+    private static class InnerClass {
+        private void innerMethod(InnerClass arg, InnerClass[] argArray) {
+        }
+    }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java
new file mode 100644
index 0000000..7af4f25
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterface.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+/**
+ * docs!
+ */
+public interface TestInterface {
+    /**
+     * docs!
+     */
+    void interfaceMethod();
+
+    /**
+     * docs!
+     */
+    default void defaultMethod() {
+    }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java
new file mode 100644
index 0000000..53aecbc
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/TestInterfaceImpl.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+public class TestInterfaceImpl implements TestInterface {
+    @Override
+    public void interfaceMethod() {
+    }
+}
diff --git a/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
new file mode 100644
index 0000000..5492ba6
--- /dev/null
+++ b/services/tests/DynamicInstrumentationManagerServiceTests/src/com/android/server/os/instrumentation/ParseMethodDescriptorTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.os.instrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+
+import android.os.instrumentation.MethodDescriptor;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.TestAbstractClass;
+import com.android.server.TestAbstractClassImpl;
+import com.android.server.TestClass;
+import com.android.server.TestInterface;
+import com.android.server.TestInterfaceImpl;
+
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+
+
+/**
+ * Test class for
+ * {@link DynamicInstrumentationManagerService#parseMethodDescriptor(ClassLoader,
+ * MethodDescriptor)}.
+ * <p>
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:ParseMethodDescriptorTest
+ */
+@Presubmit
+@SmallTest
+public class ParseMethodDescriptorTest {
+    private static final String[] PRIMITIVE_PARAMS = new String[]{
+            "boolean", "boolean[]", "byte", "byte[]", "char", "char[]", "short", "short[]", "int",
+            "int[]", "long", "long[]", "float", "float[]", "double", "double[]"};
+    private static final String[] CLASS_PARAMS =
+            new String[]{"java.lang.String", "java.lang.String[]"};
+
+    @Test
+    public void primitiveParams() {
+        assertNotNull(parseMethodDescriptor(TestClass.class.getName(), "primitiveParams",
+                PRIMITIVE_PARAMS));
+    }
+
+    @Test
+    public void classParams() {
+        assertNotNull(
+                parseMethodDescriptor(TestClass.class.getName(), "classParams", CLASS_PARAMS));
+    }
+
+    @Test
+    public void publicMethod() {
+        assertNotNull(
+                parseMethodDescriptor(TestClass.class.getName(), "publicMethod"));
+    }
+
+    @Test
+    public void privateMethod() {
+        assertNotNull(
+                parseMethodDescriptor(TestClass.class.getName(), "privateMethod"));
+    }
+
+    @Test
+    public void innerClass() {
+        assertNotNull(
+                parseMethodDescriptor(TestClass.class.getName() + "$InnerClass", "innerMethod",
+                        new String[]{TestClass.class.getName() + "$InnerClass",
+                                TestClass.class.getName() + "$InnerClass[]"}));
+    }
+
+    @Test
+    public void interface_concreteMethod() {
+        assertNotNull(
+                parseMethodDescriptor(TestInterfaceImpl.class.getName(), "interfaceMethod"));
+    }
+
+    @Test
+    public void interface_defaultMethod() {
+        assertNotNull(
+                parseMethodDescriptor(TestInterface.class.getName(), "defaultMethod"));
+    }
+
+    @Test
+    public void abstractClassImpl_abstractMethod() {
+        assertNotNull(
+                parseMethodDescriptor(TestAbstractClassImpl.class.getName(), "abstractMethod"));
+    }
+
+    @Test
+    public void abstractClass_concreteMethod() {
+        assertNotNull(
+                parseMethodDescriptor(TestAbstractClass.class.getName(), "concreteMethod"));
+    }
+
+    @Test
+    public void notFound_illegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () -> parseMethodDescriptor("foo", "bar"));
+        assertThrows(IllegalArgumentException.class,
+                () -> parseMethodDescriptor(TestClass.class.getName(), "bar"));
+        assertThrows(IllegalArgumentException.class,
+                () -> parseMethodDescriptor(TestClass.class.getName(), "primitiveParams",
+                        new String[]{"int"}));
+    }
+
+    private Method parseMethodDescriptor(String fqcn, String methodName) {
+        return DynamicInstrumentationManagerService.parseMethodDescriptor(
+                getClass().getClassLoader(),
+                getMethodDescriptor(fqcn, methodName, new String[]{}));
+    }
+
+    private Method parseMethodDescriptor(String fqcn, String methodName, String[] fqParameters) {
+        return DynamicInstrumentationManagerService.parseMethodDescriptor(
+                getClass().getClassLoader(),
+                getMethodDescriptor(fqcn, methodName, fqParameters));
+    }
+
+    private MethodDescriptor getMethodDescriptor(String fqcn, String methodName,
+            String[] fqParameters) {
+        MethodDescriptor methodDescriptor = new MethodDescriptor();
+        methodDescriptor.fullyQualifiedClassName = fqcn;
+        methodDescriptor.methodName = methodName;
+        methodDescriptor.fullyQualifiedParameters = fqParameters;
+        return methodDescriptor;
+    }
+
+
+}
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 37a34ee..205ff05 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -29,7 +29,6 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.MANAGE_USB" />
-    <uses-permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE" />
 
     <!-- Permissions needed for DisplayTransformManagerTest -->
     <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
index 06f1b27..a8708f9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessSynchronizerTest.java
@@ -223,7 +223,8 @@
                 mIntRangeUserPerceptionEnabled);
         mSynchronizer.startSynchronizing();
         verify(mDisplayManagerMock).registerDisplayListener(mDisplayListenerCaptor.capture(),
-                isA(Handler.class), eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                isA(Handler.class), eq(0L),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         mDisplayListener = mDisplayListenerCaptor.getValue();
 
         verify(mContentResolverSpy).registerContentObserver(eq(BRIGHTNESS_URI), eq(false),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index b917af4..80e5ee3 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -19,13 +19,16 @@
 import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
 import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
 import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
+import static android.Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS;
 import static android.Manifest.permission.MANAGE_DISPLAYS;
+import static android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
+import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_DISPLAY;
 import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
 import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID;
@@ -96,6 +99,7 @@
 import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.DisplayOffloader;
+import android.hardware.display.DisplayTopology;
 import android.hardware.display.DisplayViewport;
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
@@ -111,11 +115,13 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.MessageQueue;
+import android.os.PermissionEnforcer;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserManager;
+import android.os.test.FakePermissionEnforcer;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
@@ -203,11 +209,13 @@
 
     private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display";
     private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests";
-    private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED
-            | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-            | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+    private static final long STANDARD_DISPLAY_EVENTS =
+            DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CHANGED
+            | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
     private static final long STANDARD_AND_CONNECTION_DISPLAY_EVENTS =
-            STANDARD_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
+            STANDARD_DISPLAY_EVENTS
+                    | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_CONNECTION_CHANGED;
 
     private static final String EVENT_DISPLAY_ADDED = "EVENT_DISPLAY_ADDED";
     private static final String EVENT_DISPLAY_REMOVED = "EVENT_DISPLAY_REMOVED";
@@ -249,6 +257,8 @@
 
     private int[] mAllowedHdrOutputTypes;
 
+    private final FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer();
+
     private final DisplayManagerService.Injector mShortMockedInjector =
             new DisplayManagerService.Injector() {
                 @Override
@@ -426,6 +436,13 @@
         when(mContext.getResources()).thenReturn(mResources);
         mUserManager = Mockito.spy(mContext.getSystemService(UserManager.class));
 
+        mPermissionEnforcer.grant(CONTROL_DISPLAY_BRIGHTNESS);
+        mPermissionEnforcer.grant(MODIFY_USER_PREFERRED_DISPLAY_MODE);
+        doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
+                eq(PermissionEnforcer.class));
+        doReturn(mPermissionEnforcer).when(mContext).getSystemService(
+                eq(Context.PERMISSION_ENFORCER_SERVICE));
+
         VirtualDeviceManager vdm = new VirtualDeviceManager(mIVirtualDeviceManager, mContext);
         when(mContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
         when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
@@ -2379,7 +2396,7 @@
         // register display listener callback
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         long allEventsExceptDisplayAdded = STANDARD_DISPLAY_EVENTS
-                & ~DisplayManager.EVENT_FLAG_DISPLAY_ADDED;
+                & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED;
         displayManagerBinderService.registerCallbackWithEventMask(callback,
                 allEventsExceptDisplayAdded);
 
@@ -2450,7 +2467,7 @@
 
         FakeDisplayManagerCallback callback = new FakeDisplayManagerCallback();
         long allEventsExceptDisplayRemoved = STANDARD_DISPLAY_EVENTS
-                & ~DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+                & ~DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
         displayManagerBinderService.registerCallbackWithEventMask(callback,
                 allEventsExceptDisplayRemoved);
 
@@ -3665,6 +3682,87 @@
         verify(mMockVirtualDisplayAdapter).releaseVirtualDisplayLocked(binder, callingUid);
     }
 
+    @Test
+    public void testGetDisplayTopology() {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 1);
+        manageDisplaysPermission(/* granted= */ true);
+        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        initDisplayPowerController(localService);
+
+        DisplayTopology topology = displayManagerBinderService.getDisplayTopology();
+        assertNotNull(topology);
+        DisplayTopology.TreeNode display = topology.getRoot();
+        assertNotNull(display);
+        assertEquals(Display.DEFAULT_DISPLAY, display.getDisplayId());
+    }
+
+    @Test
+    public void testGetDisplayTopology_NullIfFlagDisabled() {
+        manageDisplaysPermission(/* granted= */ true);
+        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(false);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        initDisplayPowerController(localService);
+
+        DisplayTopology topology = displayManagerBinderService.getDisplayTopology();
+        assertNull(topology);
+    }
+
+    @Test
+    public void testGetDisplayTopology_withoutPermission_shouldThrowException() {
+        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        initDisplayPowerController(localService);
+
+        assertThrows(SecurityException.class, displayManagerBinderService::getDisplayTopology);
+    }
+
+    @Test
+    public void testSetDisplayTopology() {
+        manageDisplaysPermission(/* granted= */ true);
+        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        initDisplayPowerController(localService);
+
+        displayManagerBinderService.setDisplayTopology(new DisplayTopology());
+    }
+
+    @Test
+    public void testSetDisplayTopology_withoutPermission_shouldThrowException() {
+        when(mMockFlags.isDisplayTopologyEnabled()).thenReturn(true);
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        DisplayManagerInternal localService = displayManager.new LocalService();
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        initDisplayPowerController(localService);
+
+        assertThrows(SecurityException.class,
+                () -> displayManagerBinderService.setDisplayTopology(new DisplayTopology()));
+    }
+
     private void initDisplayPowerController(DisplayManagerInternal localService) {
         localService.initPowerManagement(new DisplayManagerInternal.DisplayPowerCallbacks() {
             @Override
@@ -3848,6 +3946,10 @@
         DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
         displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
         displayDeviceInfo.modeId = modeId;
+        if (modeId > 0 && modeId <= displayDeviceInfo.supportedModes.length) {
+            displayDeviceInfo.renderFrameRate =
+                displayDeviceInfo.supportedModes[modeId - 1].getRefreshRate();
+        }
         updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
     }
 
@@ -4015,9 +4117,11 @@
     private void manageDisplaysPermission(boolean granted) {
         if (granted) {
             doNothing().when(mContext).enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any());
+            mPermissionEnforcer.grant(MANAGE_DISPLAYS);
         } else {
             doThrow(new SecurityException("MANAGE_DISPLAYS permission denied")).when(mContext)
                     .enforceCallingOrSelfPermission(eq(MANAGE_DISPLAYS), any());
+            mPermissionEnforcer.revoke(MANAGE_DISPLAYS);
         }
     }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 27fd1e6..e64d985 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -139,6 +139,7 @@
     private TestLooper mTestLooper;
     private Handler mHandler;
     private DisplayPowerControllerHolder mHolder;
+    private DisplayBrightnessState mDisplayBrightnessState;
     private Sensor mProxSensor;
 
     @Mock
@@ -187,6 +188,7 @@
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         mHandler = new Handler(mTestLooper.getLooper());
+        mDisplayBrightnessState = DisplayBrightnessState.builder().build();
 
         // Set some settings to minimize unexpected events and have a consistent starting state
         Settings.System.putInt(mContext.getContentResolver(),
@@ -1453,10 +1455,11 @@
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
         when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
         when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
-        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
-                invocation -> DisplayBrightnessState.builder()
-                        .setIsSlowChange(invocation.getArgument(2))
-                        .setBrightness(invocation.getArgument(1))
+        when(mHolder.clamperController.clamp(any(), any(), anyFloat(),
+                anyBoolean(), anyInt())).thenAnswer(
+                invocation -> DisplayBrightnessState.Builder.from(mDisplayBrightnessState)
+                        .setIsSlowChange(invocation.getArgument(3))
+                        .setBrightness(invocation.getArgument(2))
                         .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
                         .setCustomAnimationRate(transitionRate).build());
 
@@ -1477,10 +1480,11 @@
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
         when(mHolder.displayPowerState.getScreenBrightness()).thenReturn(.2f);
         when(mHolder.displayPowerState.getSdrScreenBrightness()).thenReturn(.1f);
-        when(mHolder.clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
-                invocation -> DisplayBrightnessState.builder()
-                        .setIsSlowChange(invocation.getArgument(2))
-                        .setBrightness(invocation.getArgument(1))
+        when(mHolder.clamperController.clamp(any(), any(), anyFloat(),
+                anyBoolean(), anyInt())).thenAnswer(
+                invocation -> DisplayBrightnessState.Builder.from(mDisplayBrightnessState)
+                        .setIsSlowChange(invocation.getArgument(3))
+                        .setBrightness(invocation.getArgument(2))
                         .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
                         .setCustomAnimationRate(transitionRate).build());
 
@@ -2574,10 +2578,11 @@
         BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
 
         when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
-        when(clamperController.clamp(any(), anyFloat(), anyBoolean(), anyInt())).thenAnswer(
-                invocation -> DisplayBrightnessState.builder()
-                        .setIsSlowChange(invocation.getArgument(2))
-                        .setBrightness(invocation.getArgument(1))
+        when(clamperController.clamp(any(), any(), anyFloat(), anyBoolean(),
+                anyInt())).thenAnswer(
+                invocation -> DisplayBrightnessState.Builder.from(mDisplayBrightnessState)
+                        .setIsSlowChange(invocation.getArgument(3))
+                        .setBrightness(invocation.getArgument(2))
                         .setMaxBrightness(PowerManager.BRIGHTNESS_MAX)
                         .setCustomAnimationRate(-1).build());
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
index 85e7356..a2d2a81 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyCoordinatorTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.display
 
+import android.hardware.display.DisplayTopology
 import android.util.DisplayMetrics
 import android.view.Display
 import android.view.DisplayInfo
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt b/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
deleted file mode 100644
index cd8c26d..0000000
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayTopologyTest.kt
+++ /dev/null
@@ -1,476 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display
-
-import android.view.Display
-import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_BOTTOM
-import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_TOP
-import com.android.server.display.DisplayTopology.TreeNode.Position.POSITION_RIGHT
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-class DisplayTopologyTest {
-    private val topology = DisplayTopology()
-
-    @Test
-    fun addOneDisplay() {
-        val displayId = 1
-        val width = 800f
-        val height = 600f
-
-        topology.addDisplay(displayId, width, height)
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId)
-
-        val display = topology.mRoot!!
-        assertThat(display.mDisplayId).isEqualTo(displayId)
-        assertThat(display.mWidth).isEqualTo(width)
-        assertThat(display.mHeight).isEqualTo(height)
-        assertThat(display.mChildren).isEmpty()
-    }
-
-    @Test
-    fun addTwoDisplays() {
-        val displayId1 = 1
-        val width1 = 800f
-        val height1 = 600f
-
-        val displayId2 = 2
-        val width2 = 1000f
-        val height2 = 1500f
-
-        topology.addDisplay(displayId1, width1, height1)
-        topology.addDisplay(displayId2, width2, height2)
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
-
-        val display1 = topology.mRoot!!
-        assertThat(display1.mDisplayId).isEqualTo(displayId1)
-        assertThat(display1.mWidth).isEqualTo(width1)
-        assertThat(display1.mHeight).isEqualTo(height1)
-        assertThat(display1.mChildren).hasSize(1)
-
-        val display2 = display1.mChildren[0]
-        assertThat(display2.mDisplayId).isEqualTo(displayId2)
-        assertThat(display2.mWidth).isEqualTo(width2)
-        assertThat(display2.mHeight).isEqualTo(height2)
-        assertThat(display2.mChildren).isEmpty()
-        assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
-        assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
-    }
-
-    @Test
-    fun addManyDisplays() {
-        val displayId1 = 1
-        val width1 = 800f
-        val height1 = 600f
-
-        val displayId2 = 2
-        val width2 = 1000f
-        val height2 = 1500f
-
-        topology.addDisplay(displayId1, width1, height1)
-        topology.addDisplay(displayId2, width2, height2)
-
-        val noOfDisplays = 30
-        for (i in 3..noOfDisplays) {
-            topology.addDisplay(/* displayId= */ i, width1, height1)
-        }
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
-
-        val display1 = topology.mRoot!!
-        assertThat(display1.mDisplayId).isEqualTo(displayId1)
-        assertThat(display1.mWidth).isEqualTo(width1)
-        assertThat(display1.mHeight).isEqualTo(height1)
-        assertThat(display1.mChildren).hasSize(1)
-
-        val display2 = display1.mChildren[0]
-        assertThat(display2.mDisplayId).isEqualTo(displayId2)
-        assertThat(display2.mWidth).isEqualTo(width2)
-        assertThat(display2.mHeight).isEqualTo(height2)
-        assertThat(display2.mChildren).hasSize(1)
-        assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
-        assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
-
-        var display = display2
-        for (i in 3..noOfDisplays) {
-            display = display.mChildren[0]
-            assertThat(display.mDisplayId).isEqualTo(i)
-            assertThat(display.mWidth).isEqualTo(width1)
-            assertThat(display.mHeight).isEqualTo(height1)
-            // The last display should have no children
-            assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
-            assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
-            assertThat(display.mOffset).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun removeDisplays() {
-        val displayId1 = 1
-        val width1 = 800f
-        val height1 = 600f
-
-        val displayId2 = 2
-        val width2 = 1000f
-        val height2 = 1500f
-
-        topology.addDisplay(displayId1, width1, height1)
-        topology.addDisplay(displayId2, width2, height2)
-
-        val noOfDisplays = 30
-        for (i in 3..noOfDisplays) {
-            topology.addDisplay(/* displayId= */ i, width1, height1)
-        }
-
-        var removedDisplays = arrayOf(20)
-        topology.removeDisplay(20)
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
-
-        var display1 = topology.mRoot!!
-        assertThat(display1.mDisplayId).isEqualTo(displayId1)
-        assertThat(display1.mWidth).isEqualTo(width1)
-        assertThat(display1.mHeight).isEqualTo(height1)
-        assertThat(display1.mChildren).hasSize(1)
-
-        var display2 = display1.mChildren[0]
-        assertThat(display2.mDisplayId).isEqualTo(displayId2)
-        assertThat(display2.mWidth).isEqualTo(width2)
-        assertThat(display2.mHeight).isEqualTo(height2)
-        assertThat(display2.mChildren).hasSize(1)
-        assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
-        assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
-
-        var display = display2
-        for (i in 3..noOfDisplays) {
-            if (i in removedDisplays) {
-                continue
-            }
-            display = display.mChildren[0]
-            assertThat(display.mDisplayId).isEqualTo(i)
-            assertThat(display.mWidth).isEqualTo(width1)
-            assertThat(display.mHeight).isEqualTo(height1)
-            // The last display should have no children
-            assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
-            assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
-            assertThat(display.mOffset).isEqualTo(0)
-        }
-
-        topology.removeDisplay(22)
-        removedDisplays += 22
-        topology.removeDisplay(23)
-        removedDisplays += 23
-        topology.removeDisplay(25)
-        removedDisplays += 25
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
-
-        display1 = topology.mRoot!!
-        assertThat(display1.mDisplayId).isEqualTo(displayId1)
-        assertThat(display1.mWidth).isEqualTo(width1)
-        assertThat(display1.mHeight).isEqualTo(height1)
-        assertThat(display1.mChildren).hasSize(1)
-
-        display2 = display1.mChildren[0]
-        assertThat(display2.mDisplayId).isEqualTo(displayId2)
-        assertThat(display2.mWidth).isEqualTo(width2)
-        assertThat(display2.mHeight).isEqualTo(height2)
-        assertThat(display2.mChildren).hasSize(1)
-        assertThat(display2.mPosition).isEqualTo(POSITION_TOP)
-        assertThat(display2.mOffset).isEqualTo(width1 / 2 - width2 / 2)
-
-        display = display2
-        for (i in 3..noOfDisplays) {
-            if (i in removedDisplays) {
-                continue
-            }
-            display = display.mChildren[0]
-            assertThat(display.mDisplayId).isEqualTo(i)
-            assertThat(display.mWidth).isEqualTo(width1)
-            assertThat(display.mHeight).isEqualTo(height1)
-            // The last display should have no children
-            assertThat(display.mChildren).hasSize(if (i < noOfDisplays) 1 else 0)
-            assertThat(display.mPosition).isEqualTo(POSITION_RIGHT)
-            assertThat(display.mOffset).isEqualTo(0)
-        }
-    }
-
-    @Test
-    fun removeAllDisplays() {
-        val displayId = 1
-        val width = 800f
-        val height = 600f
-
-        topology.addDisplay(displayId, width, height)
-        topology.removeDisplay(displayId)
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(Display.INVALID_DISPLAY)
-        assertThat(topology.mRoot).isNull()
-    }
-
-    @Test
-    fun removeDisplayThatDoesNotExist() {
-        val displayId = 1
-        val width = 800f
-        val height = 600f
-
-        topology.addDisplay(displayId, width, height)
-        topology.removeDisplay(3)
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId)
-
-        val display = topology.mRoot!!
-        assertThat(display.mDisplayId).isEqualTo(displayId)
-        assertThat(display.mWidth).isEqualTo(width)
-        assertThat(display.mHeight).isEqualTo(height)
-        assertThat(display.mChildren).isEmpty()
-    }
-
-    @Test
-    fun removePrimaryDisplay() {
-        val displayId1 = 1
-        val displayId2 = 2
-        val width = 800f
-        val height = 600f
-
-        topology.addDisplay(displayId1, width, height)
-        topology.addDisplay(displayId2, width, height)
-        topology.mPrimaryDisplayId = displayId2
-        topology.removeDisplay(displayId2)
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(displayId1)
-        val display = topology.mRoot!!
-        assertThat(display.mDisplayId).isEqualTo(displayId1)
-        assertThat(display.mWidth).isEqualTo(width)
-        assertThat(display.mHeight).isEqualTo(height)
-        assertThat(display.mChildren).isEmpty()
-    }
-
-    @Test
-    fun normalization_noOverlaps_leavesTopologyUnchanged() {
-        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
-            /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
-        topology.mRoot = display1
-
-        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
-            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
-        display1.mChildren.add(display2)
-
-        val primaryDisplayId = 3
-        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
-            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
-        display1.mChildren.add(display3)
-        topology.mPrimaryDisplayId = primaryDisplayId
-
-        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
-            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
-        display2.mChildren.add(display4)
-
-        topology.normalize()
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
-
-        val actualDisplay1 = topology.mRoot!!
-        assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
-        assertThat(actualDisplay1.mWidth).isEqualTo(200f)
-        assertThat(actualDisplay1.mHeight).isEqualTo(600f)
-        assertThat(actualDisplay1.mChildren).hasSize(2)
-
-        val actualDisplay2 = actualDisplay1.mChildren[0]
-        assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
-        assertThat(actualDisplay2.mWidth).isEqualTo(600f)
-        assertThat(actualDisplay2.mHeight).isEqualTo(200f)
-        assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay2.mOffset).isEqualTo(0f)
-        assertThat(actualDisplay2.mChildren).hasSize(1)
-
-        val actualDisplay3 = actualDisplay1.mChildren[1]
-        assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
-        assertThat(actualDisplay3.mWidth).isEqualTo(600f)
-        assertThat(actualDisplay3.mHeight).isEqualTo(200f)
-        assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay3.mOffset).isEqualTo(400f)
-        assertThat(actualDisplay3.mChildren).isEmpty()
-
-        val actualDisplay4 = actualDisplay2.mChildren[0]
-        assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
-        assertThat(actualDisplay4.mWidth).isEqualTo(200f)
-        assertThat(actualDisplay4.mHeight).isEqualTo(600f)
-        assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay4.mOffset).isEqualTo(0f)
-        assertThat(actualDisplay4.mChildren).isEmpty()
-    }
-
-    @Test
-    fun normalization_moveDisplayWithoutReparenting() {
-        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
-            /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
-        topology.mRoot = display1
-
-        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
-            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
-        display1.mChildren.add(display2)
-
-        val primaryDisplayId = 3
-        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
-            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
-        display1.mChildren.add(display3)
-        topology.mPrimaryDisplayId = primaryDisplayId
-
-        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
-            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
-        display2.mChildren.add(display4)
-
-        // Display 3 becomes a child of display 2. Display 4 gets moved without changing its parent.
-        topology.normalize()
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
-
-        val actualDisplay1 = topology.mRoot!!
-        assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
-        assertThat(actualDisplay1.mWidth).isEqualTo(200f)
-        assertThat(actualDisplay1.mHeight).isEqualTo(600f)
-        assertThat(actualDisplay1.mChildren).hasSize(1)
-
-        val actualDisplay2 = actualDisplay1.mChildren[0]
-        assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
-        assertThat(actualDisplay2.mWidth).isEqualTo(200f)
-        assertThat(actualDisplay2.mHeight).isEqualTo(600f)
-        assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay2.mOffset).isEqualTo(0f)
-        assertThat(actualDisplay2.mChildren).hasSize(2)
-
-        val actualDisplay3 = actualDisplay2.mChildren[1]
-        assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
-        assertThat(actualDisplay3.mWidth).isEqualTo(600f)
-        assertThat(actualDisplay3.mHeight).isEqualTo(200f)
-        assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay3.mOffset).isEqualTo(10f)
-        assertThat(actualDisplay3.mChildren).isEmpty()
-
-        val actualDisplay4 = actualDisplay2.mChildren[0]
-        assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
-        assertThat(actualDisplay4.mWidth).isEqualTo(200f)
-        assertThat(actualDisplay4.mHeight).isEqualTo(600f)
-        assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay4.mOffset).isEqualTo(210f)
-        assertThat(actualDisplay4.mChildren).isEmpty()
-    }
-
-    @Test
-    fun normalization_moveDisplayWithoutReparenting_offsetOutOfBounds() {
-        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
-            /* height= */ 50f, /* position= */ null, /* offset= */ 0f)
-        topology.mRoot = display1
-
-        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 600f,
-            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 0f)
-        display1.mChildren.add(display2)
-
-        val primaryDisplayId = 3
-        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
-            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 10f)
-        display1.mChildren.add(display3)
-        topology.mPrimaryDisplayId = primaryDisplayId
-
-        // Display 3 gets moved and its left side is still on the same line as the right side
-        // of Display 1, but it no longer touches it (the offset is out of bounds), so Display 2
-        // becomes its new parent.
-        topology.normalize()
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
-
-        val actualDisplay1 = topology.mRoot!!
-        assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
-        assertThat(actualDisplay1.mWidth).isEqualTo(200f)
-        assertThat(actualDisplay1.mHeight).isEqualTo(50f)
-        assertThat(actualDisplay1.mChildren).hasSize(1)
-
-        val actualDisplay2 = actualDisplay1.mChildren[0]
-        assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
-        assertThat(actualDisplay2.mWidth).isEqualTo(600f)
-        assertThat(actualDisplay2.mHeight).isEqualTo(200f)
-        assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay2.mOffset).isEqualTo(0f)
-        assertThat(actualDisplay2.mChildren).hasSize(1)
-
-        val actualDisplay3 = actualDisplay2.mChildren[0]
-        assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
-        assertThat(actualDisplay3.mWidth).isEqualTo(600f)
-        assertThat(actualDisplay3.mHeight).isEqualTo(200f)
-        assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_BOTTOM)
-        assertThat(actualDisplay3.mOffset).isEqualTo(0f)
-        assertThat(actualDisplay3.mChildren).isEmpty()
-    }
-
-    @Test
-    fun normalization_moveAndReparentDisplay() {
-        val display1 = DisplayTopology.TreeNode(/* displayId= */ 1, /* width= */ 200f,
-            /* height= */ 600f, /* position= */ null, /* offset= */ 0f)
-        topology.mRoot = display1
-
-        val display2 = DisplayTopology.TreeNode(/* displayId= */ 2, /* width= */ 200f,
-            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
-        display1.mChildren.add(display2)
-
-        val primaryDisplayId = 3
-        val display3 = DisplayTopology.TreeNode(primaryDisplayId, /* width= */ 600f,
-            /* height= */ 200f, POSITION_RIGHT, /* offset= */ 400f)
-        display1.mChildren.add(display3)
-        topology.mPrimaryDisplayId = primaryDisplayId
-
-        val display4 = DisplayTopology.TreeNode(/* displayId= */ 4, /* width= */ 200f,
-            /* height= */ 600f, POSITION_RIGHT, /* offset= */ 0f)
-        display2.mChildren.add(display4)
-
-        topology.normalize()
-
-        assertThat(topology.mPrimaryDisplayId).isEqualTo(primaryDisplayId)
-
-        val actualDisplay1 = topology.mRoot!!
-        assertThat(actualDisplay1.mDisplayId).isEqualTo(1)
-        assertThat(actualDisplay1.mWidth).isEqualTo(200f)
-        assertThat(actualDisplay1.mHeight).isEqualTo(600f)
-        assertThat(actualDisplay1.mChildren).hasSize(1)
-
-        val actualDisplay2 = actualDisplay1.mChildren[0]
-        assertThat(actualDisplay2.mDisplayId).isEqualTo(2)
-        assertThat(actualDisplay2.mWidth).isEqualTo(200f)
-        assertThat(actualDisplay2.mHeight).isEqualTo(600f)
-        assertThat(actualDisplay2.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay2.mOffset).isEqualTo(0f)
-        assertThat(actualDisplay2.mChildren).hasSize(1)
-
-        val actualDisplay3 = actualDisplay2.mChildren[0]
-        assertThat(actualDisplay3.mDisplayId).isEqualTo(3)
-        assertThat(actualDisplay3.mWidth).isEqualTo(600f)
-        assertThat(actualDisplay3.mHeight).isEqualTo(200f)
-        assertThat(actualDisplay3.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay3.mOffset).isEqualTo(400f)
-        assertThat(actualDisplay3.mChildren).hasSize(1)
-
-        val actualDisplay4 = actualDisplay3.mChildren[0]
-        assertThat(actualDisplay4.mDisplayId).isEqualTo(4)
-        assertThat(actualDisplay4.mWidth).isEqualTo(200f)
-        assertThat(actualDisplay4.mHeight).isEqualTo(600f)
-        assertThat(actualDisplay4.mPosition).isEqualTo(POSITION_RIGHT)
-        assertThat(actualDisplay4.mOffset).isEqualTo(-400f)
-        assertThat(actualDisplay4.mChildren).isEmpty()
-    }
-}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
index da79f30..2aafdfa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java
@@ -98,6 +98,7 @@
     @Mock
     private DeviceConfig.Properties mMockProperties;
     private BrightnessClamperController mClamperController;
+    private DisplayBrightnessState mDisplayBrightnessState;
     private TestInjector mTestInjector;
 
     @Before
@@ -109,6 +110,7 @@
         when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData);
 
         mClamperController = createBrightnessClamperController();
+        mDisplayBrightnessState = DisplayBrightnessState.builder().build();
     }
 
     @Test
@@ -192,7 +194,8 @@
     public void testClamp_AppliesModifier() {
         float initialBrightness = 0.2f;
         boolean initialSlowChange = true;
-        mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
+        mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness,
+                initialSlowChange, STATE_ON);
 
         verify(mMockModifier).apply(eq(mMockRequest), any());
         verify(mMockDisplayListenerModifier).apply(eq(mMockRequest), any());
@@ -204,7 +207,8 @@
         float initialBrightness = 0.2f;
         boolean initialSlowChange = true;
         when(mMockModifier.shouldListenToLightSensor()).thenReturn(true);
-        mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
+        mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness,
+                initialSlowChange, STATE_ON);
 
         verify(mMockLightSensorController).restart();
     }
@@ -214,7 +218,8 @@
         float initialBrightness = 0.2f;
         boolean initialSlowChange = true;
         clearInvocations(mMockLightSensorController);
-        mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_OFF);
+        mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness,
+                initialSlowChange, STATE_OFF);
 
         verify(mMockLightSensorController).stop();
     }
@@ -232,8 +237,8 @@
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
 
-        DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
-                initialSlowChange, STATE_ON);
+        DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
+                mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
 
         assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
         assertEquals(PowerManager.BRIGHTNESS_MAX, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -256,8 +261,8 @@
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
 
-        DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
-                initialSlowChange, STATE_ON);
+        DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
+                mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
 
         assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -280,8 +285,8 @@
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
 
-        DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
-                initialSlowChange, STATE_ON);
+        DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
+                mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
 
         assertEquals(initialBrightness, state.getBrightness(), FLOAT_TOLERANCE);
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -304,11 +309,11 @@
         mTestInjector.mCapturedChangeListener.onChanged();
         mTestHandler.flush();
         // first call of clamp method
-        mClamperController.clamp(mMockRequest, initialBrightness,
+        mClamperController.clamp(mDisplayBrightnessState, mMockRequest, initialBrightness,
                 initialSlowChange, STATE_ON);
         // immediately second call of clamp method
-        DisplayBrightnessState state = mClamperController.clamp(mMockRequest, initialBrightness,
-                initialSlowChange, STATE_ON);
+        DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
+                mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
 
         assertEquals(clampedBrightness, state.getBrightness(), FLOAT_TOLERANCE);
         assertEquals(clampedBrightness, state.getMaxBrightness(), FLOAT_TOLERANCE);
@@ -319,6 +324,22 @@
     }
 
     @Test
+    public void testClamp_activeClamperApplied_confirmBrightnessOverrideStateReturned() {
+        float initialBrightness = 0.8f;
+        boolean initialSlowChange = false;
+        mTestInjector.mCapturedChangeListener.onChanged();
+        mTestHandler.flush();
+
+        mDisplayBrightnessState = DisplayBrightnessState.builder().setBrightnessReason(
+                BrightnessReason.REASON_OVERRIDE).build();
+
+        DisplayBrightnessState state = mClamperController.clamp(mDisplayBrightnessState,
+                mMockRequest, initialBrightness, initialSlowChange, STATE_ON);
+
+        assertEquals(BrightnessReason.REASON_OVERRIDE, state.getBrightnessReason().getReason());
+    }
+
+    @Test
     public void testAmbientLuxChanges() {
         mTestInjector.mCapturedLightSensorListener.onAmbientLuxChange(50);
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
index 3c77ec9..3aef6aa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -97,11 +97,14 @@
 
     private fun setUpLowBrightnessZone() {
         whenever(mockInjector.getBrightnessInfo(Display.DEFAULT_DISPLAY)).thenReturn(
-                BrightnessInfo(/* brightness = */ 0.05f, /* adjustedBrightness = */ 0.05f,
-                        /* brightnessMinimum = */ 0.0f, /* brightnessMaximum = */ 1.0f,
-                        BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
-                        /* highBrightnessTransitionPoint = */ 1.0f,
-                        BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE))
+            BrightnessInfo(/* brightness = */ 0.05f, /* adjustedBrightness = */ 0.05f,
+                /* brightnessMinimum = */ 0.0f, /* brightnessMaximum = */ 1.0f,
+                BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+                /* highBrightnessTransitionPoint = */ 1.0f,
+                BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
+                false /* isBrightnessOverrideByWindow */
+            )
+        )
         whenever(mockDeviceConfig.highDisplayBrightnessThresholds).thenReturn(floatArrayOf())
         whenever(mockDeviceConfig.highAmbientBrightnessThresholds).thenReturn(floatArrayOf())
         whenever(mockDeviceConfig.lowDisplayBrightnessThresholds).thenReturn(floatArrayOf(0.1f))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 58f0ab4..4e0bab8 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -1225,8 +1225,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         setBrightness(10, 10, displayListener);
@@ -1256,8 +1256,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         setBrightness(10, 10, displayListener);
@@ -1291,8 +1291,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         setBrightness(10, 10, displayListener);
@@ -1325,8 +1325,8 @@
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
@@ -1404,8 +1404,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
@@ -1464,8 +1464,8 @@
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         ArgumentCaptor<SensorEventListener> listenerCaptor =
@@ -1630,8 +1630,8 @@
                 ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                        | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         // Get the sensor listener so that we can give it new light sensor events
@@ -1730,8 +1730,8 @@
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
                 any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
-                    | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener displayListener = displayListenerCaptor.getValue();
 
         // Get the sensor listener so that we can give it new light sensor events
@@ -2877,8 +2877,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Limitation
@@ -3000,8 +3000,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         final int initialRefreshRate = 60;
@@ -3075,8 +3075,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Limitation for different display
@@ -3115,8 +3115,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Limitation
@@ -3200,8 +3200,8 @@
 
         ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                        | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Sunlight limitations
@@ -3239,8 +3239,8 @@
         ArgumentCaptor<DisplayListener> captor =
                   ArgumentCaptor.forClass(DisplayListener.class);
         verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
-                  eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
-                  | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+                eq(DisplayManager.EVENT_FLAG_DISPLAY_REMOVED),
+                eq(DisplayManager.PRIVATE_EVENT_FLAG_DISPLAY_BRIGHTNESS));
         DisplayListener listener = captor.getValue();
 
         // Specify Limitation for different display
@@ -3786,8 +3786,9 @@
 
         when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
                 new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
-                    BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT,
-                    BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE));
+                        BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT,
+                        BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE,
+                        false /* isBrightnessOverrideByWindow */));
         listener.onDisplayChanged(DISPLAY_ID);
     }
 
@@ -3897,7 +3898,12 @@
         public void registerDisplayListener(DisplayListener listener, Handler handler) {}
 
         @Override
-        public void registerDisplayListener(DisplayListener listener, Handler handler, long flag) {}
+        public void registerDisplayListener(DisplayListener listener, Handler handler,
+                long flags) {}
+
+        @Override
+        public void registerDisplayListener(DisplayListener listener, Handler handler, long flag,
+                long privateFlag) {}
 
         @Override
         public Display getDisplay(int displayId) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
new file mode 100644
index 0000000..5e2f80b
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/BatteryServiceTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.hardware.health.HealthInfo;
+import android.os.HandlerThread;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.flags.Flags;
+import com.android.server.lights.LightsManager;
+import com.android.server.lights.LogicalLight;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class BatteryServiceTest {
+
+    private static final int CURRENT_BATTERY_VOLTAGE = 3000;
+    private static final int VOLTAGE_LESS_THEN_ONE_PERCENT = 3029;
+    private static final int VOLTAGE_MORE_THEN_ONE_PERCENT = 3030;
+    private static final int CURRENT_BATTERY_TEMP = 300;
+    private static final int TEMP_LESS_THEN_ONE_DEGREE_CELSIUS = 305;
+    private static final int TEMP_MORE_THEN_ONE_DEGREE_CELSIUS = 310;
+    private static final int CURRENT_BATTERY_HEALTH = 2;
+    private static final int UPDATED_BATTERY_HEALTH = 3;
+    private static final int CURRENT_CHARGE_COUNTER = 4680000;
+    private static final int UPDATED_CHARGE_COUNTER = 4218000;
+    private static final int HANDLER_IDLE_TIME_MS = 5000;
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
+            .mockStatic(SystemProperties.class)
+            .mockStatic(ActivityManager.class)
+            .mockStatic(BatteryStatsService.class)
+            .build();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+    @Mock
+    private Context mContextMock;
+    @Mock
+    private LightsManager mLightsManagerMock;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternalMock;
+    @Mock
+    private IBatteryStats mIBatteryStatsMock;
+
+    private BatteryService mBatteryService;
+    private String mSystemUiPackage;
+
+    /**
+     * Creates a mock and registers it to {@link LocalServices}.
+     */
+    private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
+        LocalServices.removeServiceForTest(clazz);
+        LocalServices.addService(clazz, mock);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mSystemUiPackage = InstrumentationRegistry.getInstrumentation().getTargetContext()
+                .getResources().getString(R.string.config_systemUi);
+
+        when(mLightsManagerMock.getLight(anyInt())).thenReturn(mock(LogicalLight.class));
+        when(mActivityManagerInternalMock.isSystemReady()).thenReturn(true);
+        when(mContextMock.getResources()).thenReturn(
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getResources());
+        ExtendedMockito.when(BatteryStatsService.getService()).thenReturn(mIBatteryStatsMock);
+
+        doNothing().when(mIBatteryStatsMock).setBatteryState(anyInt(), anyInt(), anyInt(), anyInt(),
+                anyInt(), anyInt(), anyInt(), anyInt(), anyLong());
+        doNothing().when(() -> SystemProperties.set(anyString(), anyString()));
+        doNothing().when(() -> ActivityManager.broadcastStickyIntent(any(),
+                eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
+                eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)));
+
+        addLocalServiceMock(LightsManager.class, mLightsManagerMock);
+        addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
+
+        createBatteryService();
+    }
+
+    @Test
+    public void createBatteryService_withNullLooper_throwsNullPointerException() {
+        assertThrows(NullPointerException.class, () -> new BatteryService(mContextMock));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyVoltageUpdated_lessThenOnePercent_broadcastNotSent() {
+        mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyVoltageUpdated_beforeTwentySeconds_broadcastNotSent() {
+        mBatteryService.update(
+                createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+                        CURRENT_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyVoltageUpdated_broadcastSent() {
+        mBatteryService.mLastBroadcastVoltageUpdateTime = SystemClock.elapsedRealtime() - 20000;
+        mBatteryService.update(createHealthInfo(VOLTAGE_MORE_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyTempUpdated_lessThenOneDegreeCelsius_broadcastNotSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
+                        CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void tempUpdated_broadcastSent() {
+        long lastVoltageUpdateTime = mBatteryService.mLastBroadcastVoltageUpdateTime;
+        mBatteryService.update(
+                createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, TEMP_MORE_THEN_ONE_DEGREE_CELSIUS,
+                        UPDATED_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        assertTrue(lastVoltageUpdateTime < mBatteryService.mLastBroadcastVoltageUpdateTime);
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void batteryHealthUpdated_voltageAndTempConst_broadcastSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        CURRENT_CHARGE_COUNTER,
+                        UPDATED_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+
+        // updating counter just after the health update does not triggers broadcast.
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        UPDATED_CHARGE_COUNTER,
+                        UPDATED_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void voltageUpdated_lessThanOnePercent_flagDisabled_broadcastSent() {
+        mBatteryService.update(createHealthInfo(VOLTAGE_LESS_THEN_ONE_PERCENT, CURRENT_BATTERY_TEMP,
+                CURRENT_CHARGE_COUNTER, CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyChargeCounterUpdated_broadcastNotSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        UPDATED_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void chargeCounterUpdated_tempUpdatedLessThanOneDegree_broadcastNotSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, TEMP_LESS_THEN_ONE_DEGREE_CELSIUS,
+                        UPDATED_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(0);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RATE_LIMIT_BATTERY_CHANGED_BROADCAST)
+    public void onlyChargeCounterUpdated_broadcastSent() {
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        UPDATED_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+
+        verifyNumberOfTimesBroadcastSent(1);
+    }
+
+    private HealthInfo createHealthInfo(
+            int batteryVoltage,
+            int batteryTemperature,
+            int batteryChargeCounter,
+            int batteryHealth) {
+        HealthInfo h = new HealthInfo();
+        h.batteryVoltageMillivolts = batteryVoltage;
+        h.batteryTemperatureTenthsCelsius = batteryTemperature;
+        h.batteryChargeCounterUah = batteryChargeCounter;
+        h.batteryStatus = 5;
+        h.batteryHealth = batteryHealth;
+        h.batteryPresent = true;
+        h.batteryLevel = 100;
+        h.maxChargingCurrentMicroamps = 298125;
+        h.batteryCurrentAverageMicroamps = -2812;
+        h.batteryCurrentMicroamps = 298125;
+        h.maxChargingVoltageMicrovolts = 3000;
+        h.batteryCycleCount = 50;
+        h.chargingState = 4;
+        h.batteryCapacityLevel = 100;
+        return h;
+    }
+
+    // Creates a new battery service objects and sets the initial values.
+    private void createBatteryService() throws InterruptedException {
+        final HandlerThread handlerThread = new HandlerThread("BatteryServiceTest");
+        handlerThread.start();
+
+        mBatteryService = new BatteryService(mContextMock, handlerThread.getLooper());
+
+        // trigger the update to set the initial values.
+        mBatteryService.update(
+                createHealthInfo(CURRENT_BATTERY_VOLTAGE, CURRENT_BATTERY_TEMP,
+                        CURRENT_CHARGE_COUNTER,
+                        CURRENT_BATTERY_HEALTH));
+
+        waitForHandlerToExecute();
+    }
+
+    private void waitForHandlerToExecute() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        mBatteryService.getHandlerForTest().post(latch::countDown);
+        boolean isExecutionComplete = false;
+
+        try {
+            isExecutionComplete = latch.await(HANDLER_IDLE_TIME_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail("Handler interrupted before executing the message " + e);
+        }
+
+        assertTrue("Timed out while waiting for Handler to execute.", isExecutionComplete);
+    }
+
+    private void verifyNumberOfTimesBroadcastSent(int numberOfTimes) {
+        // Increase the numberOfTimes by 1 as one broadcast was sent initially during the test
+        // setUp.
+        verify(() -> ActivityManager.broadcastStickyIntent(any(),
+                        eq(new String[]{mSystemUiPackage}), eq(AppOpsManager.OP_NONE),
+                        eq(BatteryService.BATTERY_CHANGED_OPTIONS), eq(UserHandle.USER_ALL)),
+                times(++numberOfTimes));
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 2a825f3..dcbc234 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -1308,6 +1308,8 @@
         Intent intent = new Intent();
         Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
         intent.putExtra("EXTRA_INTENT0", extraIntent);
+        Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
+        extraIntent.putExtra("NESTED_INTENT", nestedIntent);
 
         intent.collectExtraIntentKeys();
         mAms.addCreatorToken(intent, TEST_PACKAGE);
@@ -1317,6 +1319,11 @@
         assertThat(token).isNotNull();
         assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
         assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
+
+        token = (ActivityManagerService.IntentCreatorToken) nestedIntent.getCreatorToken();
+        assertThat(token).isNotNull();
+        assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
+        assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
     }
 
     @Test
@@ -1349,6 +1356,8 @@
         Intent intent = new Intent();
         Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
         intent.putExtra("EXTRA_INTENT", extraIntent);
+        Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
+        extraIntent.putExtra("NESTED_INTENT", nestedIntent);
 
         intent.collectExtraIntentKeys();
 
@@ -1374,9 +1383,12 @@
         extraIntent = intent.getParcelableExtra("EXTRA_INTENT", Intent.class);
         extraIntent2 = intent.getParcelableExtra("EXTRA_INTENT2", Intent.class);
         extraIntent3 = intent.getParcelableExtra("EXTRA_INTENT3", Intent.class);
+        nestedIntent = extraIntent.getParcelableExtra("NESTED_INTENT", Intent.class);
 
         assertThat(extraIntent.getExtendedFlags()
                 & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
+        assertThat(nestedIntent.getExtendedFlags()
+                & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
         // sneaked in intent should have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set.
         assertThat(extraIntent2.getExtendedFlags()
                 & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isNotEqualTo(0);
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
deleted file mode 100644
index 0218a78..0000000
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
index 685e8d6..e611867 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
@@ -65,7 +65,7 @@
                 new Pair<>(Arrays.asList(mMockContextHubInfo), Arrays.asList(""));
         when(mMockContextHubInfo.getId()).thenReturn(CONTEXT_HUB_ID);
         when(mMockContextHubInfo.toString()).thenReturn(CONTEXT_HUB_STRING);
-        when(mMockContextHubWrapper.getHubs()).thenReturn(hubInfo);
+        when(mMockContextHubWrapper.getContextHubs()).thenReturn(hubInfo);
 
         when(mMockContextHubWrapper.supportsLocationSettingNotifications()).thenReturn(true);
         when(mMockContextHubWrapper.supportsWifiSettingNotifications()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index dddab65..5a7027e 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -2158,13 +2158,11 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testBackgroundChainEnabled() throws Exception {
         verify(mNetworkManager).setFirewallChainEnabled(FIREWALL_CHAIN_BACKGROUND, true);
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     @RequiresFlagsDisabled(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN)
     public void testBackgroundChainOnProcStateChangeSameDelay() throws Exception {
         // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
@@ -2194,10 +2192,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled({
-            Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE,
-            Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN
-    })
+    @RequiresFlagsEnabled(Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN)
     public void testBackgroundChainOnProcStateChangeDifferentDelays() throws Exception {
         // The app will be blocked when there is no prior proc-state.
         assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
@@ -2247,7 +2242,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testBackgroundChainOnAllowlistChange() throws Exception {
         // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
         clearInvocations(mNetworkManager);
@@ -2285,7 +2279,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testBackgroundChainOnTempAllowlistChange() throws Exception {
         // initialization calls setFirewallChainEnabled, so we want to reset the invocations.
         clearInvocations(mNetworkManager);
@@ -2387,7 +2380,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testUidObserverFiltersProcStateChanges() throws Exception {
         int testProcStateSeq = 0;
         try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
@@ -2450,7 +2442,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testUidObserverFiltersStaleChanges() throws Exception {
         final int testProcStateSeq = 51;
         try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
@@ -2470,7 +2461,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testUidObserverFiltersCapabilityChanges() throws Exception {
         int testProcStateSeq = 0;
         try (SyncBarrier b = new SyncBarrier(mService.mUidEventHandler)) {
@@ -2559,7 +2549,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE)
     public void testObsoleteHandleUidChanged() throws Exception {
         callAndWaitOnUidGone(UID_A);
         assertTrue(mService.isUidNetworkingBlocked(UID_A, false));
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
rename to services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
index d180688..154494a 100644
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.adaptiveauth;
+package com.android.server.security.adaptiveauthentication;
 
 import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH;
 import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
 import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
 
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
-import static com.android.server.adaptiveauth.AdaptiveAuthService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
+import static com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
@@ -66,12 +66,12 @@
 import org.mockito.MockitoAnnotations;
 
 /**
- * atest FrameworksServicesTests:AdaptiveAuthServiceTest
+ * atest FrameworksServicesTests:AdaptiveAuthenticationServiceTest
  */
 @Presubmit
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class AdaptiveAuthServiceTest {
+public class AdaptiveAuthenticationServiceTest {
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
@@ -81,7 +81,7 @@
     private static final int REASON_UNKNOWN = 0; // BiometricRequestConstants.RequestReason
 
     private Context mContext;
-    private AdaptiveAuthService mAdaptiveAuthService;
+    private AdaptiveAuthenticationService mAdaptiveAuthenticationService;
 
     @Mock
     LockPatternUtils mLockPatternUtils;
@@ -124,8 +124,9 @@
         LocalServices.removeServiceForTest(UserManagerInternal.class);
         LocalServices.addService(UserManagerInternal.class, mUserManager);
 
-        mAdaptiveAuthService = new AdaptiveAuthService(mContext, mLockPatternUtils);
-        mAdaptiveAuthService.init();
+        mAdaptiveAuthenticationService = new AdaptiveAuthenticationService(
+                mContext, mLockPatternUtils);
+        mAdaptiveAuthenticationService.init();
 
         verify(mLockSettings).registerLockSettingsStateListener(
                 mLockSettingsStateListenerCaptor.capture());
@@ -317,13 +318,13 @@
 
     private void verifyNotLockDevice(int expectedCntFailedAttempts, int userId) {
         assertEquals(expectedCntFailedAttempts,
-                mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+                mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId));
         verify(mWindowManager, never()).lockNow();
     }
 
     private void verifyLockDevice(int userId) {
         assertEquals(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS,
-                mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+                mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId));
         verify(mLockPatternUtils).requireStrongAuth(
                 eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(userId));
         // If userId is MANAGED_PROFILE_USER_ID, the StrongAuthFlag of its parent (PRIMARY_USER_ID)
diff --git a/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS
new file mode 100644
index 0000000..bc8efa9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 0b89c11..38ff3a2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -2291,7 +2291,9 @@
     }
 
     @Test
-    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_CLASSIFICATION,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
     public void testMoveAggregateGroups_updateChannel_multipleChannels_regroupOnClassifEnabled() {
         final String pkg = "package";
         final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
@@ -2366,7 +2368,9 @@
     }
 
     @Test
-    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_CLASSIFICATION,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
     public void testMoveSections_notificationBundled() {
         final List<NotificationRecord> notificationList = new ArrayList<>();
         final String pkg = "package";
@@ -2436,7 +2440,9 @@
     }
 
     @Test
-    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_CLASSIFICATION,
+            FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
     public void testCacheAndCancelAppSummary_notificationBundled() {
         // check that the original app summary is canceled & cached on classification regrouping
         final List<NotificationRecord> notificationList = new ArrayList<>();
@@ -2495,6 +2501,7 @@
 
     @Test
     @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_CLASSIFICATION,
             FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
             FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
     public void testSingletonGroupsRegrouped_notificationBundledBeforeDelayTimeout() {
@@ -2569,6 +2576,7 @@
 
     @Test
     @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            FLAG_NOTIFICATION_CLASSIFICATION,
             FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
             FLAG_NOTIFICATION_FORCE_GROUP_SINGLETONS})
     public void testSingletonGroupsRegrouped_notificationBundledAfterDelayTimeout() {
diff --git a/services/tests/vibrator/AndroidManifest.xml b/services/tests/vibrator/AndroidManifest.xml
index c0f514f..850884f 100644
--- a/services/tests/vibrator/AndroidManifest.xml
+++ b/services/tests/vibrator/AndroidManifest.xml
@@ -32,6 +32,9 @@
     <uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON" />
     <!-- Required to play system-only haptic feedback constants -->
     <uses-permission android:name="android.permission.VIBRATE_SYSTEM_CONSTANTS" />
+    <!-- Required to play vendor effects and start vendor sessions -->
+    <uses-permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS" />
+    <uses-permission android:name="android.permission.START_VIBRATION_SESSIONS" />
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.mock" android:required="true" />
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 dfdd0cd..88ba9e3 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.mock;
@@ -83,6 +84,8 @@
 import android.os.VibratorInfo;
 import android.os.test.FakeVibrator;
 import android.os.test.TestLooper;
+import android.os.vibrator.IVibrationSession;
+import android.os.vibrator.IVibrationSessionCallback;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
@@ -195,6 +198,7 @@
             new SparseArray<>();
 
     private final List<HalVibration> mPendingVibrations = new ArrayList<>();
+    private final List<VendorVibrationSession> mPendingSessions = new ArrayList<>();
 
     private VibratorManagerService mService;
     private Context mContextSpy;
@@ -264,6 +268,11 @@
             grantPermission(android.Manifest.permission.VIBRATE);
             // Cancel any pending vibration from tests, including external vibrations.
             cancelVibrate(mService);
+            // End pending sessions.
+            for (VendorVibrationSession session : mPendingSessions) {
+                session.cancelSession();
+            }
+            mTestLooper.dispatchAll();
             // Wait until pending vibrations end asynchronously.
             for (HalVibration vibration : mPendingVibrations) {
                 vibration.waitForEnd();
@@ -1229,6 +1238,36 @@
                 .anyMatch(PrebakedSegment.class::isInstance));
     }
 
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @Test
+    public void vibrate_withOngoingHigherImportanceSession_ignoresEffect() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+        mTestLooper.dispatchAll();
+        assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+        verify(callback).onStarted(any(IVibrationSession.class));
+
+        HalVibration vibration = vibrateAndWaitUntilFinished(service,
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                HAPTIC_FEEDBACK_ATTRS);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+        assertThat(vibration.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE);
+        verify(callback, never()).onFinishing();
+        verify(callback, never()).onFinished(anyInt());
+        // The second vibration shouldn't have played any prebaked segment.
+        assertFalse(fakeVibrator.getAllEffectSegments().stream()
+                .anyMatch(PrebakedSegment.class::isInstance));
+    }
+
     @Test
     public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect()
             throws Exception {
@@ -1289,6 +1328,36 @@
                 .filter(PrebakedSegment.class::isInstance).count());
     }
 
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @Test
+    public void vibrate_withOngoingLowerImportanceSession_cancelsOngoingSession() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+        mTestLooper.dispatchAll();
+        assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+        verify(callback).onStarted(any(IVibrationSession.class));
+
+        HalVibration vibration = vibrateAndWaitUntilFinished(service,
+                VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
+                HAPTIC_FEEDBACK_ATTRS);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
+        assertThat(vibration.getStatus()).isEqualTo(Status.FINISHED);
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+        // One segment played is the prebaked CLICK from the new vibration.
+        assertEquals(1, mVibratorProviders.get(1).getAllEffectSegments().stream()
+                .filter(PrebakedSegment.class::isInstance).count());
+    }
+
     @Test
     public void vibrate_withOngoingSameImportancePipelinedVibration_continuesOngoingEffect()
             throws Exception {
@@ -1416,16 +1485,16 @@
         // The native callback will be dispatched manually in this test.
         mTestLooper.stopAutoDispatchAndIgnoreExceptions();
 
-        ArgumentCaptor<VibratorManagerService.OnSyncedVibrationCompleteListener> listenerCaptor =
+        ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
                 ArgumentCaptor.forClass(
-                        VibratorManagerService.OnSyncedVibrationCompleteListener.class);
+                        VibratorManagerService.VibratorManagerNativeCallbacks.class);
         verify(mNativeWrapperMock).init(listenerCaptor.capture());
 
         CountDownLatch triggerCountDown = new CountDownLatch(1);
         // Mock trigger callback on registered listener right after the synced vibration starts.
         when(mNativeWrapperMock.prepareSynced(eq(new int[]{1, 2}))).thenReturn(true);
         when(mNativeWrapperMock.triggerSynced(anyLong())).then(answer -> {
-            listenerCaptor.getValue().onComplete(answer.getArgument(0));
+            listenerCaptor.getValue().onSyncedVibrationComplete(answer.getArgument(0));
             triggerCountDown.countDown();
             return true;
         });
@@ -2318,6 +2387,34 @@
         assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
     }
 
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @Test
+    public void onExternalVibration_withOngoingHigherImportanceSession_ignoreNewVibration()
+            throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+        mTestLooper.dispatchAll();
+        verify(callback).onStarted(any(IVibrationSession.class));
+
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
+        ExternalVibrationScale scale =
+                mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        // External vibration is ignored.
+        assertEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+
+        // Session still running.
+        assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+        verify(callback, never()).onFinishing();
+        verify(callback, never()).onFinished(anyInt());
+    }
+
     @Test
     public void onExternalVibration_withNewSameImportanceButRepeating_cancelsOngoingVibration()
             throws Exception {
@@ -2373,6 +2470,36 @@
         assertEquals(Arrays.asList(false), mVibratorProviders.get(1).getExternalControlStates());
     }
 
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    @Test
+    public void onExternalVibration_withOngoingLowerImportanceSession_cancelsOngoingSession()
+            throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+        mTestLooper.dispatchAll();
+        verify(callback).onStarted(any(IVibrationSession.class));
+
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS, mock(IExternalVibrationController.class));
+        ExternalVibrationScale scale =
+                mExternalVibratorService.onExternalVibrationStart(externalVibration);
+        assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+        mTestLooper.dispatchAll();
+
+        // Session is cancelled.
+        assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+        assertEquals(Arrays.asList(false, true),
+                mVibratorProviders.get(1).getExternalControlStates());
+    }
+
     @Test
     public void onExternalVibration_withRingtone_usesRingerModeSettings() {
         mockVibrators(1);
@@ -2638,6 +2765,376 @@
     }
 
     @Test
+    @DisableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_withoutFeatureFlag_throwsException() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        int vibratorId = 1;
+        mockVibrators(vibratorId);
+        VibratorManagerService service = createSystemReadyService();
+
+        IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+        assertThrows("Expected starting session without feature flag to fail!",
+                UnsupportedOperationException.class,
+                () -> startSession(service, RINGTONE_ATTRS, callback, vibratorId));
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+        verify(callback, never()).onStarted(any(IVibrationSession.class));
+        verify(callback, never()).onFinishing();
+        verify(callback, never()).onFinished(anyInt());
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_withoutCapability_doesNotStart() throws Exception {
+        int vibratorId = 1;
+        mockVibrators(vibratorId);
+        VibratorManagerService service = createSystemReadyService();
+
+        IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
+                callback, vibratorId);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+        verify(callback, never()).onFinishing();
+        verify(callback)
+                .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_withoutCallback_doesNotStart() {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        int vibratorId = 1;
+        mockVibrators(vibratorId);
+        VibratorManagerService service = createSystemReadyService();
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS,
+                /* callback= */ null, vibratorId);
+        mTestLooper.dispatchAll();
+
+        assertThat(session).isNull();
+        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_withoutVibratorIds_doesNotStart() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+
+        int[] nullIds = null;
+        IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, nullIds);
+        assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+
+        int[] emptyIds = {};
+        session = startSession(service, RINGTONE_ATTRS, callback, emptyIds);
+        assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock, never()).startSession(anyLong(), any(int[].class));
+        verify(callback, never()).onFinishing();
+        verify(callback, times(2))
+                .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_badVibratorId_failsToStart() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        when(mNativeWrapperMock.startSession(anyLong(), any(int[].class))).thenReturn(false);
+        doReturn(false).when(mNativeWrapperMock).startSession(anyLong(), eq(new int[] {1, 3}));
+        doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), eq(new int[] {1, 2}));
+        VibratorManagerService service = createSystemReadyService();
+
+        IBinder token = mock(IBinder.class);
+        IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+        doReturn(token).when(callback).asBinder();
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 3);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.IGNORED_UNSUPPORTED);
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 3}));
+        verify(callback, never()).onStarted(any(IVibrationSession.class));
+        verify(callback, never()).onFinishing();
+        verify(callback)
+                .onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_UNSUPPORTED));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_thenFinish_returnsSuccessAfterCallback() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        VibratorManagerService service = createSystemReadyService();
+        int sessionFinishDelayMs = 200;
+        IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+
+        captor.getValue().finishSession();
+
+        // Session not ended until HAL callback.
+        assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+
+        // Dispatch HAL callbacks.
+        mTestLooper.moveTimeForward(sessionFinishDelayMs);
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_thenSendCancelSignal_cancelsSession() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        VibratorManagerService service = createSystemReadyService();
+        int sessionFinishDelayMs = 200;
+        IVibrationSessionCallback callback = mockSessionCallbacks(sessionFinishDelayMs);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+
+        session.getCancellationSignal().cancel();
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_thenCancel_returnsCancelStatus() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        VibratorManagerService service = createSystemReadyService();
+        // Delay not applied when session is aborted.
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+
+        captor.getValue().cancelSession();
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_USER);
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_finishThenCancel_returnsRightAwayWithFinishedStatus()
+            throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        VibratorManagerService service = createSystemReadyService();
+        // Delay not applied when session is aborted.
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+
+        captor.getValue().finishSession();
+        mTestLooper.dispatchAll();
+        assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+
+        captor.getValue().cancelSession();
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.FINISHED);
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_SUCCESS));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_thenHalCancels_returnsCancelStatus()
+            throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1, 2);
+        VibratorManagerService service = createSystemReadyService();
+        ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
+                ArgumentCaptor.forClass(
+                        VibratorManagerService.VibratorManagerNativeCallbacks.class);
+        verify(mNativeWrapperMock).init(listenerCaptor.capture());
+        doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), any(int[].class));
+
+        IBinder token = mock(IBinder.class);
+        IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+        doReturn(token).when(callback).asBinder();
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1, 2);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] {1, 2}));
+        verify(callback).onStarted(any(IVibrationSession.class));
+
+        // Mock HAL ending session unexpectedly.
+        listenerCaptor.getValue().onVibrationSessionComplete(session.getSessionId());
+        mTestLooper.dispatchAll();
+
+        assertThat(session.getStatus()).isEqualTo(Status.CANCELLED_BY_UNKNOWN_REASON);
+        verify(callback).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_CANCELED));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_withPowerMode_usesPowerModeState() throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1);
+        VibratorManagerService service = createSystemReadyService();
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(LOW_POWER_STATE);
+        VendorVibrationSession session1 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+        VendorVibrationSession session2 = startSession(service, RINGTONE_ATTRS, callback, 1);
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<IVibrationSession> captor = ArgumentCaptor.forClass(IVibrationSession.class);
+        verify(callback).onStarted(captor.capture());
+        captor.getValue().cancelSession();
+        mTestLooper.dispatchAll();
+
+        mRegisteredPowerModeListener.onLowPowerModeChanged(NORMAL_POWER_STATE);
+        VendorVibrationSession session3 = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock, never())
+                .startSession(eq(session1.getSessionId()), any(int[].class));
+        verify(mNativeWrapperMock).startSession(eq(session2.getSessionId()), eq(new int[] {1}));
+        verify(mNativeWrapperMock).startSession(eq(session3.getSessionId()), eq(new int[] {1}));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_withOngoingHigherImportanceVibration_ignoresSession()
+            throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        VibratorManagerService service = createSystemReadyService();
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{10, 10_000}, new int[]{128, 255}, -1);
+        vibrate(service, effect, ALARM_ATTRS);
+
+        // VibrationThread will start this vibration async.
+        // Wait until second step started to ensure the noteVibratorOn was triggered.
+        assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2,
+                service, TEST_TIMEOUT_MILLIS));
+
+        VendorVibrationSession session = startSession(service, HAPTIC_FEEDBACK_ATTRS, callback, 1);
+        mTestLooper.dispatchAll();
+
+        verify(mNativeWrapperMock, never())
+                .startSession(eq(session.getSessionId()), any(int[].class));
+        assertThat(session.getStatus()).isEqualTo(Status.IGNORED_FOR_HIGHER_IMPORTANCE);
+        verify(callback, never()).onFinishing();
+        verify(callback).onFinished(eq(android.os.vibrator.VendorVibrationSession.STATUS_IGNORED));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_withOngoingLowerImportanceVibration_cancelsOngoing()
+            throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1);
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1);
+        fakeVibrator.setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+        fakeVibrator.setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        VibratorManagerService service = createSystemReadyService();
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        VibrationEffect effect = VibrationEffect.createWaveform(
+                new long[]{10, 10_000}, new int[]{128, 255}, -1);
+        HalVibration vibration = vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+        // VibrationThread will start this vibration async.
+        // Wait until second step started to ensure the noteVibratorOn was triggered.
+        assertTrue(waitUntil(s -> fakeVibrator.getAmplitudes().size() == 2, service,
+                TEST_TIMEOUT_MILLIS));
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+        vibration.waitForEnd();
+        assertTrue(waitUntil(s -> session.isStarted(), service, TEST_TIMEOUT_MILLIS));
+        mTestLooper.dispatchAll();
+
+        assertThat(vibration.getStatus()).isEqualTo(Status.CANCELLED_SUPERSEDED);
+        assertThat(session.getStatus()).isEqualTo(Status.RUNNING);
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] { 1 }));
+        verify(callback).onStarted(any(IVibrationSession.class));
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void startVibrationSession_withOngoingLowerImportanceExternalVibration_cancelsOngoing()
+            throws Exception {
+        mockCapabilities(IVibratorManager.CAP_START_SESSIONS);
+        mockVibrators(1);
+        mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
+        mVibratorProviders.get(1).setSupportedEffects(VibrationEffect.EFFECT_CLICK);
+        setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+        VibratorManagerService service = createSystemReadyService();
+        IVibrationSessionCallback callback =
+                mockSessionCallbacks(/* delayToEndSessionMillis= */ TEST_TIMEOUT_MILLIS);
+
+        IBinder firstToken = mock(IBinder.class);
+        IExternalVibrationController controller = mock(IExternalVibrationController.class);
+        ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME,
+                AUDIO_ALARM_ATTRS,
+                controller, firstToken);
+        ExternalVibrationScale scale =
+                mExternalVibratorService.onExternalVibrationStart(externalVibration);
+
+        VendorVibrationSession session = startSession(service, RINGTONE_ATTRS, callback, 1);
+        mTestLooper.dispatchAll();
+
+        assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel);
+        // The external vibration should have been cancelled
+        verify(controller).mute();
+        assertEquals(Arrays.asList(false, true, false),
+                mVibratorProviders.get(1).getExternalControlStates());
+        verify(mNativeWrapperMock).startSession(eq(session.getSessionId()), eq(new int[] { 1 }));
+        verify(callback).onStarted(any(IVibrationSession.class));
+    }
+
+    @Test
     public void frameworkStats_externalVibration_reportsAllMetrics() throws Exception {
         mockVibrators(1);
         mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
@@ -3050,6 +3547,30 @@
         when(mNativeWrapperMock.getVibratorIds()).thenReturn(vibratorIds);
     }
 
+    private IVibrationSessionCallback mockSessionCallbacks(long delayToEndSessionMillis) {
+        Handler handler = new Handler(mTestLooper.getLooper());
+        ArgumentCaptor<VibratorManagerService.VibratorManagerNativeCallbacks> listenerCaptor =
+                ArgumentCaptor.forClass(
+                        VibratorManagerService.VibratorManagerNativeCallbacks.class);
+        verify(mNativeWrapperMock).init(listenerCaptor.capture());
+        doReturn(true).when(mNativeWrapperMock).startSession(anyLong(), any(int[].class));
+        doAnswer(args -> {
+            handler.postDelayed(
+                    () -> listenerCaptor.getValue().onVibrationSessionComplete(args.getArgument(0)),
+                    delayToEndSessionMillis);
+            return null;
+        }).when(mNativeWrapperMock).endSession(anyLong(), eq(false));
+        doAnswer(args -> {
+            listenerCaptor.getValue().onVibrationSessionComplete(args.getArgument(0));
+            return null;
+        }).when(mNativeWrapperMock).endSession(anyLong(), eq(true));
+
+        IBinder token = mock(IBinder.class);
+        IVibrationSessionCallback callback = mock(IVibrationSessionCallback.class);
+        doReturn(token).when(callback).asBinder();
+        return callback;
+    }
+
     private void cancelVibrate(VibratorManagerService service) {
         service.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, service);
     }
@@ -3157,6 +3678,16 @@
         return vib;
     }
 
+    private VendorVibrationSession startSession(VibratorManagerService service,
+            VibrationAttributes attrs, IVibrationSessionCallback callback, int... vibratorIds) {
+        VendorVibrationSession session = service.startVendorVibrationSessionInternal(UID,
+                Context.DEVICE_ID_DEFAULT, PACKAGE_NAME, vibratorIds, attrs, "reason", callback);
+        if (session != null) {
+            mPendingSessions.add(session);
+        }
+        return session;
+    }
+
     private boolean waitUntil(Predicate<VibratorManagerService> predicate,
             VibratorManagerService service, long timeout) throws InterruptedException {
         long timeoutTimestamp = SystemClock.uptimeMillis() + timeout;
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 28ae271..cf5323e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -288,7 +288,6 @@
      * Sends a KEYCODE_SCREENSHOT and validates screenshot is taken if flag is enabled
      */
     @Test
-    @EnableFlags(com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE)
     @DisableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
     public void testTakeScreenshot_flagEnabled() {
         sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
@@ -296,17 +295,6 @@
     }
 
     /**
-     * Sends a KEYCODE_SCREENSHOT and validates screenshot is not taken if flag is disabled
-     */
-    @Test
-    @DisableFlags({com.android.hardware.input.Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE,
-            com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER})
-    public void testTakeScreenshot_flagDisabled() {
-        sendKeyCombination(new int[]{KEYCODE_SCREENSHOT}, 0);
-        mPhoneWindowManager.assertTakeScreenshotNotCalled();
-    }
-
-    /**
      * META+CTRL+BACKSPACE for taking a bugreport when the flag is enabled.
      */
     @Test
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index a34094c..98949d0c 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -68,7 +68,7 @@
     // Used to synchronize singleton logging lazy initialization
     private static final Object sSingletonSync = new Object();
     private static EventManager sEventManager;
-    private static SessionManager sSessionManager;
+    private static volatile SessionManager sSessionManager;
     private static Object sLock = null;
 
     /**
@@ -379,6 +379,23 @@
         return sSessionManager;
     }
 
+    @VisibleForTesting
+    public static SessionManager setSessionManager(Context context,
+            java.lang.Runnable cleanSessionRunnable) {
+        // Checking for null again outside of synchronization because we only need to synchronize
+        // during the lazy loading of the session logger. We don't need to synchronize elsewhere.
+        if (sSessionManager == null) {
+            synchronized (sSingletonSync) {
+                if (sSessionManager == null) {
+                    sSessionManager = new SessionManager(cleanSessionRunnable);
+                    sSessionManager.setContext(context);
+                    return sSessionManager;
+                }
+            }
+        }
+        return sSessionManager;
+    }
+
     public static void setTag(String tag) {
         TAG = tag;
         DEBUG = isLoggable(android.util.Log.DEBUG);
diff --git a/telecomm/java/android/telecom/Logging/SessionManager.java b/telecomm/java/android/telecom/Logging/SessionManager.java
index 00e344c..ac1e69e 100644
--- a/telecomm/java/android/telecom/Logging/SessionManager.java
+++ b/telecomm/java/android/telecom/Logging/SessionManager.java
@@ -62,9 +62,7 @@
 
     @VisibleForTesting
     public final ConcurrentHashMap<Integer, Session> mSessionMapper = new ConcurrentHashMap<>(64);
-    @VisibleForTesting
-    public java.lang.Runnable mCleanStaleSessions = () ->
-            cleanupStaleSessions(getSessionCleanupTimeoutMs());
+    private final java.lang.Runnable mCleanStaleSessions;
     private final Handler mSessionCleanupHandler = new Handler(Looper.getMainLooper());
 
     // Overridden in LogTest to skip query to ContentProvider
@@ -110,29 +108,39 @@
     }
 
     public SessionManager() {
+        mCleanStaleSessions = () -> cleanupStaleSessions(getSessionCleanupTimeoutMs());
+    }
+
+    @VisibleForTesting
+    public SessionManager(java.lang.Runnable cleanStaleSessionsRunnable) {
+        mCleanStaleSessions = cleanStaleSessionsRunnable;
     }
 
     private long getSessionCleanupTimeoutMs() {
         return mSessionCleanupTimeoutMs.get();
     }
 
-    private synchronized void resetStaleSessionTimer() {
+    private void resetStaleSessionTimer() {
         if (!Flags.endSessionImprovements()) {
-            mSessionCleanupHandler.removeCallbacksAndMessages(null);
-            // Will be null in Log Testing
-            if (mCleanStaleSessions != null) {
-                mSessionCleanupHandler.postDelayed(mCleanStaleSessions,
-                        getSessionCleanupTimeoutMs());
-            }
-        } else {
-            if (mCleanStaleSessions != null
-                    && !mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) {
+            resetStaleSessionTimerOld();
+            return;
+        }
+        // Will be null in Log Testing
+        if (mCleanStaleSessions == null) return;
+        synchronized (mSessionCleanupHandler) {
+            if (!mSessionCleanupHandler.hasCallbacks(mCleanStaleSessions)) {
                 mSessionCleanupHandler.postDelayed(mCleanStaleSessions,
                         getSessionCleanupTimeoutMs());
             }
         }
     }
 
+    private synchronized void resetStaleSessionTimerOld() {
+        if (mCleanStaleSessions == null) return;
+        mSessionCleanupHandler.removeCallbacksAndMessages(null);
+        mSessionCleanupHandler.postDelayed(mCleanStaleSessions, getSessionCleanupTimeoutMs());
+    }
+
     /**
      * Determines whether or not to start a new session or continue an existing session based on
      * the {@link Session.Info} info passed into startSession. If info is null, a new Session is
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 2a06c3d..6490cbe 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -2183,8 +2183,8 @@
      * Maximum size in bytes of the PDU to send or download when connected to a non-terrestrial
      * network. MmsService will return a result code of MMS_ERROR_TOO_LARGE_FOR_TRANSPORT if
      * the PDU exceeds this limit when connected to a non-terrestrial network.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_MMS_MAX_NTN_PAYLOAD_SIZE_BYTES_INT =
             "mms_max_ntn_payload_size_bytes_int";
 
@@ -9850,9 +9850,8 @@
      * manually scanning available cellular network.
      * If key is {@code true}, satellite plmn should not be exposed to user and should be
      * automatically set, {@code false} otherwise. Default value is {@code true}.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL =
             "remove_satellite_plmn_in_manual_network_scan_bool";
 
@@ -9877,18 +9876,18 @@
 
     /**
      * Doesn't support unrestricted traffic on satellite network.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_DATA_SUPPORT_ONLY_RESTRICTED = 0;
     /**
      * Support unrestricted but bandwidth_constrained traffic on satellite network.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_DATA_SUPPORT_BANDWIDTH_CONSTRAINED = 1;
     /**
      * Support unrestricted satellite network that serves all traffic.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_DATA_SUPPORT_ALL = 2;
     /**
      * Indicates what kind of traffic an {@link NetworkCapabilities#NET_CAPABILITY_NOT_RESTRICTED}
@@ -9898,8 +9897,8 @@
      * {@link ApnSetting#INFRASTRUCTURE_SATELLITE} from APN infrastructure_bitmask, and this
      * configuration is ignored.
      * By default it only supports restricted data.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_SATELLITE_DATA_SUPPORT_MODE_INT =
             "satellite_data_support_mode_int";
 
@@ -9911,8 +9910,8 @@
      *                 {@link com.android.ims.ImsConfig.WfcModeFeatureValueConstants#WIFI_PREFERRED}
      * {@code false} - roaming preference can be changed by user independently and is not
      *                 overridden when device is connected to non-terrestrial network.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_OVERRIDE_WFC_ROAMING_MODE_WHILE_USING_NTN_BOOL =
             "override_wfc_roaming_mode_while_using_ntn_bool";
 
@@ -9945,8 +9944,8 @@
      * Reference: GSMA TS.43-v11, 2.8.5 Fast Authentication and Token Management.
      * `app_name` is an optional attribute in the request and may vary depending on the carrier
      * requirement.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_SATELLITE_ENTITLEMENT_APP_NAME_STRING =
             "satellite_entitlement_app_name_string";
 
@@ -9954,9 +9953,8 @@
      * URL to redirect user to get more information about the carrier support for satellite.
      *
      * The default value is empty string.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_SATELLITE_INFORMATION_REDIRECT_URL_STRING =
             "satellite_information_redirect_url_string";
     /**
@@ -9966,9 +9964,8 @@
      * This will need agreement with carriers before enabling this flag.
      *
      * The default value is false.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL =
             "emergency_messaging_supported_bool";
 
@@ -9983,9 +9980,8 @@
      * prompt user to switch to using satellite emergency messaging.
      *
      * The default value is 30 seconds.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT =
             "emergency_call_to_satellite_t911_handover_timeout_millis_int";
 
@@ -9998,9 +9994,8 @@
      * The default capabilities are
      * {@link NetworkRegistrationInfo#SERVICE_TYPE_SMS}, and
      * {@link NetworkRegistrationInfo#SERVICE_TYPE_MMS}
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_CARRIER_ROAMING_SATELLITE_DEFAULT_SERVICES_INT_ARRAY =
             "carrier_roaming_satellite_default_services_int_array";
 
@@ -10031,9 +10026,8 @@
      * Defines the NIDD (Non-IP Data Delivery) APN to be used for carrier roaming to satellite
      * attachment. For more on NIDD, see 3GPP TS 29.542.
      * Note this config is the only source of truth regarding the definition of the APN.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_SATELLITE_NIDD_APN_NAME_STRING =
             "satellite_nidd_apn_name_string";
 
@@ -10044,9 +10038,8 @@
      *
      * If {@code false}, the emergency call is always blocked if device is in emergency satellite
      * mode. Note if device is NOT in emergency satellite mode, emergency call is always allowed.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_SATELLITE_ROAMING_TURN_OFF_SESSION_FOR_EMERGENCY_CALL_BOOL =
             "satellite_roaming_turn_off_session_for_emergency_call_bool";
 
@@ -10059,14 +10052,14 @@
 
     /**
      * Device can connect to carrier roaming non-terrestrial network automatically.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int CARRIER_ROAMING_NTN_CONNECT_AUTOMATIC = 0;
     /**
      * Device can connect to carrier roaming non-terrestrial network only if user manually triggers
      * satellite connection.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int CARRIER_ROAMING_NTN_CONNECT_MANUAL = 1;
     /**
      * Indicates carrier roaming non-terrestrial network connect type that the device can use to
@@ -10074,8 +10067,8 @@
      * If this key is set to CARRIER_ROAMING_NTN_CONNECT_MANUAL then connect button will be
      * displayed to user when the device is eligible to use carrier roaming
      * non-terrestrial network.
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_CARRIER_ROAMING_NTN_CONNECT_TYPE_INT =
             "carrier_roaming_ntn_connect_type_int";
 
@@ -10088,7 +10081,6 @@
      * will be made to T911.
      *
      * The default value is {@link SatelliteManager#EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911}.
-     *
      */
     @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
     public static final String
@@ -10105,9 +10097,8 @@
      * After the timer is expired, device is marked as eligible for satellite communication.
      *
      * The default value is 180 seconds.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_CARRIER_SUPPORTED_SATELLITE_NOTIFICATION_HYSTERESIS_SEC_INT =
             "carrier_supported_satellite_notification_hysteresis_sec_int";
 
@@ -10153,7 +10144,9 @@
             "satellite_roaming_esos_inactivity_timeout_sec_int";
 
     /**
-     * A string array containing the list of messaging package names that support satellite.
+     * A string array containing the list of messaging apps that support satellite.
+     *
+     * The default value contains only "com.google.android.apps.messaging"
      *
      * @hide
      */
@@ -10166,9 +10159,8 @@
      * the default APN (i.e. internet) will be used for tethering.
      *
      * This config is only available when using Preset APN(not user edited) as Preferred APN.
-     *
-     * @hide
      */
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final String KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL =
             "disable_dun_apn_while_roaming_with_preset_apn_bool";
 
@@ -11313,6 +11305,8 @@
                         NetworkRegistrationInfo.SERVICE_TYPE_SMS,
                         NetworkRegistrationInfo.SERVICE_TYPE_MMS
                 });
+        sDefaults.putStringArray(KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY, new String[]{
+                "com.google.android.apps.messaging"});
         sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
         sDefaults.putBoolean(KEY_EMERGENCY_MESSAGING_SUPPORTED_BOOL, false);
         sDefaults.putInt(KEY_EMERGENCY_CALL_TO_SATELLITE_T911_HANDOVER_TIMEOUT_MILLIS_INT,
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7be3f33..88dddcf 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -483,43 +483,43 @@
     /**
      * Telephony framework needs to access the current location of the device to perform the
      * request. However, location in the settings is disabled by users.
-     *
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_RESULT_LOCATION_DISABLED = 25;
 
     /**
      * Telephony framework needs to access the current location of the device to perform the
      * request. However, Telephony fails to fetch the current location from location service.
-     *
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26;
 
     /**
      * Emergency call is in progress.
-     *
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_RESULT_EMERGENCY_CALL_IN_PROGRESS = 27;
 
     /**
      * Disabling satellite is in progress.
-     *
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_RESULT_DISABLE_IN_PROGRESS = 28;
 
     /**
      * Enabling satellite is in progress.
-     *
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_RESULT_ENABLE_IN_PROGRESS = 29;
 
     /** @hide */
@@ -715,7 +715,7 @@
     public static final int EMERGENCY_CALL_TO_SATELLITE_HANDOVER_TYPE_T911 = 2;
 
     /**
-     * This intent will be broadcasted if there are any change to list of subscriber informations.
+     * This intent will be broadcasted if there are any change to list of subscriber information.
      * This intent will be sent only to the app with component defined in
      * config_satellite_carrier_roaming_esos_provisioned_class and package defined in
      * config_satellite_gateway_service_package
@@ -1349,12 +1349,16 @@
      * The satellite modem is being powered on.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_MODEM_STATE_ENABLING_SATELLITE = 8;
 
     /**
      * The satellite modem is being powered off.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_MODEM_STATE_DISABLING_SATELLITE = 9;
 
     /**
@@ -1414,6 +1418,8 @@
      * there is any incoming message.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int DATAGRAM_TYPE_KEEP_ALIVE = 3;
 
     /**
@@ -1421,6 +1427,8 @@
      * is the last message to emergency service provider indicating still needs help.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_STILL_NEED_HELP = 4;
 
     /**
@@ -1428,12 +1436,16 @@
      * is the last message to emergency service provider indicating no more help is needed.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int DATAGRAM_TYPE_LAST_SOS_MESSAGE_NO_HELP_NEEDED = 5;
 
     /**
      * Datagram type indicating that the message to be sent or received is of type SMS.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int DATAGRAM_TYPE_SMS = 6;
 
     /**
@@ -1441,6 +1453,8 @@
      * for pending incoming SMS.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int DATAGRAM_TYPE_CHECK_PENDING_INCOMING_SMS = 7;
 
     /** @hide */
@@ -1461,6 +1475,8 @@
      * Satellite communication restricted by user.
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
     public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER = 0;
 
     /**
diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
index 862886c..e281a3f 100644
--- a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
@@ -61,13 +61,13 @@
         assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS, result)
         assertEquals(
             listOf(customGesture),
-            inputGestureManager.getCustomInputGestures(USER_ID)
+            inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
         )
 
         inputGestureManager.removeCustomInputGesture(USER_ID, customGesture)
         assertEquals(
             listOf<InputGestureData>(),
-            inputGestureManager.getCustomInputGestures(USER_ID)
+            inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
         )
     }
 
@@ -86,7 +86,7 @@
         assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST, result)
         assertEquals(
             listOf<InputGestureData>(),
-            inputGestureManager.getCustomInputGestures(USER_ID)
+            inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
         )
     }
 
@@ -115,7 +115,7 @@
         assertEquals(InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_ALREADY_EXISTS, result)
         assertEquals(
             listOf(customGesture),
-            inputGestureManager.getCustomInputGestures(USER_ID)
+            inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
         )
     }
 
@@ -144,13 +144,67 @@
 
         assertEquals(
             listOf(customGesture, customGesture2),
-            inputGestureManager.getCustomInputGestures(USER_ID)
+            inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
         )
 
-        inputGestureManager.removeAllCustomInputGestures(USER_ID)
+        inputGestureManager.removeAllCustomInputGestures(USER_ID, /* filter = */null)
         assertEquals(
             listOf<InputGestureData>(),
-            inputGestureManager.getCustomInputGestures(USER_ID)
+            inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
+        )
+    }
+
+    @Test
+    fun filteringBasedOnTouchpadOrKeyGestures() {
+        val customKeyGesture = InputGestureData.Builder()
+            .setTrigger(
+                InputGestureData.createKeyTrigger(
+                    KeyEvent.KEYCODE_H,
+                    KeyEvent.META_META_ON
+                )
+            )
+            .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+            .build()
+        inputGestureManager.addCustomInputGesture(USER_ID, customKeyGesture)
+        val customTouchpadGesture = InputGestureData.Builder()
+            .setTrigger(
+                InputGestureData.createTouchpadTrigger(
+                    InputGestureData.TOUCHPAD_GESTURE_TYPE_THREE_FINGER_TAP
+                )
+            )
+            .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_BACK)
+            .build()
+        inputGestureManager.addCustomInputGesture(USER_ID, customTouchpadGesture)
+
+        assertEquals(
+            listOf(customTouchpadGesture, customKeyGesture),
+            inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
+        )
+        assertEquals(
+            listOf(customKeyGesture),
+            inputGestureManager.getCustomInputGestures(USER_ID, InputGestureData.Filter.KEY)
+        )
+        assertEquals(
+            listOf(customTouchpadGesture),
+            inputGestureManager.getCustomInputGestures(
+                USER_ID,
+                InputGestureData.Filter.TOUCHPAD
+            )
+        )
+
+        inputGestureManager.removeAllCustomInputGestures(USER_ID, InputGestureData.Filter.KEY)
+        assertEquals(
+            listOf(customTouchpadGesture),
+            inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
+        )
+
+        inputGestureManager.removeAllCustomInputGestures(
+            USER_ID,
+            InputGestureData.Filter.TOUCHPAD
+        )
+        assertEquals(
+            listOf<InputGestureData>(),
+            inputGestureManager.getCustomInputGestures(USER_ID, /* filter = */null)
         )
     }
 }
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 1574d1b..f317939c 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -995,11 +995,28 @@
                 intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
                 AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
             ),
+            TestData(
+                "LOCK -> Lock Screen",
+                intArrayOf(KeyEvent.KEYCODE_LOCK),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+                intArrayOf(KeyEvent.KEYCODE_LOCK),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
+            TestData(
+                "FULLSCREEN -> Maximizes a task to fit the screen",
+                intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
+                0,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            ),
         )
     }
 
     @Test
     @Parameters(method = "systemKeysTestArguments")
+    @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_NEW_25Q2_KEYCODES)
     fun testSystemKeys(test: TestData) {
         setupKeyGestureController()
         testKeyGestureInternal(test)
@@ -1029,6 +1046,9 @@
             KeyEvent.KEYCODE_STYLUS_BUTTON_SECONDARY,
             KeyEvent.KEYCODE_STYLUS_BUTTON_TERTIARY,
             KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL,
+            KeyEvent.KEYCODE_DO_NOT_DISTURB,
+            KeyEvent.KEYCODE_LOCK,
+            KeyEvent.KEYCODE_FULLSCREEN
         )
 
         val handler = KeyGestureHandler { _, _ -> false }
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 07b7338..0da4521 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorManagerServicePermissionTest.java
@@ -143,6 +143,38 @@
     }
 
     @Test
+    public void testStartVendorVibrationSessionWithoutVibratePermissionFails() throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.VIBRATE_VENDOR_EFFECTS,
+                Manifest.permission.START_VIBRATION_SESSIONS);
+        expectSecurityException("VIBRATE");
+        mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME,
+                new int[] { 1 }, ATTRS, "testVibrate", null);
+    }
+
+    @Test
+    public void testStartVendorVibrationSessionWithoutVibrateVendorEffectsPermissionFails()
+            throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.VIBRATE,
+                Manifest.permission.START_VIBRATION_SESSIONS);
+        expectSecurityException("VIBRATE");
+        mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME,
+                new int[] { 1 }, ATTRS, "testVibrate", null);
+    }
+
+    @Test
+    public void testStartVendorVibrationSessionWithoutStartSessionPermissionFails()
+            throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.VIBRATE,
+                Manifest.permission.VIBRATE_VENDOR_EFFECTS);
+        expectSecurityException("VIBRATE");
+        mVibratorService.startVendorVibrationSession(Process.myUid(), DEVICE_ID, PACKAGE_NAME,
+                new int[] { 1 }, ATTRS, "testVibrate", null);
+    }
+
+    @Test
     public void testCancelVibrateFails() throws RemoteException {
         expectSecurityException("VIBRATE");
         mVibratorService.cancelVibrate(/* usageFilter= */ -1, new Binder());
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
index 49665f7..613b926 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java
@@ -360,12 +360,10 @@
 
     private void verifyGetSafeModeTimeoutMs(
             boolean isInTestMode,
-            boolean isConfigTimeoutSupported,
             PersistableBundleWrapper carrierConfig,
             long expectedTimeoutMs)
             throws Exception {
         doReturn(isInTestMode).when(mVcnContext).isInTestMode();
-        doReturn(isConfigTimeoutSupported).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
 
         final TelephonySubscriptionSnapshot snapshot = mock(TelephonySubscriptionSnapshot.class);
         doReturn(carrierConfig).when(snapshot).getCarrierConfigForSubGrp(TEST_SUB_GRP);
@@ -377,16 +375,7 @@
     }
 
     @Test
-    public void testGetSafeModeTimeoutMs_configTimeoutUnsupported() throws Exception {
-        verifyGetSafeModeTimeoutMs(
-                false /* isInTestMode */,
-                false /* isConfigTimeoutSupported */,
-                null /* carrierConfig */,
-                TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
-    }
-
-    @Test
-    public void testGetSafeModeTimeoutMs_configTimeoutSupported() throws Exception {
+    public void testGetSafeModeTimeoutMs() throws Exception {
         final int carrierConfigTimeoutSeconds = 20;
         final PersistableBundleWrapper carrierConfig = mock(PersistableBundleWrapper.class);
         doReturn(carrierConfigTimeoutSeconds)
@@ -395,17 +384,14 @@
 
         verifyGetSafeModeTimeoutMs(
                 false /* isInTestMode */,
-                true /* isConfigTimeoutSupported */,
                 carrierConfig,
                 TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
     }
 
     @Test
-    public void testGetSafeModeTimeoutMs_configTimeoutSupported_carrierConfigNull()
-            throws Exception {
+    public void testGetSafeModeTimeoutMs_carrierConfigNull() throws Exception {
         verifyGetSafeModeTimeoutMs(
                 false /* isInTestMode */,
-                true /* isConfigTimeoutSupported */,
                 null /* carrierConfig */,
                 TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS));
     }
@@ -420,7 +406,6 @@
 
         verifyGetSafeModeTimeoutMs(
                 true /* isInTestMode */,
-                true /* isConfigTimeoutSupported */,
                 carrierConfig,
                 TimeUnit.SECONDS.toMillis(carrierConfigTimeoutSeconds));
     }
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index 4c7b25a..8374fd9 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -222,7 +222,6 @@
         doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper();
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
         doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
-        doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index 1abe77f..f260e27 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -188,7 +188,7 @@
                         ?: throw IllegalArgumentException(
                             "Invalid feature version input for $name: ${featureArgs[1]}"
                         )
-                FeatureInfo(name, featureArgs[1].toInt(), readonly = true)
+                FeatureInfo(name, featureVersion, readonly = true)
             }
         }
     }