Merge "Use different bg drawable for settings/history btn" into main
diff --git a/OWNERS b/OWNERS
index eb2bfcf..d0a634e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -47,3 +47,4 @@
 per-file BROADCASTS_OWNERS = file:/BROADCASTS_OWNERS
 per-file ADPF_OWNERS = file:/ADPF_OWNERS
 per-file GAME_MANAGER_OWNERS = file:/GAME_MANAGER_OWNERS
+per-file SDK_OWNERS = file:/SDK_OWNERS
diff --git a/SDK_OWNERS b/SDK_OWNERS
new file mode 100644
index 0000000..c9ca47a
--- /dev/null
+++ b/SDK_OWNERS
@@ -0,0 +1,6 @@
+amhk@google.com
+kimalexander@google.com
+lus@google.com
+michaelwr@google.com
+nanaasiedu@google.com
+paulduffin@google.com
diff --git a/android-sdk-flags/OWNERS b/android-sdk-flags/OWNERS
new file mode 100644
index 0000000..01f45dd
--- /dev/null
+++ b/android-sdk-flags/OWNERS
@@ -0,0 +1 @@
+include /SDK_OWNERS
diff --git a/android-sdk-flags/flags.aconfig b/android-sdk-flags/flags.aconfig
index cfe298e..19c7bf6 100644
--- a/android-sdk-flags/flags.aconfig
+++ b/android-sdk-flags/flags.aconfig
@@ -6,6 +6,7 @@
     namespace: "android_sdk"
     description: "Use the new SDK major.minor versioning scheme (e.g. Android 40.1) which replaces the old single-integer scheme (e.g. Android 15)."
     bug: "350458259"
+    is_exported: true
 
     # Use is_fixed_read_only because DeviceConfig may not be available when Build.VERSION_CODES is first accessed
     is_fixed_read_only: true
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index debd850..79aef1e 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -37,3 +37,11 @@
     description: "Ignore the important_while_foreground flag and change the related APIs to be not effective"
     bug: "374175032"
 }
+
+flag {
+    name: "get_pending_job_reasons_api"
+    is_exported: true
+    namespace: "backstage_power"
+    description: "Introduce a new getPendingJobReasons() API which returns reasons why a job may not have executed. Also deprecate the existing getPendingJobReason() API."
+    bug: "372031023"
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 790fc6b..909a9b3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -618,6 +618,26 @@
         return mRunningJob;
     }
 
+    @VisibleForTesting
+    void setRunningJobLockedForTest(JobStatus job) {
+        mRunningJob = job;
+    }
+
+    @VisibleForTesting
+    void setJobParamsLockedForTest(JobParameters params) {
+        mParams = params;
+    }
+
+    @VisibleForTesting
+    void setRunningCallbackLockedForTest(JobCallback callback) {
+        mRunningCallback = callback;
+    }
+
+    @VisibleForTesting
+    void setPendingStopReasonLockedForTest(int stopReason) {
+        mPendingStopReason = stopReason;
+    }
+
     @JobConcurrencyManager.WorkType
     int getRunningJobWorkType() {
         return mRunningJobWorkType;
@@ -786,6 +806,7 @@
                 executing = getRunningJobLocked();
             }
             if (executing != null && jobId == executing.getJobId()) {
+                executing.setAbandoned(true);
                 final StringBuilder stateSuffix = new StringBuilder();
                 stateSuffix.append(TRACE_ABANDONED_JOB);
                 stateSuffix.append(executing.getBatteryName());
@@ -1364,8 +1385,9 @@
     }
 
     /** Process MSG_TIMEOUT here. */
+    @VisibleForTesting
     @GuardedBy("mLock")
-    private void handleOpTimeoutLocked() {
+    void handleOpTimeoutLocked() {
         switch (mVerb) {
             case VERB_BINDING:
                 // The system may have been too busy. Don't drop the job or trigger an ANR.
@@ -1427,9 +1449,25 @@
                     // Not an error - client ran out of time.
                     Slog.i(TAG, "Client timed out while executing (no jobFinished received)."
                             + " Sending onStop: " + getRunningJobNameLocked());
-                    mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
-                            JobParameters.INTERNAL_STOP_REASON_TIMEOUT, "client timed out");
-                    sendStopMessageLocked("timeout while executing");
+
+                    final JobStatus executing = getRunningJobLocked();
+                    int stopReason = JobParameters.STOP_REASON_TIMEOUT;
+                    int internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT;
+                    final StringBuilder stopMessage = new StringBuilder("timeout while executing");
+                    final StringBuilder debugStopReason = new StringBuilder("client timed out");
+
+                    if (android.app.job.Flags.handleAbandonedJobs()
+                            && executing != null && executing.isAbandoned()) {
+                        final String abandonedMessage = " and maybe abandoned";
+                        stopReason = JobParameters.STOP_REASON_TIMEOUT_ABANDONED;
+                        internalStopReason = JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED;
+                        stopMessage.append(abandonedMessage);
+                        debugStopReason.append(abandonedMessage);
+                    }
+
+                    mParams.setStopReason(stopReason,
+                                    internalStopReason, debugStopReason.toString());
+                    sendStopMessageLocked(stopMessage.toString());
                 } else if (nowElapsed >= earliestStopTimeElapsed) {
                     // We've given the app the minimum execution time. See if we should stop it or
                     // let it continue running
@@ -1479,8 +1517,9 @@
      * Already running, need to stop. Will switch {@link #mVerb} from VERB_EXECUTING ->
      * VERB_STOPPING.
      */
+    @VisibleForTesting
     @GuardedBy("mLock")
-    private void sendStopMessageLocked(@Nullable String reason) {
+    void sendStopMessageLocked(@Nullable String reason) {
         removeOpTimeOutLocked();
         if (mVerb != VERB_EXECUTING) {
             Slog.e(TAG, "Sending onStopJob for a job that isn't started. " + mRunningJob);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index e3af1d8..1dc5a71 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -576,6 +576,13 @@
     private String mSystemTraceTag;
 
     /**
+     * Job maybe abandoned by not calling
+     * {@link android.app.job.JobService#jobFinished(JobParameters, boolean)} while
+     * the strong reference to {@link android.app.job.JobParameters} is lost
+     */
+    private boolean mIsAbandoned;
+
+    /**
      * Core constructor for JobStatus instances.  All other ctors funnel down to this one.
      *
      * @param job The actual requested parameters for the job
@@ -725,6 +732,8 @@
         updateNetworkBytesLocked();
 
         updateMediaBackupExemptionStatus();
+
+        mIsAbandoned = false;
     }
 
     /** Copy constructor: used specifically when cloning JobStatus objects for persistence,
@@ -1061,6 +1070,16 @@
         return job.getTraceTag();
     }
 
+    /** Returns if the job maybe abandoned */
+    public boolean isAbandoned() {
+        return mIsAbandoned;
+    }
+
+    /** Set the job maybe abandoned state*/
+    public void setAbandoned(boolean abandoned) {
+        mIsAbandoned = abandoned;
+    }
+
     /** Returns a trace tag using debug information provided by job scheduler service. */
     @NonNull
     public String computeSystemTraceTag() {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index ff4af69..8bd3ef4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -121,6 +121,9 @@
     private static final String ALARM_TAG_CLEANUP = "*job.cleanup*";
     private static final String ALARM_TAG_QUOTA_CHECK = "*job.quota_check*";
 
+    private static final String TRACE_QUOTA_STATE_CHANGED_TAG = "QuotaStateChanged:";
+    private static final String TRACE_QUOTA_STATE_CHANGED_DELIMITER = "#";
+
     private static final int SYSTEM_APP_CHECK_FLAGS =
             PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                     | PackageManager.GET_PERMISSIONS | PackageManager.MATCH_KNOWN_PACKAGES;
@@ -2657,11 +2660,12 @@
                         if (timeRemainingMs <= 50) {
                             // Less than 50 milliseconds left. Start process of shutting down jobs.
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its quota.");
-                            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
-                                Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
-                                        JobSchedulerService.TRACE_TRACK_NAME,
-                                        pkg + "#" + MSG_REACHED_TIME_QUOTA);
-                            }
+                            final StringBuilder traceMsg = new StringBuilder();
+                            traceMsg.append(TRACE_QUOTA_STATE_CHANGED_TAG)
+                                    .append(pkg)
+                                    .append(TRACE_QUOTA_STATE_CHANGED_DELIMITER)
+                                    .append(MSG_REACHED_TIME_QUOTA);
+                            Trace.instant(Trace.TRACE_TAG_POWER, traceMsg.toString());
                             mStateChangedListener.onControllerStateChanged(
                                     maybeUpdateConstraintForPkgLocked(
                                             sElapsedRealtimeClock.millis(),
@@ -2690,11 +2694,12 @@
                                 pkg.userId, pkg.packageName);
                         if (timeRemainingMs <= 0) {
                             if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota.");
-                            if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
-                                Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
-                                        JobSchedulerService.TRACE_TRACK_NAME,
-                                        pkg + "#" + MSG_REACHED_EJ_TIME_QUOTA);
-                            }
+                            final StringBuilder traceMsg = new StringBuilder();
+                            traceMsg.append(TRACE_QUOTA_STATE_CHANGED_TAG)
+                                    .append(pkg)
+                                    .append(TRACE_QUOTA_STATE_CHANGED_DELIMITER)
+                                    .append(MSG_REACHED_EJ_TIME_QUOTA);
+                            Trace.instant(Trace.TRACE_TAG_POWER, traceMsg.toString());
                             mStateChangedListener.onControllerStateChanged(
                                     maybeUpdateConstraintForPkgLocked(
                                             sElapsedRealtimeClock.millis(),
@@ -2719,11 +2724,12 @@
                             Slog.d(TAG, pkg + " has reached its count quota.");
                         }
 
-                        if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
-                            Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER,
-                                    JobSchedulerService.TRACE_TRACK_NAME,
-                                    pkg + "#" + MSG_REACHED_COUNT_QUOTA);
-                        }
+                        final StringBuilder traceMsg = new StringBuilder();
+                        traceMsg.append(TRACE_QUOTA_STATE_CHANGED_TAG)
+                                .append(pkg)
+                                .append(TRACE_QUOTA_STATE_CHANGED_DELIMITER)
+                                .append(MSG_REACHED_COUNT_QUOTA);
+                        Trace.instant(Trace.TRACE_TAG_POWER, traceMsg.toString());
 
                         mStateChangedListener.onControllerStateChanged(
                                 maybeUpdateConstraintForPkgLocked(
diff --git a/boot/boot-image-profile-extra.txt b/boot/boot-image-profile-extra.txt
index 11ca1dc..e31eb3a 100644
--- a/boot/boot-image-profile-extra.txt
+++ b/boot/boot-image-profile-extra.txt
@@ -23,3 +23,4 @@
 # For now, compile all methods in MessageQueue to avoid performance cliffs for
 # flagged/evolving hot code paths. See: b/338098106
 HSPLandroid/os/MessageQueue;->*
+HSPLandroid/os/MessageQueue$*;->*
diff --git a/core/api/current.txt b/core/api/current.txt
index 47ec2f9..0153213 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1006,6 +1006,7 @@
     field public static final int insetRight = 16843192; // 0x10101b8
     field public static final int insetTop = 16843193; // 0x10101b9
     field public static final int installLocation = 16843447; // 0x10102b7
+    field @FlaggedApi("android.security.enable_intent_matching_flags") public static final int intentMatchingFlags;
     field public static final int interactiveUiTimeout = 16844181; // 0x1010595
     field public static final int interpolator = 16843073; // 0x1010141
     field public static final int intro = 16844395; // 0x101066b
@@ -1609,6 +1610,7 @@
     field public static final int summaryColumn = 16843426; // 0x10102a2
     field public static final int summaryOff = 16843248; // 0x10101f0
     field public static final int summaryOn = 16843247; // 0x10101ef
+    field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int supplementalDescription;
     field public static final int supportedTypes = 16844369; // 0x1010651
     field public static final int supportsAssist = 16844016; // 0x10104f0
     field public static final int supportsBatteryGameMode = 16844374; // 0x1010656
@@ -4616,6 +4618,7 @@
     method public void setInheritShowWhenLocked(boolean);
     method public void setIntent(android.content.Intent);
     method @FlaggedApi("android.security.content_uri_permission_apis") public void setIntent(@Nullable android.content.Intent, @Nullable android.app.ComponentCaller);
+    method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public final void setLimitSystemEducationDialogs(boolean);
     method public void setLocusContext(@Nullable android.content.LocusId, @Nullable android.os.Bundle);
     method public final void setMediaController(android.media.session.MediaController);
     method public void setPictureInPictureParams(@NonNull android.app.PictureInPictureParams);
@@ -8783,7 +8786,8 @@
 
   @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 public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+    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>);
     field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
     field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
@@ -8816,6 +8820,7 @@
 
   @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
     method public int describeContents();
+    method public int getErrorCategory();
     method @Nullable public String getErrorMessage();
     method @NonNull public android.os.Bundle getExtras();
     method public int getResultCode();
@@ -8825,14 +8830,19 @@
     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 = 2; // 0x2
-    field public static final int RESULT_CANCELLED = 6; // 0x6
-    field public static final int RESULT_DENIED = 1; // 0x1
-    field public static final int RESULT_DISABLED = 5; // 0x5
-    field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
-    field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+    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
   }
 
 }
@@ -9677,9 +9687,11 @@
     method @Nullable public String getId();
     method @Nullable public android.net.Uri getThumbnail();
     method @Nullable public CharSequence getTitle();
+    method @NonNull public static android.app.wallpaper.WallpaperDescription readFromStream(@NonNull java.io.InputStream) throws java.io.IOException;
     method @NonNull public android.app.wallpaper.WallpaperDescription.Builder toBuilder();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @Nullable public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperDescription> CREATOR;
+    method public void writeToStream(@NonNull java.io.OutputStream) throws java.io.IOException;
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.wallpaper.WallpaperDescription> CREATOR;
   }
 
   public static final class WallpaperDescription.Builder {
@@ -9832,6 +9844,7 @@
     field public static final int RESIZE_VERTICAL = 2; // 0x2
     field public static final int WIDGET_CATEGORY_HOME_SCREEN = 1; // 0x1
     field public static final int WIDGET_CATEGORY_KEYGUARD = 2; // 0x2
+    field @FlaggedApi("android.appwidget.flags.not_keyguard_category") public static final int WIDGET_CATEGORY_NOT_KEYGUARD = 8; // 0x8
     field public static final int WIDGET_CATEGORY_SEARCHBOX = 4; // 0x4
     field public static final int WIDGET_FEATURE_CONFIGURATION_OPTIONAL = 4; // 0x4
     field public static final int WIDGET_FEATURE_HIDE_FROM_PICKER = 2; // 0x2
@@ -10941,6 +10954,7 @@
     field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
     field public static final String RESTRICTIONS_SERVICE = "restrictions";
     field public static final String ROLE_SERVICE = "role";
+    field @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public static final String SATELLITE_SERVICE = "satellite";
     field public static final String SEARCH_SERVICE = "search";
     field @FlaggedApi("android.os.security_state_service") public static final String SECURITY_STATE_SERVICE = "security_state";
     field public static final String SENSOR_SERVICE = "sensor";
@@ -17361,6 +17375,25 @@
     method public boolean setUseCompositingLayer(boolean, @Nullable android.graphics.Paint);
   }
 
+  @FlaggedApi("com.android.graphics.hwui.flags.runtime_color_filters_blenders") public class RuntimeColorFilter extends android.graphics.ColorFilter {
+    ctor public RuntimeColorFilter(@NonNull String);
+    method public void setColorUniform(@NonNull String, @ColorInt int);
+    method public void setColorUniform(@NonNull String, @ColorLong long);
+    method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color);
+    method public void setFloatUniform(@NonNull String, float);
+    method public void setFloatUniform(@NonNull String, float, float);
+    method public void setFloatUniform(@NonNull String, float, float, float);
+    method public void setFloatUniform(@NonNull String, float, float, float, float);
+    method public void setFloatUniform(@NonNull String, @NonNull float[]);
+    method public void setInputColorFilter(@NonNull String, @NonNull android.graphics.ColorFilter);
+    method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+    method public void setIntUniform(@NonNull String, int);
+    method public void setIntUniform(@NonNull String, int, int);
+    method public void setIntUniform(@NonNull String, int, int, int);
+    method public void setIntUniform(@NonNull String, int, int, int, int);
+    method public void setIntUniform(@NonNull String, @NonNull int[]);
+  }
+
   public class RuntimeShader extends android.graphics.Shader {
     ctor public RuntimeShader(@NonNull String);
     method public void setColorUniform(@NonNull String, @ColorInt int);
@@ -19314,6 +19347,8 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> AUTOMOTIVE_LENS_FACING;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> AUTOMOTIVE_LOCATION;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> COLOR_CORRECTION_AVAILABLE_MODES;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>> COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_ANTIBANDING_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AE_AVAILABLE_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Integer>[]> CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES;
@@ -19615,6 +19650,7 @@
     field public static final int COLOR_CORRECTION_ABERRATION_MODE_FAST = 1; // 0x1
     field public static final int COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY = 2; // 0x2
     field public static final int COLOR_CORRECTION_ABERRATION_MODE_OFF = 0; // 0x0
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") public static final int COLOR_CORRECTION_MODE_CCT = 3; // 0x3
     field public static final int COLOR_CORRECTION_MODE_FAST = 1; // 0x1
     field public static final int COLOR_CORRECTION_MODE_HIGH_QUALITY = 2; // 0x2
     field public static final int COLOR_CORRECTION_MODE_TRANSFORM_MATRIX = 0; // 0x0
@@ -19901,6 +19937,8 @@
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Boolean> BLACK_LEVEL_LOCK;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_ABERRATION_MODE;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_COLOR_TEMPERATURE;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_COLOR_TINT;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> COLOR_CORRECTION_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM;
@@ -19991,6 +20029,8 @@
     method public int getSequenceId();
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> BLACK_LEVEL_LOCK;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> COLOR_CORRECTION_ABERRATION_MODE;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> COLOR_CORRECTION_COLOR_TEMPERATURE;
+    field @FlaggedApi("com.android.internal.camera.flags.color_temperature") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> COLOR_CORRECTION_COLOR_TINT;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.RggbChannelVector> COLOR_CORRECTION_GAINS;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> COLOR_CORRECTION_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.hardware.camera2.params.ColorSpaceTransform> COLOR_CORRECTION_TRANSFORM;
@@ -21372,6 +21412,7 @@
     field public static final int TYPE_IP = 20; // 0x14
     field public static final int TYPE_LINE_ANALOG = 5; // 0x5
     field public static final int TYPE_LINE_DIGITAL = 6; // 0x6
+    field @FlaggedApi("android.media.audio.enable_multichannel_group_device") public static final int TYPE_MULTICHANNEL_GROUP = 32; // 0x20
     field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19
     field public static final int TYPE_TELEPHONY = 18; // 0x12
     field public static final int TYPE_TV_TUNER = 17; // 0x11
@@ -23089,6 +23130,65 @@
     field public static final int AC4Profile11 = 514; // 0x202
     field public static final int AC4Profile21 = 1026; // 0x402
     field public static final int AC4Profile22 = 1028; // 0x404
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band0 = 513; // 0x201
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band1 = 514; // 0x202
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band2 = 516; // 0x204
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel11Band3 = 520; // 0x208
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band0 = 257; // 0x101
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band1 = 258; // 0x102
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band2 = 260; // 0x104
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel1Band3 = 264; // 0x108
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band0 = 2049; // 0x801
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band1 = 2050; // 0x802
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band2 = 2052; // 0x804
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel21Band3 = 2056; // 0x808
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band0 = 1025; // 0x401
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band1 = 1026; // 0x402
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band2 = 1028; // 0x404
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel2Band3 = 1032; // 0x408
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band0 = 8193; // 0x2001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band1 = 8194; // 0x2002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band2 = 8196; // 0x2004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel31Band3 = 8200; // 0x2008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band0 = 4097; // 0x1001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band1 = 4098; // 0x1002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band2 = 4100; // 0x1004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel3Band3 = 4104; // 0x1008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band0 = 32769; // 0x8001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band1 = 32770; // 0x8002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band2 = 32772; // 0x8004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel41Band3 = 32776; // 0x8008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band0 = 16385; // 0x4001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band1 = 16386; // 0x4002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band2 = 16388; // 0x4004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel4Band3 = 16392; // 0x4008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band0 = 131073; // 0x20001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band1 = 131074; // 0x20002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band2 = 131076; // 0x20004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel51Band3 = 131080; // 0x20008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band0 = 65537; // 0x10001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band1 = 65538; // 0x10002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band2 = 65540; // 0x10004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel5Band3 = 65544; // 0x10008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band0 = 524289; // 0x80001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band1 = 524290; // 0x80002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band2 = 524292; // 0x80004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel61Band3 = 524296; // 0x80008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band0 = 262145; // 0x40001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band1 = 262146; // 0x40002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band2 = 262148; // 0x40004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel6Band3 = 262152; // 0x40008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band0 = 2097153; // 0x200001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band1 = 2097154; // 0x200002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band2 = 2097156; // 0x200004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel71Band3 = 2097160; // 0x200008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band0 = 1048577; // 0x100001
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band1 = 1048578; // 0x100002
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band2 = 1048580; // 0x100004
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVLevel7Band3 = 1048584; // 0x100008
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVProfile422_10 = 1; // 0x1
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVProfile422_10HDR10 = 4096; // 0x1000
+    field @FlaggedApi("android.media.codec.apv_support") public static final int APVProfile422_10HDR10Plus = 8192; // 0x2000
     field public static final int AV1Level2 = 1; // 0x1
     field public static final int AV1Level21 = 2; // 0x2
     field public static final int AV1Level22 = 4; // 0x4
@@ -23939,6 +24039,7 @@
     field public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
     field public static final String MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
     field public static final String MIMETYPE_TEXT_VTT = "text/vtt";
+    field @FlaggedApi("android.media.codec.apv_support") public static final String MIMETYPE_VIDEO_APV = "video/apv";
     field public static final String MIMETYPE_VIDEO_AV1 = "video/av01";
     field public static final String MIMETYPE_VIDEO_AVC = "video/avc";
     field public static final String MIMETYPE_VIDEO_DOLBY_VISION = "video/dolby-vision";
@@ -24568,6 +24669,7 @@
     field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_ARC = 10; // 0xa
     field @FlaggedApi("com.android.media.flags.enable_audio_policies_device_and_bluetooth_controller") public static final int TYPE_HDMI_EARC = 29; // 0x1d
     field public static final int TYPE_HEARING_AID = 23; // 0x17
+    field @FlaggedApi("android.media.audio.enable_multichannel_group_device") public static final int TYPE_MULTICHANNEL_SPEAKER_GROUP = 32; // 0x20
     field public static final int TYPE_REMOTE_AUDIO_VIDEO_RECEIVER = 1003; // 0x3eb
     field @FlaggedApi("com.android.media.flags.enable_new_media_route_2_info_types") public static final int TYPE_REMOTE_CAR = 1008; // 0x3f0
     field @FlaggedApi("com.android.media.flags.enable_new_media_route_2_info_types") public static final int TYPE_REMOTE_COMPUTER = 1006; // 0x3ee
@@ -34435,6 +34537,7 @@
     method public int describeContents();
     method @NonNull public static android.os.VibrationEffect.Composition startComposition();
     method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope();
+    method @FlaggedApi("android.os.vibrator.normalized_pwle_effects") @NonNull public static android.os.VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(@FloatRange(from=0) float);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect> CREATOR;
     field public static final int DEFAULT_AMPLITUDE = -1; // 0xffffffff
     field public static final int EFFECT_CLICK = 0; // 0x0
@@ -37199,7 +37302,7 @@
   }
 
   public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
-    method @Nullable public static android.accounts.Account getDefaultAccount(@NonNull android.content.ContentResolver);
+    method @Deprecated @FlaggedApi("android.provider.new_default_account_api_enabled") @Nullable public static android.accounts.Account getDefaultAccount(@NonNull android.content.ContentResolver);
     field public static final String ACTION_SET_DEFAULT_ACCOUNT = "android.provider.action.SET_DEFAULT_ACCOUNT";
     field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/setting";
@@ -39815,7 +39918,7 @@
 
 package android.security.advancedprotection {
 
-  @FlaggedApi("android.security.aapm_api") public class AdvancedProtectionManager {
+  @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionManager {
     method @RequiresPermission(android.Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE) public boolean isAdvancedProtectionEnabled();
     method @RequiresPermission(android.Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE) public void registerAdvancedProtectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.security.advancedprotection.AdvancedProtectionManager.Callback);
     method @RequiresPermission(android.Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE) public void unregisterAdvancedProtectionCallback(@NonNull android.security.advancedprotection.AdvancedProtectionManager.Callback);
@@ -42086,6 +42189,7 @@
     ctor public WallpaperService();
     method public final android.os.IBinder onBind(android.content.Intent);
     method @MainThread public abstract android.service.wallpaper.WallpaperService.Engine onCreateEngine();
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @MainThread @Nullable public android.service.wallpaper.WallpaperService.Engine onCreateEngine(@NonNull android.app.wallpaper.WallpaperDescription);
     field public static final String SERVICE_INTERFACE = "android.service.wallpaper.WallpaperService";
     field public static final String SERVICE_META_DATA = "android.service.wallpaper";
   }
@@ -42101,6 +42205,7 @@
     method public boolean isPreview();
     method public boolean isVisible();
     method public void notifyColorsChanged();
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable public android.app.wallpaper.WallpaperDescription onApplyWallpaper(int);
     method @MainThread public void onApplyWindowInsets(android.view.WindowInsets);
     method @MainThread public android.os.Bundle onCommand(String, int, int, int, android.os.Bundle, boolean);
     method @MainThread @Nullable public android.app.WallpaperColors onComputeColors();
@@ -47771,6 +47876,19 @@
 
 }
 
+package android.telephony.satellite {
+
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") @RequiresPermission(anyOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public void registerStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateChangeListener);
+    method @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") @RequiresPermission(anyOf={android.Manifest.permission.READ_BASIC_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE, "carrier privileges"}) public void unregisterStateChangeListener(@NonNull android.telephony.satellite.SatelliteStateChangeListener);
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public interface SatelliteStateChangeListener {
+    method public void onEnabledStateChanged(boolean);
+  }
+
+}
+
 package android.text {
 
   @Deprecated public class AlteredCharSequence implements java.lang.CharSequence android.text.GetChars {
@@ -53129,6 +53247,7 @@
     method public android.animation.StateListAnimator getStateListAnimator();
     method protected int getSuggestedMinimumHeight();
     method protected int getSuggestedMinimumWidth();
+    method @FlaggedApi("android.view.accessibility.supplemental_description") @Nullable public CharSequence getSupplementalDescription();
     method @NonNull public java.util.List<android.graphics.Rect> getSystemGestureExclusionRects();
     method @Deprecated public int getSystemUiVisibility();
     method public Object getTag();
@@ -53509,6 +53628,7 @@
     method public void setSoundEffectsEnabled(boolean);
     method public void setStateDescription(@Nullable CharSequence);
     method public void setStateListAnimator(android.animation.StateListAnimator);
+    method @FlaggedApi("android.view.accessibility.supplemental_description") public void setSupplementalDescription(@Nullable CharSequence);
     method public void setSystemGestureExclusionRects(@NonNull java.util.List<android.graphics.Rect>);
     method @Deprecated public void setSystemUiVisibility(int);
     method public void setTag(Object);
@@ -54063,6 +54183,7 @@
     method public void onStopNestedScroll(android.view.View);
     method public void onViewAdded(android.view.View);
     method public void onViewRemoved(android.view.View);
+    method @FlaggedApi("android.view.flags.toolkit_viewgroup_set_requested_frame_rate_api") public void propagateRequestedFrameRate(float, boolean);
     method public void recomputeViewAttributes(android.view.View);
     method public void removeAllViews();
     method public void removeAllViewsInLayout();
@@ -55115,6 +55236,7 @@
     field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
     field public static final int CONTENT_CHANGE_TYPE_STATE_DESCRIPTION = 64; // 0x40
     field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
+    field @FlaggedApi("android.view.accessibility.supplemental_description") public static final int CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION = 32768; // 0x8000
     field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
     field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0
     field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityEvent> CREATOR;
@@ -55277,6 +55399,7 @@
     method @Nullable public android.view.accessibility.AccessibilityNodeInfo getParent(int);
     method public android.view.accessibility.AccessibilityNodeInfo.RangeInfo getRangeInfo();
     method @Nullable public CharSequence getStateDescription();
+    method @FlaggedApi("android.view.accessibility.supplemental_description") @Nullable public CharSequence getSupplementalDescription();
     method public CharSequence getText();
     method public int getTextSelectionEnd();
     method public int getTextSelectionStart();
@@ -55299,6 +55422,7 @@
     method public boolean isDismissable();
     method public boolean isEditable();
     method public boolean isEnabled();
+    method @FlaggedApi("android.view.accessibility.a11y_is_required_api") public boolean isFieldRequired();
     method public boolean isFocusable();
     method public boolean isFocused();
     method @FlaggedApi("android.view.accessibility.granular_scrolling") public boolean isGranularScrollingSupported();
@@ -55353,6 +55477,7 @@
     method public void setEnabled(boolean);
     method public void setError(CharSequence);
     method @FlaggedApi("android.view.accessibility.a11y_expansion_state_api") public void setExpandedState(int);
+    method @FlaggedApi("android.view.accessibility.a11y_is_required_api") public void setFieldRequired(boolean);
     method public void setFocusable(boolean);
     method public void setFocused(boolean);
     method @FlaggedApi("android.view.accessibility.granular_scrolling") public void setGranularScrollingSupported(boolean);
@@ -55385,6 +55510,7 @@
     method public void setSource(android.view.View);
     method public void setSource(android.view.View, int);
     method public void setStateDescription(@Nullable CharSequence);
+    method @FlaggedApi("android.view.accessibility.supplemental_description") public void setSupplementalDescription(@Nullable CharSequence);
     method public void setText(CharSequence);
     method public void setTextEntryKey(boolean);
     method public void setTextSelectable(boolean);
@@ -55587,6 +55713,7 @@
     method public int getType();
     method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float);
     field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
+    field @FlaggedApi("android.view.accessibility.indeterminate_range_info") public static final int RANGE_TYPE_INDETERMINATE = 3; // 0x3
     field public static final int RANGE_TYPE_INT = 0; // 0x0
     field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
   }
@@ -61921,6 +62048,11 @@
     method public void markSyncReady();
   }
 
+  @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") public final class SystemOnBackInvokedCallbacks {
+    method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull android.app.Activity);
+    method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback moveTaskToBackCallback(@NonNull android.app.Activity);
+  }
+
   @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable {
     ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int);
     method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 4c9ce07..bc73220 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -102,6 +102,7 @@
     method @NonNull public android.os.UserHandle getUser();
     field public static final String PAC_PROXY_SERVICE = "pac_proxy";
     field public static final String TEST_NETWORK_SERVICE = "test_network";
+    field @FlaggedApi("android.os.mainline_vcn_platform_api") public static final String VCN_MANAGEMENT_SERVICE = "vcn_management";
     field @FlaggedApi("android.webkit.update_service_ipc_wrapper") public static final String WEBVIEW_UPDATE_SERVICE = "webviewupdate";
   }
 
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9879e7f..02cd00d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -382,6 +382,7 @@
     field public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
     field public static final String SHUTDOWN = "android.permission.SHUTDOWN";
     field public static final String SIGNAL_REBOOT_READINESS = "android.permission.SIGNAL_REBOOT_READINESS";
+    field @FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing") public static final String SINGLE_USER_TIS_ACCESS = "android.permission.SINGLE_USER_TIS_ACCESS";
     field public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission.SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
     field public static final String STAGE_HEALTH_CONNECT_REMOTE_DATA = "android.permission.STAGE_HEALTH_CONNECT_REMOTE_DATA";
     field public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission.START_ACTIVITIES_FROM_BACKGROUND";
@@ -703,6 +704,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_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";
     field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
@@ -1265,8 +1267,10 @@
   public class WallpaperManager {
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int);
     method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @Nullable @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.app.wallpaper.WallpaperInstance getWallpaperInstance(int);
     method public void setDisplayOffset(android.os.IBinder, int, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponent(android.content.ComponentName);
+    method @FlaggedApi("android.app.live_wallpaper_content_handling") @RequiresPermission(allOf={android.Manifest.permission.SET_WALLPAPER_COMPONENT, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public boolean setWallpaperComponentWithDescription(@NonNull android.app.wallpaper.WallpaperDescription, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT) public boolean setWallpaperComponentWithFlags(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public void setWallpaperDimAmount(@FloatRange(from=0.0f, to=1.0f) float);
   }
@@ -7388,7 +7392,7 @@
     field public static final String EXTRA_VOLUME_STREAM_VALUE = "android.media.EXTRA_VOLUME_STREAM_VALUE";
     field public static final int FLAG_BLUETOOTH_ABS_VOLUME = 64; // 0x40
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int STREAM_ASSISTANT = 11; // 0xb
-    field public static final int STREAM_BLUETOOTH_SCO = 6; // 0x6
+    field @Deprecated @FlaggedApi("android.media.audio.deprecate_stream_bt_sco") public static final int STREAM_BLUETOOTH_SCO = 6; // 0x6
     field public static final int SUCCESS = 0; // 0x0
   }
 
@@ -7605,7 +7609,7 @@
   public final class MediaRecorder.AudioSource {
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int ECHO_REFERENCE = 1997; // 0x7cd
     field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) public static final int HOTWORD = 1999; // 0x7cf
-    field @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
+    field @RequiresPermission(android.Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT) public static final int RADIO_TUNER = 1998; // 0x7ce
     field @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public static final int ULTRASOUND = 2000; // 0x7d0
   }
 
@@ -8162,6 +8166,7 @@
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPid(@NonNull String);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int, @NonNull String);
     method @RequiresPermission("android.permission.TUNER_RESOURCE_ACCESS") public int getClientPriority(int);
+    method @FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing") @RequiresPermission(android.Manifest.permission.SINGLE_USER_TIS_ACCESS) public int getClientUserId(@NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO) public java.util.List<android.media.tv.TunedInfo> getCurrentTunedInfos();
     method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
     method @Nullable @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE) public android.os.IBinder getExtensionInterface(@NonNull String, @NonNull String);
@@ -12110,7 +12115,7 @@
   }
 
   public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
-    method @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccount(@NonNull android.content.ContentResolver, @Nullable android.accounts.Account);
+    method @Deprecated @FlaggedApi("android.provider.new_default_account_api_enabled") @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS) public static void setDefaultAccount(@NonNull android.content.ContentResolver, @Nullable android.accounts.Account);
   }
 
   public static final class ContactsContract.SimContacts {
@@ -12467,7 +12472,16 @@
 
 package android.security.advancedprotection {
 
-  @FlaggedApi("android.security.aapm_api") public class AdvancedProtectionManager {
+  @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionFeature implements android.os.Parcelable {
+    ctor public AdvancedProtectionFeature(@NonNull String);
+    method public int describeContents();
+    method @NonNull public String getId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.security.advancedprotection.AdvancedProtectionFeature> CREATOR;
+  }
+
+  @FlaggedApi("android.security.aapm_api") public final class AdvancedProtectionManager {
+    method @NonNull @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public java.util.List<android.security.advancedprotection.AdvancedProtectionFeature> getAdvancedProtectionFeatures();
     method @RequiresPermission(android.Manifest.permission.SET_ADVANCED_PROTECTION_MODE) public void setAdvancedProtectionEnabled(boolean);
   }
 
@@ -15594,9 +15608,9 @@
   }
 
   @FlaggedApi("com.android.internal.telephony.flags.emergency_callback_mode_notification") public static interface TelephonyCallback.EmergencyCallbackModeListener {
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onCallbackModeRestarted(int, @NonNull java.time.Duration, int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onCallbackModeStarted(int, @NonNull java.time.Duration, int);
-    method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onCallbackModeStopped(int, int, int);
+    method public void onCallbackModeRestarted(int, @NonNull java.time.Duration, int);
+    method public void onCallbackModeStarted(int, @NonNull java.time.Duration, int);
+    method public void onCallbackModeStopped(int, int, int);
   }
 
   public static interface TelephonyCallback.LinkCapacityEstimateChangedListener {
@@ -18245,7 +18259,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onSatelliteDatagramReceived(long, @NonNull android.telephony.satellite.SatelliteDatagram, int, @NonNull java.util.function.Consumer<java.lang.Void>);
   }
 
-  public final class SatelliteManager {
+  @FlaggedApi("com.android.internal.telephony.flags.satellite_state_change_listener") public final class SatelliteManager {
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void addAttachRestrictionForCarrier(int, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a5b5ef3..98d6f58 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1030,6 +1030,7 @@
   public abstract class Context {
     method @NonNull public java.io.File getCrateDir(@NonNull String);
     method public abstract int getDisplayId();
+    method @NonNull public java.util.List<android.content.IntentFilter> getRegisteredIntentFilters(@NonNull android.content.BroadcastReceiver);
     method @NonNull public android.os.UserHandle getUser();
     method public int getUserId();
     method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 4fb35c3..7a1c759 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1262,6 +1262,33 @@
         }
     }
 
+    /**
+     * To make users aware of system features such as the app header menu and its various
+     * functionalities, educational dialogs are shown to demonstrate how to find and utilize these
+     * features. Using this method, an activity can specify if it wants these educational dialogs to
+     * be shown. When set to {@code true}, these dialogs are not completely blocked; however, the
+     * system will be notified that they should not be shown unless necessary. If this API is not
+     * called, the system's educational dialogs are not limited by default.
+     *
+     * <p>This method can be utilized when activities have states where showing an
+     * educational dialog would be disruptive to the user. For example, if a game application is
+     * expecting prompt user input, this method can be used to limit educational dialogs such as the
+     * dialogs that showcase the app header's features which, in this instance, would disrupt the
+     * user's experience if shown.</p>
+     *
+     * <p>Note that educational dialogs may be shown soon after this activity is launched, so
+     * this method must be called early if the intent is to limit the dialogs from the start.</p>
+     */
+    @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
+    public final void setLimitSystemEducationDialogs(boolean limitSystemEducationDialogs) {
+        try {
+            ActivityTaskManager
+                  .getService().setLimitSystemEducationDialogs(mToken, limitSystemEducationDialogs);
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
     /** Return the application that owns this activity. */
     public final Application getApplication() {
         return mApplication;
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 56538d9..2b0e86c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1618,9 +1618,12 @@
     /** @hide Access to read heart rate sensor. */
     public static final int OP_READ_HEART_RATE = AppProtoEnums.APP_OP_READ_HEART_RATE;
 
+    /** @hide Access to read skin temperature. */
+    public static final int OP_READ_SKIN_TEMPERATURE = AppProtoEnums.APP_OP_READ_SKIN_TEMPERATURE;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 150;
+    public static final int _NUM_OP = 151;
 
     /**
      * All app ops represented as strings.
@@ -1774,6 +1777,7 @@
             OPSTR_EMERGENCY_LOCATION,
             OPSTR_RECEIVE_SENSITIVE_NOTIFICATIONS,
             OPSTR_READ_HEART_RATE,
+            OPSTR_READ_SKIN_TEMPERATURE,
     })
     public @interface AppOpString {}
 
@@ -2516,6 +2520,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 skin temperature. */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_PLATFORM_SKIN_TEMPERATURE_ENABLED)
+    public static final String OPSTR_READ_SKIN_TEMPERATURE = "android:read_skin_temperature";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2591,6 +2600,7 @@
             OP_POST_NOTIFICATION,
             // Health
             Flags.replaceBodySensorPermissionEnabled() ? OP_READ_HEART_RATE : OP_NONE,
+            Flags.platformSkinTemperatureEnabled() ? OP_READ_SKIN_TEMPERATURE : OP_NONE,
     };
 
     /**
@@ -3103,6 +3113,11 @@
             .setPermission(Flags.replaceBodySensorPermissionEnabled() ?
                 HealthPermissions.READ_HEART_RATE : null)
             .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_READ_SKIN_TEMPERATURE, OPSTR_READ_SKIN_TEMPERATURE,
+            "READ_SKIN_TEMPERATURE").setPermission(
+                Flags.platformSkinTemperatureEnabled()
+                    ? HealthPermissions.READ_SKIN_TEMPERATURE : 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 ecef0db..2cf718e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1962,6 +1962,24 @@
         }
     }
 
+    @Override
+    @NonNull
+    public List<IntentFilter> getRegisteredIntentFilters(@NonNull BroadcastReceiver receiver) {
+        if (mPackageInfo != null) {
+            IIntentReceiver rd = mPackageInfo.findRegisteredReceiverDispatcher(
+                    receiver, getOuterContext());
+            try {
+                final List<IntentFilter> filters = ActivityManager.getService()
+                        .getRegisteredIntentFilters(rd);
+                return filters == null ? new ArrayList<>() : filters;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            throw new RuntimeException("Not supported in system context");
+        }
+    }
+
     private void validateServiceIntent(Intent service) {
         if (service.getComponent() == null && service.getPackage() == null) {
             if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f880901..34a3ad1 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -181,6 +181,7 @@
             in IntentFilter filter, in String requiredPermission, int userId, int flags);
     @UnsupportedAppUsage
     void unregisterReceiver(in IIntentReceiver receiver);
+    List<IntentFilter> getRegisteredIntentFilters(in IIntentReceiver receiver);
     /** @deprecated Use {@link #broadcastIntentWithFeature} instead */
     @UnsupportedAppUsage(maxTargetSdk=29, publicAlternatives="Use {@link android.content.Context#sendBroadcast(android.content.Intent)} instead")
     int broadcastIntent(in IApplicationThread caller, in Intent intent,
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 003104a..ec7b72e 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -242,6 +242,9 @@
 
     boolean supportsLocalVoiceInteraction();
 
+    // Sets whether system educational dialogs should be limited
+    void setLimitSystemEducationDialogs(IBinder appToken, boolean limitSystemEducationDialogs);
+
     // Get device configuration
     ConfigurationInfo getDeviceConfigurationInfo();
 
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index 5acb9b5..f693e9b 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -24,6 +24,8 @@
 import android.app.IWallpaperManagerCallback;
 import android.app.ILocalWallpaperColorConsumer;
 import android.app.WallpaperInfo;
+import android.app.wallpaper.WallpaperDescription;
+import android.app.wallpaper.WallpaperInstance;
 import android.content.ComponentName;
 import android.app.WallpaperColors;
 
@@ -61,8 +63,8 @@
     /**
      * Set the live wallpaper.
      */
-    void setWallpaperComponentChecked(in ComponentName name, in String callingPackage, int which,
-            int userId);
+    void setWallpaperComponentChecked(in WallpaperDescription description, in String callingPackage,
+            int which, int userId);
 
     /**
      * Set the live wallpaper. This only affects the system wallpaper.
@@ -129,6 +131,13 @@
      */
     WallpaperInfo getWallpaperInfoWithFlags(int which, int userId);
 
+   /**
+    * Return the instance information about the wallpaper described by `which`, or null if lock
+    * screen is requested and it is the same as home.
+    */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL)")
+    WallpaperInstance getWallpaperInstance(int which, int userId);
+
     /**
      * Return a file descriptor for the file that contains metadata about the given user's
      * wallpaper.
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 1e45d6f..b8233bc 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -1627,6 +1627,18 @@
         }
     }
 
+    IIntentReceiver findRegisteredReceiverDispatcher(BroadcastReceiver r, Context context) {
+        synchronized (mReceivers) {
+            final ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map =
+                    mReceivers.get(context);
+            if (map != null) {
+                final LoadedApk.ReceiverDispatcher rd = map.get(r);
+                return rd == null ? null : rd.getIIntentReceiver();
+            }
+            return null;
+        }
+    }
+
     public IIntentReceiver forgetReceiverDispatcher(Context context,
             BroadcastReceiver r) {
         synchronized (mReceivers) {
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 26bfec3..038dcdb 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import static android.text.TextUtils.formatSimple;
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -40,6 +41,7 @@
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
+import dalvik.annotation.optimization.NeverCompile;
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileOutputStream;
@@ -201,6 +203,23 @@
     }
 
     /**
+     * The list of known and legal modules.  The list is not sorted.
+     */
+    private static final String[] sValidModule = {
+        MODULE_SYSTEM, MODULE_BLUETOOTH, MODULE_TELEPHONY, MODULE_TEST,
+    };
+
+    /**
+     * Verify that the module string is in the legal list.  Throw if it is not.
+     */
+    private static void throwIfInvalidModule(@NonNull String name) {
+        for (int i = 0; i < sValidModule.length; i++) {
+            if (sValidModule[i].equals(name)) return;
+        }
+        throw new IllegalArgumentException("invalid module: " + name);
+    }
+
+    /**
      * All legal keys start with one of the following strings.
      */
     private static final String[] sValidKeyPrefix = {
@@ -254,8 +273,11 @@
     // written to global store.
     private static final int NONCE_BYPASS = 3;
 
+    // The largest reserved nonce value.  Update this whenever a reserved nonce is added.
+    private static final int MAX_RESERVED_NONCE = NONCE_BYPASS;
+
     private static boolean isReservedNonce(long n) {
-        return n >= NONCE_UNSET && n <= NONCE_BYPASS;
+        return n >= NONCE_UNSET && n <= MAX_RESERVED_NONCE;
     }
 
     /**
@@ -293,7 +315,7 @@
     private long mMisses = 0;
 
     @GuardedBy("mLock")
-    private long[] mSkips = new long[]{ 0, 0, 0, 0 };
+    private long[] mSkips = new long[MAX_RESERVED_NONCE + 1];
 
     @GuardedBy("mLock")
     private long mMissOverflow = 0;
@@ -811,10 +833,20 @@
         return false; // Always disable shared memory on Ravenwood. (for now)
     }
 
+    /**
+     * Keys that cannot be put in shared memory yet.
+     */
+    private static boolean inSharedMemoryDenyList(@NonNull String name) {
+        final String pkginfo = PREFIX_SYSTEM + "package_info";
+        return name.equals(pkginfo);
+    };
+
     // Return true if this cache can use shared memory for its nonce.  Shared memory may be used
     // if the module is the system.
     private static boolean sharedMemoryOkay(@NonNull String name) {
-        return sSharedMemoryAvailable && name.startsWith(PREFIX_SYSTEM);
+        return sSharedMemoryAvailable
+                && name.startsWith(PREFIX_SYSTEM)
+                && !inSharedMemoryDenyList(name);
     }
 
     /**
@@ -844,6 +876,73 @@
     }
 
     /**
+     * A public argument builder to configure cache behavior.  The root instance requires a
+     * module; this is immutable.  New instances are created with member methods.  It is important
+     * to note that the member methods create new instances: they do not modify 'this'.  The api
+     * is allowed to be null in the record constructor to facility reuse of Args instances.
+     * @hide
+     */
+    public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) {
+
+        // Validation: the module must be one of the known module strings and the maxEntries must
+        // be positive.
+        public Args {
+            throwIfInvalidModule(mModule);
+            checkArgumentPositive(mMaxEntries, "max cache size must be positive");
+        }
+
+        // The base constructor must include the module.  Modules do not change in a source file,
+        // so even if the Args is reused, the module will not/should not change.  The api is null,
+        // which is not legal, but there is no reasonable default.  Clients must call the api
+        // method to set the field properly.
+        public Args(@NonNull String module) {
+            this(module, /* api */ null, /* maxEntries */ 32);
+        }
+
+        public Args api(@NonNull String api) {
+            return new Args(mModule, api, mMaxEntries);
+        }
+
+        public Args maxEntries(int val) {
+            return new Args(mModule, mApi, val);
+        }
+    }
+
+    /**
+     * Make a new property invalidated cache.  The key is computed from the module and api
+     * parameters.
+     *
+     * @param args The cache configuration.
+     * @param cacheName Name of this cache in debug and dumpsys
+     * @param computer The code to compute values that are not in the cache.
+     * @hide
+     */
+    public PropertyInvalidatedCache(@NonNull Args args, @NonNull String cacheName,
+            @Nullable QueryHandler<Query, Result> computer) {
+        mPropertyName = createPropertyName(args.mModule, args.mApi);
+        mCacheName = cacheName;
+        mNonce = getNonceHandler(mPropertyName);
+        mMaxEntries = args.mMaxEntries;
+        mCache = createMap();
+        mComputer = (computer != null) ? computer : new DefaultComputer<>(this);
+        registerCache();
+    }
+
+    /**
+     * Burst a property name into module and api.  Throw if the key is invalid.  This method is
+     * used in to transition legacy cache constructors to the args constructor.
+     */
+    private static Args parseProperty(@NonNull String name) {
+        throwIfInvalidCacheKey(name);
+        // Strip off the leading well-known prefix.
+        String base = name.substring(CACHE_KEY_PREFIX.length() + 1);
+        int dot = base.indexOf(".");
+        String module = base.substring(0, dot);
+        String api = base.substring(dot + 1);
+        return new Args(module).api(api);
+    }
+
+    /**
      * Make a new property invalidated cache.  This constructor names the cache after the
      * property name.  New clients should prefer the constructor that takes an explicit
      * cache name.
@@ -857,7 +956,7 @@
      * @hide
      */
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
-        this(maxEntries, propertyName, propertyName);
+        this(parseProperty(propertyName).maxEntries(maxEntries), propertyName, null);
     }
 
     /**
@@ -873,13 +972,7 @@
      */
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
             @NonNull String cacheName) {
-        mPropertyName = propertyName;
-        mCacheName = cacheName;
-        mNonce = getNonceHandler(mPropertyName);
-        mMaxEntries = maxEntries;
-        mComputer = new DefaultComputer<>(this);
-        mCache = createMap();
-        registerCache();
+        this(parseProperty(propertyName).maxEntries(maxEntries), cacheName, null);
     }
 
     /**
@@ -897,13 +990,7 @@
     @TestApi
     public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
             @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
-        mPropertyName = createPropertyName(module, api);
-        mCacheName = cacheName;
-        mNonce = getNonceHandler(mPropertyName);
-        mMaxEntries = maxEntries;
-        mComputer = computer;
-        mCache = createMap();
-        registerCache();
+        this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
     }
 
     // Create a map.  This should be called only from the constructor.
@@ -1171,7 +1258,8 @@
     public @Nullable Result query(@NonNull Query query) {
         // Let access to mDisabled race: it's atomic anyway.
         long currentNonce = (!isDisabled()) ? getCurrentNonce() : NONCE_DISABLED;
-        if (bypass(query)) {
+        if (!isReservedNonce(currentNonce)
+            && bypass(query)) {
             currentNonce = NONCE_BYPASS;
         }
         for (;;) {
@@ -1639,54 +1727,62 @@
         return false;
     }
 
-    /**
-     * helper method to check if dump should be skipped due to zero values
-     * @param args takes command arguments to check if -brief is present
-     * @return True if dump should be skipped
-     */
-    private boolean skipDump(String[] args) {
-        for (String a : args) {
-            if (a.equals(BRIEF)) {
-                return (mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED]
-                      + mSkips[NONCE_BYPASS] + mHits + mMisses) == 0;
-            }
+    @GuardedBy("mLock")
+    private long getSkipsLocked() {
+        int sum = 0;
+        for (int i = 0; i < mSkips.length; i++) {
+            sum += mSkips[i];
         }
-        return false;
+        return sum;
     }
 
+    // Return true if this cache has had any activity.  If the hits, misses, and skips are all
+    // zero then the client never tried to use the cache.
+    private boolean isActive() {
+        synchronized (mLock) {
+            return mHits + mMisses + getSkipsLocked() > 0;
+        }
+    }
+
+    @NeverCompile
     private void dumpContents(PrintWriter pw, boolean detailed, String[] args) {
         // If the user has requested specific caches and this is not one of them, return
         // immediately.
         if (detailed && !showDetailed(args)) {
             return;
         }
+        // Does the user want brief output?
+        boolean brief = false;
+        for (String a : args) brief |= a.equals(BRIEF);
 
         NonceHandler.Stats stats = mNonce.getStats();
 
         synchronized (mLock) {
-            if (!skipDump(args)) {
-                pw.println(formatSimple("  Cache Name: %s", cacheName()));
-                pw.println(formatSimple("    Property: %s", mPropertyName));
-                final long skips =
-                        mSkips[NONCE_CORKED] + mSkips[NONCE_UNSET] + mSkips[NONCE_DISABLED]
-                                + mSkips[NONCE_BYPASS];
-                pw.println(formatSimple(
-                        "    Hits: %d, Misses: %d, Skips: %d, Clears: %d",
-                        mHits, mMisses, skips, mClears));
-                pw.println(formatSimple(
-                        "    Skip-corked: %d, Skip-unset: %d, Skip-bypass: %d, Skip-other: %d",
-                        mSkips[NONCE_CORKED], mSkips[NONCE_UNSET],
-                        mSkips[NONCE_BYPASS], mSkips[NONCE_DISABLED]));
-                pw.println(formatSimple(
-                        "    Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
-                        mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
-                pw.println(formatSimple(
-                        "    Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
-                        mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
-                pw.println(formatSimple("    Enabled: %s", mDisabled ? "false" : "true"));
-                pw.println("");
+            if (brief && !isActive()) {
+                return;
             }
 
+            pw.println(formatSimple("  Cache Name: %s", cacheName()));
+            pw.println(formatSimple("    Property: %s", mPropertyName));
+            pw.println(formatSimple(
+                "    Hits: %d, Misses: %d, Skips: %d, Clears: %d",
+                mHits, mMisses, getSkipsLocked(), mClears));
+
+            // Print all the skip reasons.
+            pw.format("    Skip-%s: %d", sNonceName[0], mSkips[0]);
+            for (int i = 1; i < mSkips.length; i++) {
+                pw.format(", Skip-%s: %d", sNonceName[i], mSkips[i]);
+            }
+            pw.println();
+
+            pw.println(formatSimple(
+                "    Nonce: 0x%016x, Invalidates: %d, CorkedInvalidates: %d",
+                mLastSeenNonce, stats.invalidated, stats.corkedInvalidates));
+            pw.println(formatSimple(
+                "    Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
+                mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
+            pw.println(formatSimple("    Enabled: %s", mDisabled ? "false" : "true"));
+
             // No specific cache was requested.  This is the default, and no details
             // should be dumped.
             if (!detailed) {
@@ -1713,6 +1809,7 @@
      * specific caches (selection is by cache name or property name); if these switches
      * are used then the output includes both cache statistics and cache entries.
      */
+    @NeverCompile
     private static void dumpCacheInfo(@NonNull PrintWriter pw, @NonNull String[] args) {
         if (!sEnabled) {
             pw.println("  Caching is disabled in this process.");
@@ -1745,6 +1842,7 @@
      * are used then the output includes both cache statistics and cache entries.
      * @hide
      */
+    @NeverCompile
     public static void dumpCacheInfo(@NonNull ParcelFileDescriptor pfd, @NonNull String[] args) {
         // Create a PrintWriter that uses a byte array.  The code can safely write to
         // this array without fear of blocking.  The completed byte array will be sent
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 081ce31..9c6f509 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -340,6 +340,12 @@
     public int requestedVisibleTypes;
 
     /**
+     * Whether the top activity has requested to limit educational dialogs shown by the system.
+     * @hide
+     */
+    public boolean isTopActivityLimitSystemEducationDialogs;
+
+    /**
      * Encapsulate specific App Compat information.
      * @hide
      */
@@ -482,10 +488,12 @@
                 && Objects.equals(topActivity, that.topActivity)
                 && isTopActivityTransparent == that.isTopActivityTransparent
                 && isTopActivityStyleFloating == that.isTopActivityStyleFloating
-                && lastNonFullscreenBounds == this.lastNonFullscreenBounds
+                && Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
                 && Objects.equals(capturedLink, that.capturedLink)
                 && capturedLinkTimestamp == that.capturedLinkTimestamp
                 && requestedVisibleTypes == that.requestedVisibleTypes
+                && isTopActivityLimitSystemEducationDialogs
+                    == that.isTopActivityLimitSystemEducationDialogs
                 && appCompatTaskInfo.equalsForTaskOrganizer(that.appCompatTaskInfo)
                 && Objects.equals(topActivityMainWindowFrame, that.topActivityMainWindowFrame);
     }
@@ -562,6 +570,7 @@
         capturedLink = source.readTypedObject(Uri.CREATOR);
         capturedLinkTimestamp = source.readLong();
         requestedVisibleTypes = source.readInt();
+        isTopActivityLimitSystemEducationDialogs = source.readBoolean();
         appCompatTaskInfo = source.readTypedObject(AppCompatTaskInfo.CREATOR);
         topActivityMainWindowFrame = source.readTypedObject(Rect.CREATOR);
     }
@@ -616,6 +625,7 @@
         dest.writeTypedObject(capturedLink, flags);
         dest.writeLong(capturedLinkTimestamp);
         dest.writeInt(requestedVisibleTypes);
+        dest.writeBoolean(isTopActivityLimitSystemEducationDialogs);
         dest.writeTypedObject(appCompatTaskInfo, flags);
         dest.writeTypedObject(topActivityMainWindowFrame, flags);
     }
@@ -660,6 +670,8 @@
                 + " capturedLink=" + capturedLink
                 + " capturedLinkTimestamp=" + capturedLinkTimestamp
                 + " requestedVisibleTypes=" + requestedVisibleTypes
+                + " isTopActivityLimitSystemEducationDialogs="
+                + isTopActivityLimitSystemEducationDialogs
                 + " appCompatTaskInfo=" + appCompatTaskInfo
                 + " topActivityMainWindowFrame=" + topActivityMainWindowFrame
                 + "}";
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 014e4660..479f3df 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -21,10 +21,12 @@
 import static android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 
 import static com.android.window.flags.Flags.FLAG_MULTI_CROP;
 import static com.android.window.flags.Flags.multiCrop;
 
+import android.Manifest;
 import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
@@ -39,6 +41,8 @@
 import android.annotation.TestApi;
 import android.annotation.UiContext;
 import android.app.compat.CompatChanges;
+import android.app.wallpaper.WallpaperDescription;
+import android.app.wallpaper.WallpaperInstance;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -319,12 +323,12 @@
      * This is only used internally by the framework and the WallpaperBackupAgent.
      * @hide
      */
-    @IntDef(value = {
+    @IntDef(prefix = { "ORIENTATION_" }, value = {
             ORIENTATION_UNKNOWN,
-            PORTRAIT,
-            LANDSCAPE,
-            SQUARE_PORTRAIT,
-            SQUARE_LANDSCAPE,
+            ORIENTATION_PORTRAIT,
+            ORIENTATION_LANDSCAPE,
+            ORIENTATION_SQUARE_PORTRAIT,
+            ORIENTATION_SQUARE_LANDSCAPE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ScreenOrientation {}
@@ -338,25 +342,25 @@
      * Portrait orientation of most screens
      * @hide
      */
-    public static final int PORTRAIT = 0;
+    public static final int ORIENTATION_PORTRAIT = 0;
 
     /**
      * Landscape orientation of most screens
      * @hide
      */
-    public static final int LANDSCAPE = 1;
+    public static final int ORIENTATION_LANDSCAPE = 1;
 
     /**
      * Portrait orientation with similar width and height (e.g. the inner screen of a foldable)
      * @hide
      */
-    public static final int SQUARE_PORTRAIT = 2;
+    public static final int ORIENTATION_SQUARE_PORTRAIT = 2;
 
     /**
      * Landscape orientation with similar width and height (e.g. the inner screen of a foldable)
      * @hide
      */
-    public static final int SQUARE_LANDSCAPE = 3;
+    public static final int ORIENTATION_SQUARE_LANDSCAPE = 3;
 
     /**
      * Converts a (width, height) screen size to a {@link ScreenOrientation}.
@@ -367,10 +371,10 @@
     public static @ScreenOrientation int getOrientation(Point screenSize) {
         float ratio = ((float) screenSize.x) / screenSize.y;
         // ratios between 3/4 and 4/3 are considered square
-        return ratio >= 4 / 3f ? LANDSCAPE
-                : ratio > 1f ? SQUARE_LANDSCAPE
-                : ratio > 3 / 4f ? SQUARE_PORTRAIT
-                : PORTRAIT;
+        return ratio >= 4 / 3f ? ORIENTATION_LANDSCAPE
+                : ratio > 1f ? ORIENTATION_SQUARE_LANDSCAPE
+                : ratio > 3 / 4f ? ORIENTATION_SQUARE_PORTRAIT
+                : ORIENTATION_PORTRAIT;
     }
 
     /**
@@ -379,10 +383,10 @@
      */
     public static @ScreenOrientation int getRotatedOrientation(@ScreenOrientation int orientation) {
         switch (orientation) {
-            case PORTRAIT: return LANDSCAPE;
-            case LANDSCAPE: return PORTRAIT;
-            case SQUARE_PORTRAIT: return SQUARE_LANDSCAPE;
-            case SQUARE_LANDSCAPE: return SQUARE_PORTRAIT;
+            case ORIENTATION_PORTRAIT: return ORIENTATION_LANDSCAPE;
+            case ORIENTATION_LANDSCAPE: return ORIENTATION_PORTRAIT;
+            case ORIENTATION_SQUARE_PORTRAIT: return ORIENTATION_SQUARE_LANDSCAPE;
+            case ORIENTATION_SQUARE_LANDSCAPE: return ORIENTATION_SQUARE_PORTRAIT;
             default: return ORIENTATION_UNKNOWN;
         }
     }
@@ -2023,6 +2027,34 @@
     }
 
     /**
+     * Returns the description of the designated wallpaper. Returns null if the lock screen
+     * wallpaper is requested lock screen wallpaper is not set.
+
+     * @param which Specifies wallpaper to request (home or lock).
+     * @throws IllegalArgumentException if {@code which} is not exactly one of
+     * {{@link #FLAG_SYSTEM},{@link #FLAG_LOCK}}.
+     *
+     * @hide
+     */
+    @Nullable
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @RequiresPermission(READ_WALLPAPER_INTERNAL)
+    @SystemApi
+    public WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which) {
+        checkExactlyOneWallpaperFlagSet(which);
+        try {
+            if (sGlobals.mService == null) {
+                Log.w(TAG, "WallpaperService not running");
+                throw new RuntimeException(new DeadSystemException());
+            } else {
+                return sGlobals.mService.getWallpaperInstance(which, mContext.getUserId());
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Get an open, readable file descriptor for the file that contains metadata about the
      * context user's wallpaper.
      *
@@ -2955,15 +2987,66 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT)
+    @RequiresPermission(allOf = {android.Manifest.permission.SET_WALLPAPER_COMPONENT,
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
     public boolean setWallpaperComponentWithFlags(@NonNull ComponentName name,
             @SetWallpaperFlags int which, int userId) {
+        WallpaperDescription description = new WallpaperDescription.Builder().setComponent(
+                name).build();
+        return setWallpaperComponentWithDescription(description, which, userId);
+    }
+
+    /**
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, along with associated metadata.
+     *
+     * <p>This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+     * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
+     * another user's wallpaper.
+     * </p>
+     *
+     * @param description wallpaper component and metadata to set
+     * @param which Specifies wallpaper destination (home and/or lock).
+     * @return true on success, otherwise false
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @SystemApi
+    @RequiresPermission(allOf = {android.Manifest.permission.SET_WALLPAPER_COMPONENT,
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
+    public boolean setWallpaperComponentWithDescription(@NonNull WallpaperDescription description,
+            @SetWallpaperFlags int which) {
+        return setWallpaperComponentWithDescription(description, which, mContext.getUserId());
+    }
+
+    /**
+     * Set the implementation of {@link android.service.wallpaper.WallpaperService} used to render
+     * wallpaper, along with associated metadata.
+     *
+     * <p>This can only be called by packages with android.permission.SET_WALLPAPER_COMPONENT
+     * permission. The caller must hold the INTERACT_ACROSS_USERS_FULL permission to change
+     * another user's wallpaper.
+     * </p>
+     *
+     * @param description wallpaper component and metadata to set
+     * @param which Specifies wallpaper destination (home and/or lock).
+     * @param userId User for whom the component should be set.
+     * @return true on success, otherwise false
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @RequiresPermission(allOf = {android.Manifest.permission.SET_WALLPAPER_COMPONENT,
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional = true)
+    public boolean setWallpaperComponentWithDescription(@NonNull WallpaperDescription description,
+            @SetWallpaperFlags int which, int userId) {
         if (sGlobals.mService == null) {
             Log.w(TAG, "WallpaperManagerService not running");
             throw new RuntimeException(new DeadSystemException());
         }
         try {
-            sGlobals.mService.setWallpaperComponentChecked(name, mContext.getOpPackageName(),
+            sGlobals.mService.setWallpaperComponentChecked(description, mContext.getOpPackageName(),
                     which, userId);
             return true;
         } catch (RemoteException e) {
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index fd58377..3aaca25 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -341,3 +341,11 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "set_mte_policy_coexistence"
+    is_exported: true
+    namespace: "enterprise"
+    description: "Enables coexistence support for Setting MTE policy."
+    bug: "376213673"
+}
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index 74cae07..5ddb590 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -53,8 +53,8 @@
  * offers a more convenient and type-safe way to build app functions. The SDK provides predefined
  * function schemas for common use cases and associated data classes for function parameters and
  * return values. Apps only have to implement the provided interfaces. Internally, the SDK converts
- * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and
- * {@link ExecuteAppFunctionResponse#getResultDocument()}.
+ * these data classes into {@link ExecuteAppFunctionRequest#getParameters()} and {@link
+ * ExecuteAppFunctionResponse#getResultDocument()}.
  *
  * <p>**Discovering App Functions:**
  *
@@ -69,13 +69,12 @@
  * <p>**Executing App Functions:**
  *
  * <p>To execute an app function, the caller app can retrieve the {@code functionIdentifier} from
- * the {@code AppFunctionStaticMetadata} document and use it to build an
- * {@link ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request
- * to execute the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS}
- * or {@code  android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app
- * functions from other apps. An app can always execute its own app functions and doesn't need these
- * permissions. AppFunction SDK provides a convenient way to achieve this and is the preferred
- * method.
+ * the {@code AppFunctionStaticMetadata} document and use it to build an {@link
+ * ExecuteAppFunctionRequest}. Then, invoke {@link #executeAppFunction} with the request to execute
+ * the app function. Callers need the {@code android.permission.EXECUTE_APP_FUNCTIONS} or {@code
+ * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} permission to execute app functions from other
+ * apps. An app can always execute its own app functions and doesn't need these permissions.
+ * AppFunction SDK provides a convenient way to achieve this and is the preferred method.
  *
  * <p>**Example:**
  *
@@ -213,12 +212,13 @@
     /**
      * Returns a boolean through a callback, indicating whether the app function is enabled.
      *
-     * <p>* This method can only check app functions owned by the caller, or those where the caller
+     * <p>This method can only check app functions owned by the caller, or those where the caller
      * has visibility to the owner package and holds either the {@link
      * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link
      * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission.
      *
-     * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
+     * <p>If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with
+     * errors:
      *
      * <ul>
      *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
@@ -232,23 +232,47 @@
      * @param executor the executor to run the request
      * @param callback the callback to receive the function enabled check result
      */
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+                Manifest.permission.EXECUTE_APP_FUNCTIONS
+            },
+            conditional = true)
     public void isAppFunctionEnabled(
             @NonNull String functionIdentifier,
             @NonNull String targetPackage,
             @NonNull Executor executor,
             @NonNull OutcomeReceiver<Boolean, Exception> callback) {
-        Objects.requireNonNull(functionIdentifier);
-        Objects.requireNonNull(targetPackage);
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(callback);
-        AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class);
-        if (appSearchManager == null) {
-            callback.onError(new IllegalStateException("Failed to get AppSearchManager."));
-            return;
-        }
+        isAppFunctionEnabledInternal(functionIdentifier, targetPackage, executor, callback);
+    }
 
-        AppFunctionManagerHelper.isAppFunctionEnabled(
-                functionIdentifier, targetPackage, appSearchManager, executor, callback);
+    /**
+     * Returns a boolean through a callback, indicating whether the app function is enabled.
+     *
+     * <p>This method can only check app functions owned by the caller, unlike {@link
+     * #isAppFunctionEnabled(String, String, Executor, OutcomeReceiver)}, which allows specifying a
+     * different target package.
+     *
+     * <p>If the operation fails, the callback's {@link OutcomeReceiver#onError} is called with
+     * errors:
+     *
+     * <ul>
+     *   <li>{@link IllegalArgumentException}, if the function is not found or the caller does not
+     *       have access to it.
+     * </ul>
+     *
+     * @param functionIdentifier the identifier of the app function to check (unique within the
+     *     target package) and in most cases, these are automatically generated by the AppFunctions
+     *     SDK
+     * @param executor the executor to run the request
+     * @param callback the callback to receive the function enabled check result
+     */
+    public void isAppFunctionEnabled(
+            @NonNull String functionIdentifier,
+            @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+        isAppFunctionEnabledInternal(
+                functionIdentifier, mContext.getPackageName(), executor, callback);
     }
 
     /**
@@ -291,6 +315,25 @@
         }
     }
 
+    private void isAppFunctionEnabledInternal(
+            @NonNull String functionIdentifier,
+            @NonNull String targetPackage,
+            @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+        Objects.requireNonNull(functionIdentifier);
+        Objects.requireNonNull(targetPackage);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+        AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class);
+        if (appSearchManager == null) {
+            callback.onError(new IllegalStateException("Failed to get AppSearchManager."));
+            return;
+        }
+
+        AppFunctionManagerHelper.isAppFunctionEnabled(
+                functionIdentifier, targetPackage, appSearchManager, executor, callback);
+    }
+
     private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub {
 
         private final OutcomeReceiver<Void, Exception> mCallback;
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index 83453a9..cdf02e6 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -73,38 +73,102 @@
      */
     public static final String PROPERTY_RETURN_VALUE = "returnValue";
 
-    /** The call was successful. */
+    /**
+     * 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. */
-    public static final int RESULT_DENIED = 1;
+    /**
+     * 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.
-     */
-    public static final int RESULT_APP_UNKNOWN_ERROR = 2;
-
-    /** An internal unexpected error coming from the system. */
-    public static final int RESULT_INTERNAL_ERROR = 3;
-
-    /**
-     * The caller supplied invalid arguments to the call.
      *
-     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
      */
-    public static final int RESULT_INVALID_ARGUMENT = 4;
-
-    /** The caller tried to execute a disabled app function. */
-    public static final int RESULT_DISABLED = 5;
+    public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
 
     /**
-     * The operation was cancelled. Use this error code to report that a cancellation is done after
-     * receiving a cancellation signal.
+     * The error category is unknown.
+     *
+     * <p>This is the default value for {@link #getErrorCategory}.
      */
-    public static final int RESULT_CANCELLED = 6;
+    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;
@@ -198,6 +262,36 @@
     }
 
     /**
+     * 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.
@@ -280,11 +374,28 @@
                 RESULT_OK,
                 RESULT_DENIED,
                 RESULT_APP_UNKNOWN_ERROR,
-                RESULT_INTERNAL_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/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index 3b0c867..5e09517 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -12,4 +12,18 @@
   namespace: "machine_learning"
   description: "Flag to refresh the token to the callback"
   bug: "309689654"
+}
+
+flag {
+    name: "multi_window_screen_context"
+    namespace: "machine_learning"
+    description: "Report screen context and positions for all windows."
+    bug: "371065456"
+}
+
+flag {
+    name: "contextual_search_window_layer"
+    namespace: "machine_learning"
+    description: "Identify live contextual search UI to exclude from contextual search screenshot."
+    bug: "372510690"
 }
\ No newline at end of file
diff --git a/core/java/android/app/jank/JankDataProcessor.java b/core/java/android/app/jank/JankDataProcessor.java
new file mode 100644
index 0000000..981a916
--- /dev/null
+++ b/core/java/android/app/jank/JankDataProcessor.java
@@ -0,0 +1,351 @@
+/*
+ * 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.jank;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.jank.StateTracker.StateData;
+import android.util.Log;
+import android.util.Pools.SimplePool;
+import android.view.SurfaceControl.JankData;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class is responsible for associating frames received from SurfaceFlinger to active widget
+ * states and logging those states back to the platform.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+public class JankDataProcessor {
+
+    private static final int MAX_IN_MEMORY_STATS = 25;
+    private static final int LOG_BATCH_FREQUENCY = 50;
+    private int mCurrentBatchCount = 0;
+    private StateTracker mStateTracker = null;
+    private ArrayList<StateData> mPendingStates = new ArrayList<>();
+    private SimplePool<PendingJankStat> mPendingJankStatsPool =
+            new SimplePool<>(MAX_IN_MEMORY_STATS);
+    private HashMap<String, PendingJankStat> mPendingJankStats = new HashMap<>();
+
+    public JankDataProcessor(@NonNull StateTracker stateTracker) {
+        mStateTracker = stateTracker;
+    }
+
+    /**
+     * Called once per batch of JankData.
+     * @param jankData data received from SurfaceFlinger to be processed
+     * @param activityName name of the activity that is tracking jank metrics.
+     * @param appUid the uid of the app.
+     */
+    public void processJankData(List<JankData> jankData, String activityName, int appUid) {
+        mCurrentBatchCount++;
+        // add all the previous and active states to the pending states list.
+        mStateTracker.retrieveAllStates(mPendingStates);
+
+        // TODO b/376332122 Look to see if this logic can be optimized.
+        for (int i = 0; i < jankData.size(); i++) {
+            JankData frame = jankData.get(i);
+            // for each frame we need to check if the state was active during that time.
+            for (int j = 0; j < mPendingStates.size(); j++) {
+                StateData pendingState = mPendingStates.get(j);
+                // This state was active during the frame
+                if (frame.frameVsyncId >= pendingState.mVsyncIdStart
+                        && frame.frameVsyncId <= pendingState.mVsyncIdEnd) {
+                    recordFrameCount(frame, pendingState, activityName, appUid);
+
+                    pendingState.mProcessed = true;
+                }
+            }
+        }
+        // At this point we have attributed all frames to a state.
+        if (mCurrentBatchCount >= LOG_BATCH_FREQUENCY) {
+            logMetricCounts();
+        }
+        // return the StatData object back to the pool to be reused.
+        jankDataProcessingComplete();
+    }
+
+    /**
+     * Returns the aggregate map of different pending jank stats.
+     */
+    @VisibleForTesting
+    public HashMap<String, PendingJankStat> getPendingJankStats() {
+        return mPendingJankStats;
+    }
+
+    private void jankDataProcessingComplete() {
+        mStateTracker.stateProcessingComplete();
+        mPendingStates.clear();
+    }
+
+    /**
+     * Determine if frame is Janky and add to existing memory counter or create a new one.
+     */
+    private void recordFrameCount(JankData frameData, StateData stateData, String activityName,
+            int appUid) {
+        // Check if we have an existing Jank state
+        PendingJankStat jankStats = mPendingJankStats.get(stateData.mStateDataKey);
+
+        if (jankStats == null) {
+            // Check if we have space for another pending stat
+            if (mPendingJankStats.size() > MAX_IN_MEMORY_STATS) {
+                return;
+            }
+
+            jankStats = mPendingJankStatsPool.acquire();
+            if (jankStats == null) {
+                jankStats = new PendingJankStat();
+            }
+            jankStats.clearStats();
+            jankStats.mActivityName = activityName;
+            jankStats.mUid = appUid;
+            mPendingJankStats.put(stateData.mStateDataKey, jankStats);
+        }
+        // This state has already been accounted for
+        if (jankStats.processedVsyncId == frameData.frameVsyncId) return;
+
+        jankStats.mTotalFrames += 1;
+        if (frameData.jankType == JankData.JANK_APPLICATION) {
+            jankStats.mJankyFrames += 1;
+        }
+        jankStats.recordFrameOverrun(frameData.actualAppFrameTimeNs);
+        jankStats.processedVsyncId = frameData.frameVsyncId;
+
+    }
+
+    /**
+     * When called will log pending Jank stats currently stored in memory to the platform. Will not
+     * clear any pending widget states.
+     */
+    public void logMetricCounts() {
+        //TODO b/374607503 when api changes are in add enum mapping for category and state.
+
+        try {
+            mPendingJankStats.values().forEach(stat -> {
+                        FrameworkStatsLog.write(FrameworkStatsLog.JANK_FRAME_COUNT_BY_WIDGET,
+                                /*app uid*/ stat.getUid(),
+                                /*activity name*/ stat.getActivityName(),
+                                /*widget id*/ stat.getWidgetId(),
+                                /*refresh rate*/ stat.getRefreshRate(),
+                                /*widget category*/ 0,
+                                /*widget state*/ 0,
+                                /*total frames*/ stat.getTotalFrames(),
+                                /*janky frames*/ stat.getJankyFrames(),
+                                /*histogram*/ stat.mFrameOverrunBuckets);
+                        Log.d(stat.mActivityName, stat.toString());
+                        // return the pending stat to the pool it will be reset the next time its
+                        // used.
+                        mPendingJankStatsPool.release(stat);
+                    }
+            );
+            // All stats have been recorded and added back to the pool for reuse, clear the pending
+            // stats.
+            mPendingJankStats.clear();
+            mCurrentBatchCount = 0;
+        } catch (Exception exception) {
+            // TODO b/374608358 handle logging exceptions.
+        }
+    }
+
+    public static final class PendingJankStat {
+        private static final int NANOS_PER_MS = 1000000;
+        public long processedVsyncId = -1;
+
+        // UID of the app
+        private int mUid;
+
+        // The name of the activity that is currently collecting frame metrics.
+        private String mActivityName;
+
+        // The id that has been set for the widget.
+        private String mWidgetId;
+
+        // A general category that the widget applies to.
+        private String mWidgetCategory;
+
+        // The states that the UI elements can report
+        private String mWidgetState;
+
+        // The number of frames reported during this state.
+        private long mTotalFrames;
+
+        // Total number of frames determined to be janky during the reported state.
+        private long mJankyFrames;
+
+        private int mRefreshRate;
+
+        private static final int[] sFrameOverrunHistogramBounds =  {
+                Integer.MIN_VALUE, -200, -150, -100, -90, -80, -70, -60, -50, -40, -30, -25, -20,
+                -18, -16, -14, -12, -10, -8, -6, -4, -2, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 25,
+                30, 40, 50, 60, 70, 80, 90, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000
+        };
+        private final int[] mFrameOverrunBuckets = new int[sFrameOverrunHistogramBounds.length];
+
+        // Histogram of frame duration overruns encoded in predetermined buckets.
+        public PendingJankStat() {
+        }
+        public long getProcessedVsyncId() {
+            return processedVsyncId;
+        }
+
+        public void setProcessedVsyncId(long processedVsyncId) {
+            this.processedVsyncId = processedVsyncId;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        public void setUid(int uid) {
+            mUid = uid;
+        }
+
+        public String getActivityName() {
+            return mActivityName;
+        }
+
+        public void setActivityName(String activityName) {
+            mActivityName = activityName;
+        }
+
+        public String getWidgetId() {
+            return mWidgetId;
+        }
+
+        public void setWidgetId(String widgetId) {
+            mWidgetId = widgetId;
+        }
+
+        public String getWidgetCategory() {
+            return mWidgetCategory;
+        }
+
+        public void setWidgetCategory(String widgetCategory) {
+            mWidgetCategory = widgetCategory;
+        }
+
+        public String getWidgetState() {
+            return mWidgetState;
+        }
+
+        public void setWidgetState(String widgetState) {
+            mWidgetState = widgetState;
+        }
+
+        public long getTotalFrames() {
+            return mTotalFrames;
+        }
+
+        public void setTotalFrames(long totalFrames) {
+            mTotalFrames = totalFrames;
+        }
+
+        public long getJankyFrames() {
+            return mJankyFrames;
+        }
+
+        public void setJankyFrames(long jankyFrames) {
+            mJankyFrames = jankyFrames;
+        }
+
+        public int[] getFrameOverrunBuckets() {
+            return mFrameOverrunBuckets;
+        }
+
+        public int getRefreshRate() {
+            return mRefreshRate;
+        }
+
+        public void setRefreshRate(int refreshRate) {
+            mRefreshRate = refreshRate;
+        }
+
+        /**
+         * Will convert the frame time from ns to ms and record how long the frame took to render.
+         */
+        public void recordFrameOverrun(long frameTimeNano) {
+            try {
+                // TODO b/375650163 calculate frame overrun from refresh rate.
+                int frameTimeMillis = (int) frameTimeNano / NANOS_PER_MS;
+                mFrameOverrunBuckets[indexForFrameOverrun(frameTimeMillis)]++;
+            } catch (IndexOutOfBoundsException exception) {
+                // TODO b/375650163 figure out how to handle this if it happens.
+            }
+        }
+
+        /**
+         * resets all fields in the object back to defaults.
+         */
+        public void clearStats() {
+            this.mUid = -1;
+            this.mActivityName = "";
+            this.processedVsyncId = -1;
+            this.mJankyFrames = 0;
+            this.mTotalFrames = 0;
+            this.mWidgetCategory = "";
+            this.mWidgetState = "";
+            this.mRefreshRate = 0;
+            clearHistogram();
+        }
+
+        private void clearHistogram() {
+            for (int i = 0; i < mFrameOverrunBuckets.length; i++) {
+                mFrameOverrunBuckets[i] = 0;
+            }
+        }
+
+        // This takes the overrun time and returns what bucket it belongs to in the histogram.
+        private int indexForFrameOverrun(int overrunTime) {
+            if (overrunTime < 20) {
+                if (overrunTime >= -20) {
+                    return (overrunTime + 20) / 2 + 12;
+                }
+                if (overrunTime >= -30) {
+                    return (overrunTime + 30) / 5 + 10;
+                }
+                if (overrunTime >= -100) {
+                    return (overrunTime + 100) / 10 + 3;
+                }
+                if (overrunTime >= -200) {
+                    return (overrunTime + 200) / 50 + 1;
+                }
+                return 0;
+            }
+            if (overrunTime < 30) {
+                return (overrunTime - 20) / 5 + 32;
+            }
+            if (overrunTime < 100) {
+                return (overrunTime - 30) / 10 + 34;
+            }
+            if (overrunTime < 200) {
+                return (overrunTime - 50) / 100 + 41;
+            }
+            if (overrunTime < 1000) {
+                return (overrunTime - 200) / 100 + 43;
+            }
+            return sFrameOverrunHistogramBounds.length - 1;
+        }
+
+    }
+}
diff --git a/core/java/android/app/jank/StateTracker.java b/core/java/android/app/jank/StateTracker.java
index cb457ff..c86d5a5 100644
--- a/core/java/android/app/jank/StateTracker.java
+++ b/core/java/android/app/jank/StateTracker.java
@@ -36,7 +36,6 @@
  * @hide
  */
 @FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
-@VisibleForTesting
 public class StateTracker {
 
     // Used to synchronize access to mPreviousStates.
@@ -188,7 +187,6 @@
     /**
      * @hide
      */
-    @VisibleForTesting
     public static class StateData {
 
         // Concatenated string of widget category, widget state and widget id.
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
index 7484a85..f51f748 100644
--- a/core/java/android/app/performance.aconfig
+++ b/core/java/android/app/performance.aconfig
@@ -7,7 +7,7 @@
      is_exported: true
      is_fixed_read_only: true
      description: "PropertyInvalidatedCache uses shared memory for nonces."
-     bug: "366552454"
+     bug: "360897450"
 }
 
 flag {
@@ -16,5 +16,5 @@
      is_exported: true
      is_fixed_read_only: true
      description: "Enforce PropertyInvalidatedCache.setTestMode() protocol"
-     bug: "366552454"
+     bug: "360897450"
 }
diff --git a/core/java/android/app/trust/ITrustManager.aidl b/core/java/android/app/trust/ITrustManager.aidl
index 740f593..730bb73 100644
--- a/core/java/android/app/trust/ITrustManager.aidl
+++ b/core/java/android/app/trust/ITrustManager.aidl
@@ -41,5 +41,6 @@
     void unlockedByBiometricForUser(int userId, in BiometricSourceType source);
     void clearAllBiometricRecognized(in BiometricSourceType target, int unlockedUser);
     boolean isActiveUnlockRunning(int userId);
+    @EnforcePermission("ACCESS_FINE_LOCATION")
     boolean isInSignificantPlace();
 }
diff --git a/core/java/android/app/trust/TrustManager.java b/core/java/android/app/trust/TrustManager.java
index 88d4d69..1ef83cd 100644
--- a/core/java/android/app/trust/TrustManager.java
+++ b/core/java/android/app/trust/TrustManager.java
@@ -305,6 +305,7 @@
      *
      * @hide
      */
+    @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
     public boolean isInSignificantPlace() {
         try {
             return mService.isInSignificantPlace();
diff --git a/core/java/android/app/wallpaper/WallpaperDescription.java b/core/java/android/app/wallpaper/WallpaperDescription.java
index dedcb48..c3d6340 100644
--- a/core/java/android/app/wallpaper/WallpaperDescription.java
+++ b/core/java/android/app/wallpaper/WallpaperDescription.java
@@ -18,6 +18,8 @@
 
 import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+
 import android.annotation.FlaggedApi;
 import android.app.WallpaperInfo;
 import android.content.ComponentName;
@@ -29,6 +31,7 @@
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.util.Log;
+import android.util.Xml;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -40,6 +43,8 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -149,6 +154,46 @@
         return Objects.hash(mComponent, mId);
     }
 
+    ////// Stream read/write
+
+    /**
+     * Writes the content of the {@link WallpaperDescription} to a {@link OutputStream}.
+     *
+     * <p>The content can be read by {@link #readFromStream}. This method is intended for use by
+     * trusted apps only, and the format is not guaranteed to be stable.</p>
+     */
+    public void writeToStream(@NonNull OutputStream outputStream) throws IOException {
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        serializer.setOutput(outputStream, UTF_8.name());
+        serializer.startTag(null, "description");
+        try {
+            saveToXml(serializer);
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        serializer.endTag(null, "description");
+        serializer.flush();
+    }
+
+    /**
+     * Reads a {@link PersistableBundle} from an {@link InputStream}.
+     *
+     * <p>The stream must be generated by {@link #writeToStream}. This method is intended for use by
+     * trusted apps only, and the format is not guaranteed to be stable.</p>
+     */
+    @NonNull
+    public static WallpaperDescription readFromStream(@NonNull InputStream inputStream)
+            throws IOException {
+        try {
+            TypedXmlPullParser parser = Xml.newFastPullParser();
+            parser.setInput(inputStream, UTF_8.name());
+            parser.next();
+            return WallpaperDescription.restoreFromXml(parser);
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+    }
+
     ////// XML storage
 
     /** @hide */
@@ -255,7 +300,7 @@
         mContent = PersistableBundle.CREATOR.createFromParcel(in);
     }
 
-    @Nullable
+    @NonNull
     public static final Creator<WallpaperDescription> CREATOR = new Creator<>() {
         @Override
         public WallpaperDescription createFromParcel(Parcel source) {
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 72f992a..79eeaa9 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -306,6 +306,38 @@
     }
 
     /**
+     * Returns an {@link IntentSender} for starting the configuration activity for the widget.
+     *
+     * @return The {@link IntentSender} or null if service is currently unavailable
+     *
+     * @throws android.content.ActivityNotFoundException If configuration activity is not found.
+     *
+     * @see #startAppWidgetConfigureActivityForResult
+     *
+     * @hide
+     */
+    @Nullable
+    public final IntentSender getIntentSenderForConfigureActivity(int appWidgetId,
+            int intentFlags)  {
+        if (sService == null) {
+            return null;
+        }
+
+        IntentSender intentSender;
+        try {
+            intentSender = sService.createAppWidgetConfigIntentSender(mContextOpPackageName,
+                    appWidgetId, intentFlags);
+        } catch (RemoteException e) {
+            throw new RuntimeException("system server dead?", e);
+        }
+
+        if (intentSender == null) {
+            throw new ActivityNotFoundException();
+        }
+        return intentSender;
+    }
+
+    /**
      * Starts an app widget provider configure activity for result on behalf of the caller.
      * Use this method if the provider is in another profile as you are not allowed to start
      * an activity in another profile. You can optionally provide a request code that is
@@ -330,18 +362,11 @@
             return;
         }
         try {
-            IntentSender intentSender = sService.createAppWidgetConfigIntentSender(
-                    mContextOpPackageName, appWidgetId, intentFlags);
-            if (intentSender != null) {
-                activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0,
-                        options);
-            } else {
-                throw new ActivityNotFoundException();
-            }
+            IntentSender intentSender = getIntentSenderForConfigureActivity(appWidgetId,
+                    intentFlags);
+            activity.startIntentSenderForResult(intentSender, requestCode, null, 0, 0, 0, options);
         } catch (IntentSender.SendIntentException e) {
             throw new ActivityNotFoundException();
-        } catch (RemoteException e) {
-            throw new RuntimeException("system server dead?", e);
         }
     }
 
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index d8142fd..8f20ea0 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -246,7 +246,8 @@
      * this widget. Can have the value {@link
      * AppWidgetProviderInfo#WIDGET_CATEGORY_HOME_SCREEN} or {@link
      * AppWidgetProviderInfo#WIDGET_CATEGORY_KEYGUARD} or {@link
-     * AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX}.
+     * AppWidgetProviderInfo#WIDGET_CATEGORY_SEARCHBOX} or {@link
+     * AppWidgetProviderInfo#WIDGET_CATEGORY_NOT_KEYGUARD}.
      */
     public static final String OPTION_APPWIDGET_HOST_CATEGORY = "appWidgetCategory";
 
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 1a80cac2..75c76d1 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -93,11 +93,26 @@
      */
     public static final int WIDGET_CATEGORY_SEARCHBOX = 4;
 
+    /**
+     * Indicates that the widget should never be shown on the keyguard.
+     *
+     * <p>Some keyguard style features may decide that {@link #WIDGET_CATEGORY_KEYGUARD} isn't
+     * required to be added by an app to show on the feature when chosen by a user.
+     * This category allows for a stronger statement about placement of the widget that, even in the
+     * above case, this widget should not be offered on the keyguard.
+     *
+     * <p>Setting this category doesn't change the behavior of AppWidgetManager queries, it is the
+     * responsibility of the widget surface to respect this value.
+     */
+    @FlaggedApi(android.appwidget.flags.Flags.FLAG_NOT_KEYGUARD_CATEGORY)
+    public static final int WIDGET_CATEGORY_NOT_KEYGUARD = 8;
+
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
             WIDGET_CATEGORY_HOME_SCREEN,
             WIDGET_CATEGORY_KEYGUARD,
             WIDGET_CATEGORY_SEARCHBOX,
+            WIDGET_CATEGORY_NOT_KEYGUARD,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CategoryFlags {}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 3839b5f..940a4d2 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -83,3 +83,12 @@
   is_exported: true
   is_fixed_read_only: true
 }
+
+flag {
+  name: "not_keyguard_category"
+  namespace: "app_widgets"
+  description: "Adds the NOT_KEYGUARD category to AppWidgetInfo categories"
+  bug: "365169792"
+  is_exported: true
+  is_fixed_read_only: true
+}
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 9eb6d56..9af2016 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -145,3 +145,11 @@
     bug: "370657575"
     is_exported: true
 }
+
+flag {
+    namespace: "virtual_devices"
+    name: "default_device_camera_access_policy"
+    description: "API for default device camera access policy"
+    bug: "371173368"
+    is_exported: true
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 5893da3..186f7b3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3658,6 +3658,27 @@
     public abstract void unregisterReceiver(BroadcastReceiver receiver);
 
     /**
+     * Returns the list of {@link IntentFilter} objects that have been registered for the given
+     * {@link BroadcastReceiver}.
+     *
+     * @param receiver The {@link BroadcastReceiver} whose registered intent filters
+     *                 should be retrieved.
+     *
+     * @return A list of registered intent filters, or an empty list if the given receiver is not
+     *         registered.
+     *
+     * @throws NullPointerException if the {@code receiver} is {@code null}.
+     *
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi") // TestApi
+    @TestApi
+    @NonNull
+    public List<IntentFilter> getRegisteredIntentFilters(@NonNull BroadcastReceiver receiver) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Request that a given application service be started.  The Intent
      * should either contain the complete class name of a specific service
      * implementation to start, or a specific package name to target.  If the
@@ -4873,6 +4894,8 @@
      * @see android.net.vcn.VcnManager
      * @hide
      */
+    @FlaggedApi(android.os.Flags.FLAG_MAINLINE_VCN_PLATFORM_API)
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final String VCN_MANAGEMENT_SERVICE = "vcn_management";
 
     /**
@@ -6654,8 +6677,8 @@
      *
      * @see #getSystemService(String)
      * @see android.telephony.satellite.SatelliteManager
-     * @hide
      */
+    @FlaggedApi(com.android.internal.telephony.flags.Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
     public static final String SATELLITE_SERVICE = "satellite";
 
     /**
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 79fa6ea..23d17cb 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -843,6 +843,13 @@
         mBase.unregisterReceiver(receiver);
     }
 
+    /** @hide */
+    @Override
+    @NonNull
+    public List<IntentFilter> getRegisteredIntentFilters(@NonNull BroadcastReceiver receiver) {
+        return mBase.getRegisteredIntentFilters(receiver);
+    }
+
     @Override
     public @Nullable ComponentName startService(Intent service) {
         return mBase.startService(service);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 9bec598..6fa5a9b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -41,10 +41,7 @@
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.StatusBarManager;
-import android.app.compat.CompatChanges;
 import android.bluetooth.BluetoothDevice;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.Overridable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -673,12 +670,6 @@
 @android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class Intent implements Parcelable, Cloneable {
     private static final String TAG = "Intent";
-
-    /** @hide */
-    @ChangeId
-    @Overridable
-    public static final long ENABLE_PREVENT_INTENT_REDIRECT = 29076063L;
-
     private static final String ATTR_ACTION = "action";
     private static final String TAG_CATEGORIES = "categories";
     private static final String ATTR_CATEGORY = "category";
@@ -908,7 +899,7 @@
         boolean isForeign = (intent.mLocalFlags & LOCAL_FLAG_FROM_PARCEL) != 0;
         boolean isWithoutTrustedCreatorToken =
                 (intent.mLocalFlags & Intent.LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT) == 0;
-        if (isForeign && isWithoutTrustedCreatorToken) {
+        if (isForeign && isWithoutTrustedCreatorToken && preventIntentRedirect()) {
             intent.addExtendedFlags(EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN);
         }
     }
@@ -12255,7 +12246,7 @@
      * @hide
      */
     public void collectExtraIntentKeys() {
-        if (!isPreventIntentRedirectEnabled()) return;
+        if (!preventIntentRedirect()) return;
 
         if (mExtras != null && !mExtras.isEmpty()) {
             for (String key : mExtras.keySet()) {
@@ -12272,14 +12263,6 @@
         }
     }
 
-    /**
-     * @hide
-     */
-    public static boolean isPreventIntentRedirectEnabled() {
-        return preventIntentRedirect() && CompatChanges.isChangeEnabled(
-                ENABLE_PREVENT_INTENT_REDIRECT);
-    }
-
     /** @hide */
     public void checkCreatorToken() {
         if (mExtras == null) return;
@@ -12368,7 +12351,7 @@
             out.writeInt(0);
         }
 
-        if (isPreventIntentRedirectEnabled()) {
+        if (preventIntentRedirect()) {
             if (mCreatorTokenInfo == null) {
                 out.writeInt(0);
             } else {
@@ -12435,7 +12418,7 @@
             mOriginalIntent = new Intent(in);
         }
 
-        if (isPreventIntentRedirectEnabled()) {
+        if (preventIntentRedirect()) {
             if (in.readInt() != 0) {
                 mCreatorTokenInfo = new CreatorTokenInfo();
                 mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 6db7dfe..ed965b3 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -78,8 +78,7 @@
 
     /**
      * Applications can use OverlayManager to create overlays to overlay on itself resources. The
-     * overlay target is itself, or the Android package, and the work range is only in caller
-     * application.
+     * overlay target is itself and the work range is only in caller application.
      *
      * <p>In {@link android.content.Context#getSystemService(String)}, it crashes because of {@link
      * java.lang.NullPointerException} if the parameter is OverlayManager. if the self-targeting is
@@ -402,7 +401,7 @@
     }
 
     /**
-     * Get the related information of self-targeting overlays for {@code targetPackageName}.
+     * Get the related information of overlays for {@code targetPackageName}.
      *
      * @param targetPackageName the target package name
      * @return a list of overlay information
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index 87b2e93..becd0ea 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -209,7 +209,6 @@
      */
     public static final class Builder {
         private final List<Request> mRequests = new ArrayList<>();
-        private boolean mSelfTargeting = false;
 
         /**
          * Request that an overlay package be enabled and change its loading
@@ -247,18 +246,6 @@
         }
 
         /**
-         * Request that an overlay package be self-targeting. Self-targeting overlays enable
-         * applications to overlay on itself resources. The overlay target is itself, or the Android
-         * package, and the work range is only in caller application.
-         * @param selfTargeting whether the overlay is self-targeting, the default is false.
-         * @hide
-         */
-        public Builder setSelfTargeting(boolean selfTargeting) {
-            mSelfTargeting = selfTargeting;
-            return this;
-        }
-
-        /**
          * Registers the fabricated overlay with the overlay manager so it can be enabled and
          * disabled for any user.
          *
@@ -299,7 +286,7 @@
          */
         @NonNull
         public OverlayManagerTransaction build() {
-            return new OverlayManagerTransaction(mRequests, mSelfTargeting);
+            return new OverlayManagerTransaction(mRequests, false /* selfTargeting */);
         }
     }
 
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 35f9cff..528bde8 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -536,7 +536,7 @@
 
 flag {
   name: "ignore_restrictions_when_deleting_private_profile"
-  namespace: "multiuser"
+  namespace: "profile_experiences"
   description: "Ignore any user restrictions when deleting private profiles."
   bug: "350953833"
   metadata {
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 347bebd..6fd4d01 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -75,10 +75,7 @@
     private static final String TAG = "AssetManager";
     private static final boolean DEBUG_REFS = false;
 
-    /**
-     * @hide
-     */
-    public static final String FRAMEWORK_APK_PATH = getFrameworkApkPath();
+    private static final String FRAMEWORK_APK_PATH = getFrameworkApkPath();
     private static final String FRAMEWORK_APK_PATH_DEVICE = "/system/framework/framework-res.apk";
     private static final String FRAMEWORK_APK_PATH_RAVENWOOD = "ravenwood-data/framework-res.apk";
 
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index c2febae..e8893e4 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -5,6 +5,9 @@
     },
     {
       "path": "frameworks/base/core/tests/coretests/src/com/android/internal/content/res"
+    },
+    {
+      "path": "platform_testing/libraries/screenshot"
     }
   ],
   "presubmit": [
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index 0af2f25..e98fc0c 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -74,3 +74,12 @@
     description: "Feature flag for passing a dimension to create an frro"
     bug: "369672322"
 }
+
+flag {
+    name: "rro_control_for_android_no_overlayable"
+    is_exported: true
+    namespace: "resource_manager"
+    description: "Allow enabling and disabling RROs targeting android package with no overlayable"
+    bug: "364035303"
+}
+
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 830b7e0..b097bc0 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -90,6 +90,8 @@
             throws IOException {
         Objects.requireNonNull(overlayInfo);
         Preconditions.checkArgument(overlayInfo.isFabricated(), "Not accepted overlay");
+        Preconditions.checkStringNotEmpty(
+                overlayInfo.getTargetOverlayableName(), "Without overlayable name");
         final String overlayName =
                 OverlayManagerImpl.checkOverlayNameValid(overlayInfo.getOverlayName());
         final String path =
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index e8d7e1e..a7e7a57 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -150,6 +150,7 @@
      * All values are in micro-Tesla (uT) and measure the ambient magnetic field
      * in the X, Y and Z axis.
      *
+     *
      * <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:
      * </h4> All values are in radians/second and measure the rate of rotation
      * around the device's local X, Y and Z axis. The coordinate system is the
@@ -406,10 +407,9 @@
      * </h4>
      *
      * <ul>
-     * <li> values[0]: ambient (room) temperature in degree Celsius.</li>
+     * <li> values[0]: Ambient (room) temperature in degrees Celsius</li>
      * </ul>
      *
-     *
      * <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD_UNCALIBRATED
      * Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED}:</h4>
      * Similar to {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD},
@@ -549,8 +549,20 @@
      *  <li> values[0]: 1.0 </li>
      * </ul>
      *
-     *   <h4>{@link android.hardware.Sensor#TYPE_HEART_BEAT
-     * Sensor.TYPE_HEART_BEAT}:</h4>
+     * <h4>{@link android.hardware.Sensor#TYPE_STEP_COUNTER Sensor.TYPE_STEP_COUNTER}:</h4>
+     *
+     * <ul>
+     * <li>values[0]: Number of steps taken by the user since the last reboot while the sensor is
+     * activated</li>
+     * </ul>
+     *
+     * <h4>{@link android.hardware.Sensor#TYPE_STEP_DETECTOR Sensor.TYPE_STEP_DETECTOR}:</h4>
+     *
+     * <ul>
+     * <li>values[0]: Always set to 1.0, representing a single step detected event</li>
+     * </ul>
+     *
+     * <h4>{@link android.hardware.Sensor#TYPE_HEART_BEAT Sensor.TYPE_HEART_BEAT}:</h4>
      *
      * A sensor of this type returns an event everytime a heart beat peak is
      * detected.
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 2d29900..7a23033 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -61,3 +61,13 @@
   description: "Feature flag for biometric prompt improvements in private space"
   bug: "365554098"
 }
+
+flag {
+  name: "effective_user_bp"
+  namespace: "biometrics_framework"
+  description: "Feature flag for using effective user in biometric prompt"
+  bug: "365094949"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1137e1e..16d82ca 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -775,6 +775,46 @@
             new Key<int[]>("android.colorCorrection.availableAberrationModes", int[].class);
 
     /**
+     * <p>The range of supported color temperature values for
+     * {@link CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE android.colorCorrection.colorTemperature}.</p>
+     * <p>This key lists the valid range of color temperature values for
+     * {@link CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE android.colorCorrection.colorTemperature} supported by this camera device.</p>
+     * <p>This key will be null on devices that do not support CCT mode for
+     * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}.</p>
+     * <p><b>Range of valid values:</b><br></p>
+     * <p>The minimum supported range will be [2856K,6500K]. The maximum supported
+     * range will be [1000K,40000K].</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<android.util.Range<Integer>> COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE =
+            new Key<android.util.Range<Integer>>("android.colorCorrection.colorTemperatureRange", new TypeReference<android.util.Range<Integer>>() {{ }});
+
+    /**
+     * <p>List of color correction modes for {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} that are
+     * supported by this camera device.</p>
+     * <p>This key lists the valid modes for {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. If no
+     * color correction modes are available for a device, this key will be null.</p>
+     * <p>Camera devices that have a FULL hardware level will always include at least
+     * FAST, HIGH_QUALITY, and TRANSFORM_MATRIX modes.</p>
+     * <p><b>Range of valid values:</b><br>
+     * Any value listed in {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<int[]> COLOR_CORRECTION_AVAILABLE_MODES =
+            new Key<int[]>("android.colorCorrection.availableModes", int[].class);
+
+    /**
      * <p>List of auto-exposure antibanding modes for {@link CaptureRequest#CONTROL_AE_ANTIBANDING_MODE android.control.aeAntibandingMode} that are
      * supported by this camera device.</p>
      * <p>Not all of the auto-exposure anti-banding modes may be
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index acb48f3..86bbd4a 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -2169,6 +2169,22 @@
      */
     public static final int COLOR_CORRECTION_MODE_HIGH_QUALITY = 2;
 
+    /**
+     * <p>Use
+     * {@link CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE android.colorCorrection.colorTemperature} and
+     * {@link CaptureRequest#COLOR_CORRECTION_COLOR_TINT android.colorCorrection.colorTint} to adjust the white balance based
+     * on correlated color temperature.</p>
+     * <p>If AWB is enabled with <code>{@link CaptureRequest#CONTROL_AWB_MODE android.control.awbMode} != OFF</code>, then
+     * CCT is ignored.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TEMPERATURE
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TINT
+     * @see CaptureRequest#CONTROL_AWB_MODE
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final int COLOR_CORRECTION_MODE_CCT = 3;
+
     //
     // Enumeration values for CaptureRequest#COLOR_CORRECTION_ABERRATION_MODE
     //
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index a5c5a99..8142bbe 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1086,11 +1086,17 @@
      *   <li>{@link #COLOR_CORRECTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
      * </ul>
      *
+     * <p><b>Available values for this device:</b><br>
+     * Starting from API level 36, {@link CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_MODES android.colorCorrection.availableModes}
+     * can be used to check the list of supported values. Prior to API level 36,
+     * TRANSFORM_MATRIX, HIGH_QUALITY, and FAST are guaranteed to be available
+     * as valid modes on devices that support this key.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Full capability</b> -
      * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
      * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
      *
+     * @see CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_MODES
      * @see CaptureRequest#COLOR_CORRECTION_GAINS
      * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
      * @see CaptureRequest#CONTROL_AWB_MODE
@@ -1195,6 +1201,60 @@
             new Key<Integer>("android.colorCorrection.aberrationMode", int.class);
 
     /**
+     * <p>Specifies the color temperature for CCT mode in Kelvin
+     * to adjust the white balance of the image.</p>
+     * <p>Sets the color temperature in Kelvin units for when
+     * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is CCT to adjust the
+     * white balance of the image.</p>
+     * <p>If CCT mode is enabled without a requested color temperature,
+     * a default value will be set by the camera device. The default value can be
+     * retrieved by checking the corresponding capture result. Color temperatures
+     * requested outside the advertised {@link CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE android.colorCorrection.colorTemperatureRange}
+     * will be clamped.</p>
+     * <p><b>Units</b>: Kelvin</p>
+     * <p><b>Range of valid values:</b><br>
+     * {@link CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE android.colorCorrection.colorTemperatureRange}</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<Integer> COLOR_CORRECTION_COLOR_TEMPERATURE =
+            new Key<Integer>("android.colorCorrection.colorTemperature", int.class);
+
+    /**
+     * <p>Specifies the color tint for CCT mode to adjust the white
+     * balance of the image.</p>
+     * <p>Sets the color tint for when {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}
+     * is CCT to adjust the white balance of the image.</p>
+     * <p>If CCT mode is enabled without a requested color tint,
+     * a default value will be set by the camera device. The default value can be
+     * retrieved by checking the corresponding capture result. Color tints requested
+     * outside the supported range will be clamped to the nearest limit (-50 or +50).</p>
+     * <p><b>Units</b>: D_uv defined as the distance from the Planckian locus on the CIE 1931 xy
+     * chromaticity diagram, with the range ±50 mapping to ±0.01 D_uv</p>
+     * <p><b>Range of valid values:</b><br>
+     * The supported range, -50 to +50, corresponds to a D_uv distance
+     * of ±0.01 below and above the Planckian locus. Some camera devices may have
+     * limitations to achieving the full ±0.01 D_uv range at some color temperatures
+     * (e.g., below 1500K). In these cases, the applied D_uv value may be clamped and
+     * the actual color tint will be reported in the {@link CaptureRequest#COLOR_CORRECTION_COLOR_TINT android.colorCorrection.colorTint}
+     * result.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TINT
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<Integer> COLOR_CORRECTION_COLOR_TINT =
+            new Key<Integer>("android.colorCorrection.colorTint", int.class);
+
+    /**
      * <p>The desired setting for the camera device's auto-exposure
      * algorithm's antibanding compensation.</p>
      * <p>Some kinds of lighting fixtures, such as some fluorescent
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index a6bdb3f..ae72ca4 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -487,11 +487,17 @@
      *   <li>{@link #COLOR_CORRECTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
      * </ul>
      *
+     * <p><b>Available values for this device:</b><br>
+     * Starting from API level 36, {@link CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_MODES android.colorCorrection.availableModes}
+     * can be used to check the list of supported values. Prior to API level 36,
+     * TRANSFORM_MATRIX, HIGH_QUALITY, and FAST are guaranteed to be available
+     * as valid modes on devices that support this key.</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
      * <p><b>Full capability</b> -
      * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
      * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
      *
+     * @see CameraCharacteristics#COLOR_CORRECTION_AVAILABLE_MODES
      * @see CaptureRequest#COLOR_CORRECTION_GAINS
      * @see CaptureRequest#COLOR_CORRECTION_TRANSFORM
      * @see CaptureRequest#CONTROL_AWB_MODE
@@ -596,6 +602,60 @@
             new Key<Integer>("android.colorCorrection.aberrationMode", int.class);
 
     /**
+     * <p>Specifies the color temperature for CCT mode in Kelvin
+     * to adjust the white balance of the image.</p>
+     * <p>Sets the color temperature in Kelvin units for when
+     * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} is CCT to adjust the
+     * white balance of the image.</p>
+     * <p>If CCT mode is enabled without a requested color temperature,
+     * a default value will be set by the camera device. The default value can be
+     * retrieved by checking the corresponding capture result. Color temperatures
+     * requested outside the advertised {@link CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE android.colorCorrection.colorTemperatureRange}
+     * will be clamped.</p>
+     * <p><b>Units</b>: Kelvin</p>
+     * <p><b>Range of valid values:</b><br>
+     * {@link CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE android.colorCorrection.colorTemperatureRange}</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#COLOR_CORRECTION_COLOR_TEMPERATURE_RANGE
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<Integer> COLOR_CORRECTION_COLOR_TEMPERATURE =
+            new Key<Integer>("android.colorCorrection.colorTemperature", int.class);
+
+    /**
+     * <p>Specifies the color tint for CCT mode to adjust the white
+     * balance of the image.</p>
+     * <p>Sets the color tint for when {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}
+     * is CCT to adjust the white balance of the image.</p>
+     * <p>If CCT mode is enabled without a requested color tint,
+     * a default value will be set by the camera device. The default value can be
+     * retrieved by checking the corresponding capture result. Color tints requested
+     * outside the supported range will be clamped to the nearest limit (-50 or +50).</p>
+     * <p><b>Units</b>: D_uv defined as the distance from the Planckian locus on the CIE 1931 xy
+     * chromaticity diagram, with the range ±50 mapping to ±0.01 D_uv</p>
+     * <p><b>Range of valid values:</b><br>
+     * The supported range, -50 to +50, corresponds to a D_uv distance
+     * of ±0.01 below and above the Planckian locus. Some camera devices may have
+     * limitations to achieving the full ±0.01 D_uv range at some color temperatures
+     * (e.g., below 1500K). In these cases, the applied D_uv value may be clamped and
+     * the actual color tint will be reported in the {@link CaptureRequest#COLOR_CORRECTION_COLOR_TINT android.colorCorrection.colorTint}
+     * result.</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CaptureRequest#COLOR_CORRECTION_COLOR_TINT
+     * @see CaptureRequest#COLOR_CORRECTION_MODE
+     */
+    @PublicKey
+    @NonNull
+    @FlaggedApi(Flags.FLAG_COLOR_TEMPERATURE)
+    public static final Key<Integer> COLOR_CORRECTION_COLOR_TINT =
+            new Key<Integer>("android.colorCorrection.colorTint", int.class);
+
+    /**
      * <p>The desired setting for the camera device's auto-exposure
      * algorithm's antibanding compensation.</p>
      * <p>Some kinds of lighting fixtures, such as some fluorescent
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 22dbf5b..d38be9b 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -1457,7 +1457,7 @@
     /**
      * Set the mirroring mode for a surface belonging to this OutputConfiguration
      *
-     * <p>This function is identical to {@link #setMirroMode(int)} if {@code surface} is
+     * <p>This function is identical to {@link #setMirrorMode(int)} if {@code surface} is
      * the only surface belonging to this OutputConfiguration.</p>
      *
      * <p>If this OutputConfiguration contains a deferred surface, the application can either
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 75ffcc3..399184c 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -491,10 +491,16 @@
         public static final int POLICY_DIM = 2;
         // Policy: Make the screen bright as usual.
         public static final int POLICY_BRIGHT = 3;
+        // The maximum policy constant. Useful for iterating through all constants in tests.
+        public static final int POLICY_MAX = POLICY_BRIGHT;
 
         // The basic overall policy to apply: off, doze, dim or bright.
         public int policy;
 
+        // The reason behind the current policy.
+        @Display.StateReason
+        public int policyReason;
+
         // If true, the proximity sensor overrides the screen state when an object is
         // nearby, turning it off temporarily until the object is moved away.
         public boolean useProximitySensor;
@@ -541,6 +547,7 @@
 
         public DisplayPowerRequest() {
             policy = POLICY_BRIGHT;
+            policyReason = Display.STATE_REASON_DEFAULT_POLICY;
             useProximitySensor = false;
             screenBrightnessOverride = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             screenAutoBrightnessAdjustmentOverride = Float.NaN;
@@ -561,6 +568,7 @@
 
         public void copyFrom(DisplayPowerRequest other) {
             policy = other.policy;
+            policyReason = other.policyReason;
             useProximitySensor = other.useProximitySensor;
             screenBrightnessOverride = other.screenBrightnessOverride;
             screenBrightnessOverrideTag = other.screenBrightnessOverrideTag;
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 4a9efe0..c456698 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -20,16 +20,18 @@
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS;
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG;
 import static com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG;
+import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
 import static com.android.hardware.input.Flags.keyboardA11yBounceKeysFlag;
+import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
 import static com.android.hardware.input.Flags.keyboardA11ySlowKeysFlag;
 import static com.android.hardware.input.Flags.keyboardA11yStickyKeysFlag;
-import static com.android.hardware.input.Flags.keyboardA11yMouseKeys;
 import static com.android.hardware.input.Flags.mouseReverseVerticalScrolling;
 import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
 import static com.android.hardware.input.Flags.touchpadTapDragging;
+import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
 import static com.android.hardware.input.Flags.touchpadVisualizer;
-import static com.android.input.flags.Flags.enableInputFilterRustImpl;
 import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
+import static com.android.input.flags.Flags.enableInputFilterRustImpl;
 import static com.android.input.flags.Flags.keyboardRepeatKeys;
 
 import android.Manifest;
@@ -379,6 +381,15 @@
     }
 
     /**
+     * Returns true if the feature flag for the touchpad three-finger tap shortcut is enabled.
+     *
+     * @hide
+     */
+    public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() {
+        return enableCustomizableInputGestures() && touchpadThreeFingerTapShortcut();
+    }
+
+    /**
      * Returns true if the feature flag for mouse reverse vertical scrolling is enabled.
      * @hide
      */
@@ -498,6 +509,22 @@
     }
 
     /**
+     * Returns true if three-finger taps on the touchpad should trigger a customizable shortcut
+     * rather than a middle click.
+     *
+     * The returned value only applies to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @return Whether three-finger taps should trigger the shortcut.
+     *
+     * @hide
+     */
+    public static boolean useTouchpadThreeFingerTapShortcut(@NonNull Context context) {
+        // TODO(b/365063048): determine whether to enable the shortcut based on the settings.
+        return isTouchpadThreeFingerTapShortcutFeatureFlagEnabled();
+    }
+
+    /**
      * Whether a pointer icon will be shown over the location of a stylus pointer.
      *
      * @hide
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 0f290d9..9d42b67 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -113,6 +113,10 @@
     public static final int KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS = 65;
     public static final int KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS = 66;
     public static final int KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS = 67;
+    public static final int KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW = 68;
+    public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69;
+    public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 70;
+    public static final int KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE = 71;
 
     public static final int FLAG_CANCELLED = 1;
 
@@ -194,7 +198,11 @@
             KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
             KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
             KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
-            KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
+            KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
+            KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+            KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+            KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+            KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface KeyGestureType {
@@ -541,6 +549,14 @@
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
             case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
                 return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
+            case KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SNAP_LEFT_FREEFORM_WINDOW;
+            case KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SNAP_RIGHT_FREEFORM_WINDOW;
+            case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MAXIMIZE_FREEFORM_WINDOW;
+            case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE:
+                return FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RESTORE_FREEFORM_WINDOW_SIZE;
             default:
                 return LOG_EVENT_UNSPECIFIED;
         }
@@ -749,6 +765,14 @@
                 return "KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS";
             case KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS:
                 return "KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS";
+            case KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW:
+                return "KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW";
+            case KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW:
+                return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW";
+            case KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW:
+                return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW";
+            case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE:
+                return "KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE";
             default:
                 return Integer.toHexString(value);
         }
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index e6982b9..f9cb94a 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -1,7 +1,10 @@
 package: "com.android.hardware.input"
 container: "system"
 
-# Project link: https://gantry.corp.google.com/projects/android_platform_input_native/changes
+# Project link: https://gantry.corp.google.com/projects/android_platform_input/changes
+
+# NOTE: the input_native namespace is deprecated. New flags should be added to the input namespace
+# instead.
 
 flag {
     namespace: "input_native"
@@ -155,3 +158,10 @@
   description: "Allows privileged focused windows to capture power key events."
   bug: "357144512"
 }
+
+flag {
+  name: "touchpad_three_finger_tap_shortcut"
+  namespace: "input"
+  description: "Turns three-finger touchpad taps into a customizable shortcut."
+  bug: "365063048"
+}
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 48eb968..f7dc790 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -5,13 +5,6 @@
 # Flags used for module APIs must be in aconfig files under each modules
 
 flag {
-  name: "ipsec_transform_state"
-  namespace: "core_networking_ipsec"
-  description: "The flag controls the access for getIpSecTransformState and IpSecTransformState"
-  bug: "308011229"
-}
-
-flag {
     name: "powered_off_finding_platform"
     namespace: "nearby"
     description: "Controls whether the Powered Off Finding feature is enabled"
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 3e5ac6f..66f4198 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -21,6 +21,7 @@
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Process;
+import android.os.UserHandle;
 import android.ravenwood.annotation.RavenwoodKeepWholeClass;
 import android.ravenwood.annotation.RavenwoodRedirect;
 import android.ravenwood.annotation.RavenwoodRedirectionClass;
@@ -76,19 +77,14 @@
     private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
     private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
     private IdleHandler[] mPendingIdleHandlers;
-    private boolean mLegacyQuitting;
+    private boolean mQuitting;
 
     // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
-    private boolean mLegacyBlocked;
+    private boolean mBlocked;
 
     // Tracks the number of async message. We use this in enqueueMessage() to avoid searching the
     // queue for async messages when inserting a message at the tail.
-    private int mLegacyAsyncMessageCount;
-
-    // The next barrier token.
-    // Barriers are indicated by messages with a null target whose arg1 field carries the token.
-    @UnsupportedAppUsage
-    private int mLegacyNextBarrierToken;
+    private int mAsyncMessageCount;
 
     /*
      * Select between two implementations of message queue. The legacy implementation is used
@@ -97,7 +93,9 @@
      * system processes and provides a higher level of concurrency and higher enqueue throughput
      * than the legacy implementation.
      */
-    private static boolean sForceConcurrent = false;
+    private static boolean sUseConcurrent;
+
+    private static boolean sUseConcurrentInitialized = false;
 
     @RavenwoodRedirect
     private native static long nativeInit();
@@ -114,8 +112,9 @@
     private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
 
     MessageQueue(boolean quitAllowed) {
-        if (!sForceConcurrent) {
-            sForceConcurrent = Process.myUid() < Process.FIRST_APPLICATION_UID;
+        if (!sUseConcurrentInitialized) {
+            sUseConcurrent = UserHandle.isCore(Process.myUid());
+            sUseConcurrentInitialized = true;
         }
         mQuitAllowed = quitAllowed;
         mPtr = nativeInit();
@@ -139,7 +138,7 @@
         }
     }
 
-    private class MatchDeliverableMessages extends MessageCompare {
+    private static final class MatchDeliverableMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -159,7 +158,7 @@
      * @return True if the looper is idle.
      */
     public boolean isIdle() {
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             final long now = SystemClock.uptimeMillis();
 
             if (stackHasMessages(null, 0, null, null, now, mMatchDeliverableMessages, false)) {
@@ -209,7 +208,7 @@
         if (handler == null) {
             throw new NullPointerException("Can't add a null IdleHandler");
         }
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             synchronized (mIdleHandlersLock) {
                 mIdleHandlers.add(handler);
             }
@@ -230,7 +229,7 @@
      * @param handler The IdleHandler to be removed.
      */
     public void removeIdleHandler(@NonNull IdleHandler handler) {
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             synchronized (mIdleHandlersLock) {
                 mIdleHandlers.remove(handler);
             }
@@ -253,7 +252,7 @@
      * @hide
      */
     public boolean isPolling() {
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             // If the loop is quitting then it must not be idling.
             // We can assume mPtr != 0 when sQuitting is false.
             return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr);
@@ -266,8 +265,8 @@
 
     private boolean isPollingLocked() {
         // If the loop is quitting then it must not be idling.
-        // We can assume mPtr != 0 when mLegacyQuitting is false.
-        return !mLegacyQuitting && nativeIsPolling(mPtr);
+        // We can assume mPtr != 0 when mQuitting is false.
+        return !mQuitting && nativeIsPolling(mPtr);
     }
 
     /**
@@ -304,7 +303,7 @@
             throw new IllegalArgumentException("listener must not be null");
         }
 
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             synchronized (mFileDescriptorRecordsLock) {
                 updateOnFileDescriptorEventListenerLocked(fd, events, listener);
             }
@@ -332,7 +331,7 @@
         if (fd == null) {
             throw new IllegalArgumentException("fd must not be null");
         }
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             synchronized (mFileDescriptorRecordsLock) {
                 updateOnFileDescriptorEventListenerLocked(fd, 0, null);
             }
@@ -389,7 +388,7 @@
         final int oldWatchedEvents;
         final OnFileDescriptorEventListener listener;
         final int seq;
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             synchronized (mFileDescriptorRecordsLock) {
                 record = mFileDescriptorRecords.get(fd);
                 if (record == null) {
@@ -709,7 +708,7 @@
 
     @UnsupportedAppUsage
     Message next() {
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             return nextConcurrent();
         }
 
@@ -748,7 +747,7 @@
                         nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                     } else {
                         // Got a message.
-                        mLegacyBlocked = false;
+                        mBlocked = false;
                         if (prevMsg != null) {
                             prevMsg.next = msg.next;
                             if (prevMsg.next == null) {
@@ -764,7 +763,7 @@
                         if (DEBUG) Log.v(TAG_L, "Returning message: " + msg);
                         msg.markInUse();
                         if (msg.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         if (TRACE) {
                             Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
@@ -777,7 +776,7 @@
                 }
 
                 // Process the quit message now that all pending messages have been handled.
-                if (mLegacyQuitting) {
+                if (mQuitting) {
                     dispose();
                     return null;
                 }
@@ -791,7 +790,7 @@
                 }
                 if (pendingIdleHandlerCount <= 0) {
                     // No idle handlers to run.  Loop and wait some more.
-                    mLegacyBlocked = true;
+                    mBlocked = true;
                     continue;
                 }
 
@@ -835,7 +834,7 @@
             throw new IllegalStateException("Main thread not allowed to quit.");
         }
 
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             synchronized (mIdleHandlersLock) {
                 if (sQuitting.compareAndSet(this, false, true)) {
                     if (safe) {
@@ -850,10 +849,10 @@
             }
         } else {
             synchronized (this) {
-                if (mLegacyQuitting) {
+                if (mQuitting) {
                     return;
                 }
-                mLegacyQuitting = true;
+                mQuitting = true;
 
                 if (safe) {
                     removeAllFutureMessagesLocked();
@@ -861,7 +860,7 @@
                     removeAllMessagesLocked();
                 }
 
-                // We can assume mPtr != 0 because mLegacyQuitting was previously false.
+                // We can assume mPtr != 0 because mQuitting was previously false.
                 nativeWake(mPtr);
             }
         }
@@ -899,8 +898,13 @@
     private int postSyncBarrier(long when) {
         // Enqueue a new sync barrier token.
         // We don't need to wake the queue because the purpose of a barrier is to stall it.
-        if (sForceConcurrent) {
-            final int token = mNextBarrierToken.getAndIncrement();
+        if (sUseConcurrent) {
+            final int token = mNextBarrierTokenAtomic.getAndIncrement();
+
+            // b/376573804: apps and tests may expect to be able to use reflection
+            // to read this value. Make some effort to support this legacy use case.
+            mNextBarrierToken = token + 1;
+
             final Message msg = Message.obtain();
 
             msg.markInUse();
@@ -915,7 +919,7 @@
         }
 
         synchronized (this) {
-            final int token = mLegacyNextBarrierToken++;
+            final int token = mNextBarrierToken++;
             final Message msg = Message.obtain();
             msg.markInUse();
             msg.when = when;
@@ -954,7 +958,7 @@
         }
     }
 
-    private class MatchBarrierToken extends MessageCompare {
+    private static final class MatchBarrierToken extends MessageCompare {
         int mBarrierToken;
 
         MatchBarrierToken(int token) {
@@ -987,7 +991,7 @@
     public void removeSyncBarrier(int token) {
         // Remove a sync barrier token from the queue.
         // If the queue is no longer stalled by a barrier then wake it.
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             boolean removed;
             MessageNode first;
             final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token);
@@ -1042,8 +1046,8 @@
             p.recycleUnchecked();
 
             // If the loop is quitting then it is already awake.
-            // We can assume mPtr != 0 when mLegacyQuitting is false.
-            if (needWake && !mLegacyQuitting) {
+            // We can assume mPtr != 0 when mQuitting is false.
+            if (needWake && !mQuitting) {
                 nativeWake(mPtr);
             }
         }
@@ -1054,7 +1058,7 @@
             throw new IllegalArgumentException("Message must have a target.");
         }
 
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             if (msg.isInUse()) {
                 throw new IllegalStateException(msg + " This message is already in use.");
             }
@@ -1067,7 +1071,7 @@
                 throw new IllegalStateException(msg + " This message is already in use.");
             }
 
-            if (mLegacyQuitting) {
+            if (mQuitting) {
                 IllegalStateException e = new IllegalStateException(
                         msg.target + " sending message to a Handler on a dead thread");
                 Log.w(TAG_L, e.getMessage(), e);
@@ -1083,7 +1087,7 @@
                 // New head, wake up the event queue if blocked.
                 msg.next = p;
                 mMessages = msg;
-                needWake = mLegacyBlocked;
+                needWake = mBlocked;
                 if (p == null) {
                     mLast = mMessages;
                 }
@@ -1091,14 +1095,14 @@
                 // Message is to be inserted at tail or middle of queue. Usually we don't have to
                 // wake up the event queue unless there is a barrier at the head of the queue and
                 // the message is the earliest asynchronous message in the queue.
-                needWake = mLegacyBlocked && p.target == null && msg.isAsynchronous();
+                needWake = mBlocked && p.target == null && msg.isAsynchronous();
 
                 // For readability, we split this portion of the function into two blocks based on
                 // whether tail tracking is enabled. This has a minor implication for the case
                 // where tail tracking is disabled. See the comment below.
                 if (Flags.messageQueueTailTracking()) {
                     if (when >= mLast.when) {
-                        needWake = needWake && mLegacyAsyncMessageCount == 0;
+                        needWake = needWake && mAsyncMessageCount == 0;
                         msg.next = null;
                         mLast.next = msg;
                         mLast = msg;
@@ -1156,10 +1160,10 @@
             }
 
             if (msg.isAsynchronous()) {
-                mLegacyAsyncMessageCount++;
+                mAsyncMessageCount++;
             }
 
-            // We can assume mPtr != 0 because mLegacyQuitting is false.
+            // We can assume mPtr != 0 because mQuitting is false.
             if (needWake) {
                 nativeWake(mPtr);
             }
@@ -1167,7 +1171,7 @@
         return true;
     }
 
-    private static class MatchHandlerWhatAndObject extends MessageCompare {
+    private static final class MatchHandlerWhatAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1183,7 +1187,7 @@
         if (h == null) {
             return false;
         }
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject,
                     false);
         }
@@ -1199,7 +1203,7 @@
         }
     }
 
-    private static class MatchHandlerWhatAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1215,7 +1219,7 @@
         if (h == null) {
             return false;
         }
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals,
                     false);
 
@@ -1232,7 +1236,7 @@
         }
     }
 
-    private static class MatchHandlerRunnableAndObject extends MessageCompare {
+    private static final class MatchHandlerRunnableAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1249,7 +1253,7 @@
         if (h == null) {
             return false;
         }
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject,
                     false);
         }
@@ -1266,7 +1270,7 @@
         }
     }
 
-    private static class MatchHandler extends MessageCompare {
+    private static final class MatchHandler extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1281,7 +1285,7 @@
         if (h == null) {
             return false;
         }
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false);
         }
         synchronized (this) {
@@ -1300,7 +1304,7 @@
         if (h == null) {
             return;
         }
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true);
             return;
         }
@@ -1313,7 +1317,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1331,7 +1335,7 @@
                             && (object == null || n.obj == object)) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1351,7 +1355,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true);
             return;
         }
@@ -1365,7 +1369,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1383,7 +1387,7 @@
                             && (object == null || object.equals(n.obj))) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1403,7 +1407,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
             return;
         }
@@ -1416,7 +1420,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1434,7 +1438,7 @@
                             && (object == null || n.obj == object)) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1449,7 +1453,7 @@
         }
     }
 
-    private static class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1466,7 +1470,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
             return;
         }
@@ -1479,7 +1483,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1497,7 +1501,7 @@
                             && (object == null || object.equals(n.obj))) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1512,7 +1516,7 @@
         }
     }
 
-    private static class MatchHandlerAndObject extends MessageCompare {
+    private static final class MatchHandlerAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1528,7 +1532,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
             return;
         }
@@ -1541,7 +1545,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1558,7 +1562,7 @@
                     if (n.target == h && (object == null || n.obj == object)) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1573,7 +1577,7 @@
         }
     }
 
-    private static class MatchHandlerAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1590,7 +1594,7 @@
             return;
         }
 
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
             return;
         }
@@ -1603,7 +1607,7 @@
                 Message n = p.next;
                 mMessages = n;
                 if (p.isAsynchronous()) {
-                    mLegacyAsyncMessageCount--;
+                    mAsyncMessageCount--;
                 }
                 p.recycleUnchecked();
                 p = n;
@@ -1620,7 +1624,7 @@
                     if (n.target == h && (object == null || object.equals(n.obj))) {
                         Message nn = n.next;
                         if (n.isAsynchronous()) {
-                            mLegacyAsyncMessageCount--;
+                            mAsyncMessageCount--;
                         }
                         n.recycleUnchecked();
                         p.next = nn;
@@ -1644,7 +1648,7 @@
         }
         mMessages = null;
         mLast = null;
-        mLegacyAsyncMessageCount = 0;
+        mAsyncMessageCount = 0;
     }
 
     private void removeAllFutureMessagesLocked() {
@@ -1672,7 +1676,7 @@
                     p = n;
                     n = p.next;
                     if (p.isAsynchronous()) {
-                        mLegacyAsyncMessageCount--;
+                        mAsyncMessageCount--;
                     }
                     p.recycleUnchecked();
                 } while (n != null);
@@ -1680,7 +1684,7 @@
         }
     }
 
-    private static class MatchAllMessages extends MessageCompare {
+    private static final class MatchAllMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1692,7 +1696,7 @@
         findOrRemoveMessages(null, -1, null, null, 0, mMatchAllMessages, true);
     }
 
-    private static class MatchAllFutureMessages extends MessageCompare {
+    private static final class MatchAllFutureMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1738,7 +1742,7 @@
 
     @NeverCompile
     void dump(Printer pw, String prefix, Handler h) {
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             long now = SystemClock.uptimeMillis();
             int n = 0;
 
@@ -1780,7 +1784,7 @@
                 n++;
             }
             pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()
-                    + ", quitting=" + mLegacyQuitting + ")");
+                    + ", quitting=" + mQuitting + ")");
         }
     }
 
@@ -1799,7 +1803,7 @@
 
     @NeverCompile
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
-        if (sForceConcurrent) {
+        if (sUseConcurrent) {
             final long messageQueueToken = proto.start(fieldId);
 
             StackNode node = (StackNode) sState.getVolatile(this);
@@ -1824,7 +1828,7 @@
                 msg.dumpDebug(proto, MessageQueueProto.MESSAGES);
             }
             proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPollingLocked());
-            proto.write(MessageQueueProto.IS_QUITTING, mLegacyQuitting);
+            proto.write(MessageQueueProto.IS_QUITTING, mQuitting);
         }
         proto.end(messageQueueToken);
     }
@@ -2292,7 +2296,11 @@
 
     // The next barrier token.
     // Barriers are indicated by messages with a null target whose arg1 field carries the token.
-    private final AtomicInteger mNextBarrierToken = new AtomicInteger(1);
+    private final AtomicInteger mNextBarrierTokenAtomic = new AtomicInteger(1);
+
+    // Must retain this for compatibility reasons.
+    @UnsupportedAppUsage
+    private int mNextBarrierToken;
 
     /* Protects mNextIsDrainingStack */
     private final ReentrantLock mDrainingLock = new ReentrantLock();
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
index 0efa02f..9db88d1 100644
--- a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -382,7 +382,7 @@
         }
     }
 
-    private class MatchDeliverableMessages extends MessageCompare {
+    private static final class MatchDeliverableMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -984,7 +984,7 @@
         return token;
     }
 
-    private class MatchBarrierToken extends MessageCompare {
+    private static final class MatchBarrierToken extends MessageCompare {
         int mBarrierToken;
 
         MatchBarrierToken(int token) {
@@ -1165,7 +1165,7 @@
         return foundInStack || foundInQueue;
     }
 
-    private static class MatchHandlerWhatAndObject extends MessageCompare {
+    private static final class MatchHandlerWhatAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1185,7 +1185,7 @@
         return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false);
     }
 
-    private static class MatchHandlerWhatAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerWhatAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1206,7 +1206,7 @@
                 false);
     }
 
-    private static class MatchHandlerRunnableAndObject extends MessageCompare {
+    private static final class MatchHandlerRunnableAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1227,7 +1227,7 @@
         return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false);
     }
 
-    private static class MatchHandler extends MessageCompare {
+    private static final class MatchHandler extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1266,7 +1266,7 @@
         findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
     }
 
-    private static class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1285,7 +1285,7 @@
         findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
     }
 
-    private static class MatchHandlerAndObject extends MessageCompare {
+    private static final class MatchHandlerAndObject extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1303,7 +1303,7 @@
         findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
     }
 
-    private static class MatchHandlerAndObjectEquals extends MessageCompare {
+    private static final class MatchHandlerAndObjectEquals extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1322,7 +1322,7 @@
         findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
     }
 
-    private static class MatchAllMessages extends MessageCompare {
+    private static final class MatchAllMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
@@ -1334,7 +1334,7 @@
         findOrRemoveMessages(null, -1, null, null, 0, mMatchAllMessages, true);
     }
 
-    private static class MatchAllFutureMessages extends MessageCompare {
+    private static final class MatchAllFutureMessages extends MessageCompare {
         @Override
         public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
                 long when) {
diff --git a/core/java/android/os/IpcDataCache.java b/core/java/android/os/IpcDataCache.java
index 7875c23..8db1567 100644
--- a/core/java/android/os/IpcDataCache.java
+++ b/core/java/android/os/IpcDataCache.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.app.PropertyInvalidatedCache;
+import android.app.PropertyInvalidatedCache.Args;
 import android.text.TextUtils;
 import android.util.ArraySet;
 
@@ -341,7 +342,7 @@
     public IpcDataCache(int maxEntries, @NonNull @IpcDataCacheModule String module,
             @NonNull String api, @NonNull String cacheName,
             @NonNull QueryHandler<Query, Result> computer) {
-        super(maxEntries, module, api, cacheName, computer);
+        super(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
     }
 
     /**
@@ -563,7 +564,8 @@
      * @hide
      */
     public IpcDataCache(@NonNull Config config, @NonNull QueryHandler<Query, Result> computer) {
-        super(config.maxEntries(), config.module(), config.api(), config.name(), computer);
+      super(new Args(config.module()).maxEntries(config.maxEntries()).api(config.api()),
+          config.name(), computer);
     }
 
     /**
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 32db3be..9d4ac29 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -517,9 +517,15 @@
     public static final int GO_TO_SLEEP_REASON_DEVICE_FOLD = 13;
 
     /**
+     * Go to sleep reason code: reason unknown.
      * @hide
      */
-    public static final int GO_TO_SLEEP_REASON_MAX =  GO_TO_SLEEP_REASON_DEVICE_FOLD;
+    public static final int GO_TO_SLEEP_REASON_UNKNOWN = 14;
+
+    /**
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_MAX =  GO_TO_SLEEP_REASON_UNKNOWN;
 
     /**
      * @hide
@@ -540,6 +546,7 @@
             case GO_TO_SLEEP_REASON_QUIESCENT: return "quiescent";
             case GO_TO_SLEEP_REASON_SLEEP_BUTTON: return "sleep_button";
             case GO_TO_SLEEP_REASON_TIMEOUT: return "timeout";
+            case GO_TO_SLEEP_REASON_UNKNOWN: return "unknown";
             default: return Integer.toString(sleepReason);
         }
     }
@@ -635,6 +642,7 @@
             GO_TO_SLEEP_REASON_QUIESCENT,
             GO_TO_SLEEP_REASON_SLEEP_BUTTON,
             GO_TO_SLEEP_REASON_TIMEOUT,
+            GO_TO_SLEEP_REASON_UNKNOWN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface GoToSleepReason{}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index b5ecc14..90993e1 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -33,6 +33,7 @@
 import android.app.ActivityTaskManager;
 import android.app.ActivityThread;
 import android.app.IActivityManager;
+import android.app.IActivityTaskManager;
 import android.app.IBackgroundActivityLaunchCallback;
 import android.app.IUnsafeIntentStrictModeCallback;
 import android.app.PendingIntent;
@@ -2189,8 +2190,11 @@
 
     private static void registerBackgroundActivityLaunchCallback() {
         try {
-            ActivityTaskManager.getService().registerBackgroundActivityStartCallback(
+            IActivityTaskManager service = ActivityTaskManager.getService();
+            if (service != null) {
+                service.registerBackgroundActivityStartCallback(
                     new BackgroundActivityLaunchCallback());
+            }
         } catch (DeadObjectException e) {
             // ignore
         } catch (RemoteException e) {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index fa99f35..4bc8fe0 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -5502,10 +5502,14 @@
             Manifest.permission.CREATE_USERS,
             Manifest.permission.QUERY_USERS}, conditional = true)
     public @NonNull int[] getProfileIds(@UserIdInt int userId, boolean enabledOnly) {
-        try {
-            return mService.getProfileIds(userId, enabledOnly);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
+        if (android.multiuser.Flags.cacheProfileIdsReadOnly()) {
+            return enabledOnly ? getEnabledProfileIds(userId) : getProfileIdsWithDisabled(userId);
+        } else {
+            try {
+                return mService.getProfileIds(userId, enabledOnly);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -5518,8 +5522,14 @@
             Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS,
             Manifest.permission.QUERY_USERS}, conditional = true)
+    @CachedProperty(api = "user_manager_users")
     public int[] getProfileIdsWithDisabled(@UserIdInt int userId) {
-        return getProfileIds(userId, false /* enabledOnly */);
+        if (android.multiuser.Flags.cacheProfileIdsReadOnly()) {
+            return UserManagerCache.getProfileIdsWithDisabled(
+                (Integer userIdentifuer) -> mService.getProfileIds(userIdentifuer, false), userId);
+        } else {
+            return getProfileIds(userId, false /* enabledOnly */);
+        }
     }
 
     /**
@@ -5530,8 +5540,21 @@
             Manifest.permission.MANAGE_USERS,
             Manifest.permission.CREATE_USERS,
             Manifest.permission.QUERY_USERS}, conditional = true)
+    @CachedProperty(api = "user_manager_users_enabled")
     public int[] getEnabledProfileIds(@UserIdInt int userId) {
-        return getProfileIds(userId, true /* enabledOnly */);
+        if (android.multiuser.Flags.cacheProfileIdsReadOnly()) {
+            return UserManagerCache.getEnabledProfileIds(
+                (Integer userIdentifuer) -> mService.getProfileIds(userIdentifuer, true), userId);
+        } else {
+            return getProfileIds(userId, true /* enabledOnly */);
+        }
+    }
+
+    /** @hide */
+    public static final void invalidateEnabledProfileIds() {
+        if (android.multiuser.Flags.cacheProfileIdsReadOnly()) {
+            UserManagerCache.invalidateEnabledProfileIds();
+        }
     }
 
     /**
@@ -6443,6 +6466,21 @@
         if (android.multiuser.Flags.cacheProfileParentReadOnly()) {
             UserManagerCache.invalidateProfileParent();
         }
+        invalidateEnabledProfileIds();
+    }
+
+    /**
+     * Invalidate caches when related to specific user info flags change.
+     *
+     * @param flag a combination of FLAG_ constants, from the list in
+     *        {@link UserInfo#UserInfoFlag}, whose value has changed and the associated
+     *        invalidations must therefore be performed.
+     * @hide
+     */
+    public static final void invalidateOnUserInfoFlagChange(@UserInfoFlag int flags) {
+        if ((flags & UserInfo.FLAG_DISABLED) > 0) {
+            invalidateEnabledProfileIds();
+        }
     }
 
     /**
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index a3844e2..0cffd9f 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -1740,7 +1740,9 @@
      *
      * <p>The waveform envelope builder offers more flexibility for creating waveform effects,
      * allowing control over vibration amplitude and frequency via smooth transitions between
-     * values.
+     * values. The waveform will start the first transition from the vibrator off state, using
+     * the same frequency of the first control point. To provide a different initial vibration
+     * frequency, use {@link #startWaveformEnvelope(float)}.
      *
      * <p>Note: To check whether waveform envelope effects are supported, use
      * {@link Vibrator#areEnvelopeEffectsSupported()}.
@@ -1754,6 +1756,32 @@
     }
 
     /**
+     * Start building a waveform vibration with an initial frequency.
+     *
+     * <p>The waveform envelope builder offers more flexibility for creating waveform effects,
+     * allowing control over vibration amplitude and frequency via smooth transitions between
+     * values.
+     *
+     * <p>This is the same as {@link #startWaveformEnvelope()}, but the waveform will start
+     * vibrating at given frequency, in hertz, while it transitions to the new amplitude and
+     * frequency of the first control point.
+     *
+     * <p>Note: To check whether waveform envelope effects are supported, use
+     * {@link Vibrator#areEnvelopeEffectsSupported()}.
+     *
+     * @param initialFrequencyHz The starting frequency of the vibration, in hertz. Must be greater
+     *                           than zero.
+     *
+     * @see VibrationEffect.WaveformEnvelopeBuilder
+     */
+    @FlaggedApi(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    @NonNull
+    public static VibrationEffect.WaveformEnvelopeBuilder startWaveformEnvelope(
+            @FloatRange(from = 0) float initialFrequencyHz) {
+        return new WaveformEnvelopeBuilder(initialFrequencyHz);
+    }
+
+    /**
      * A builder for waveform effects described by its envelope.
      *
      * <p>Waveform effect envelopes are defined by one or more control points describing a target
@@ -1810,6 +1838,10 @@
 
         private WaveformEnvelopeBuilder() {}
 
+        private WaveformEnvelopeBuilder(float initialFrequency) {
+            mLastFrequencyHz = initialFrequency;
+        }
+
         /**
          * Adds a new control point to the end of this waveform envelope.
          *
@@ -1841,7 +1873,7 @@
                 @FloatRange(from = 0, to = 1) float amplitude,
                 @FloatRange(from = 0) float frequencyHz, int timeMillis) {
 
-            if (mSegments.isEmpty()) {
+            if (mLastFrequencyHz == 0) {
                 mLastFrequencyHz = frequencyHz;
             }
 
diff --git a/core/java/android/os/vibrator/PwlePoint.java b/core/java/android/os/vibrator/PwlePoint.java
new file mode 100644
index 0000000..ea3ae6c
--- /dev/null
+++ b/core/java/android/os/vibrator/PwlePoint.java
@@ -0,0 +1,66 @@
+/*
+ * 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 java.util.Objects;
+
+/**
+ * A {@link PwlePoint} represents a single point in an envelope vibration effect. Defined by its
+ * amplitude, frequency and time to transition to this point from the previous one in the envelope.
+ *
+ * @hide
+ */
+public final class PwlePoint {
+    private final float mAmplitude;
+    private final float mFrequencyHz;
+    private final int mTimeMillis;
+
+    /** @hide */
+    public PwlePoint(float amplitude, float frequencyHz, int timeMillis) {
+        mAmplitude = amplitude;
+        mFrequencyHz = frequencyHz;
+        mTimeMillis = timeMillis;
+    }
+
+    public float getAmplitude() {
+        return mAmplitude;
+    }
+
+    public float getFrequencyHz() {
+        return mFrequencyHz;
+    }
+
+    public int getTimeMillis() {
+        return mTimeMillis;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof PwlePoint)) {
+            return false;
+        }
+        PwlePoint other = (PwlePoint) obj;
+        return Float.compare(mAmplitude, other.mAmplitude) == 0
+                && Float.compare(mFrequencyHz, other.mFrequencyHz) == 0
+                && mTimeMillis == other.mTimeMillis;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAmplitude, mFrequencyHz, mTimeMillis);
+    }
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 7944be1..6a49322 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -314,3 +314,21 @@
     description: "Enable AppOp mode caching in AppOpsManager"
     bug: "366013082"
 }
+
+flag {
+    name: "permission_tree_apis_deprecated"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "This flag is used to deprecate permission tree related APIs"
+    bug: "376535612"
+}
+
+flag {
+    name: "enable_otp_in_text_classifiers"
+    is_fixed_read_only: true
+    is_exported: true
+    namespace: "permissions"
+    description: "Enables ExtServices to leverage TextClassifier for OTP detection"
+    bug: "351976749"
+}
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 8afc177..1b289fd 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -9383,7 +9383,14 @@
          * @param resolver the ContentResolver to query.
          * @return the default account for new contacts, or null if it's not set or set to NULL
          * account.
+         *
+         * @deprecated This API is only supported up to Android version
+         *      * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. On later versions,
+         * {@link ContactsContract.RawContacts.DefaultAccount#getDefaultAccountForNewContacts}
+         * should be used.
          */
+        @Deprecated
+        @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
         @Nullable
         public static Account getDefaultAccount(@NonNull ContentResolver resolver) {
             Bundle response = resolver.call(ContactsContract.AUTHORITY_URI,
@@ -9404,7 +9411,14 @@
          * @param resolver the ContentResolver to query.
          * @param account the account to be set to default.
          * @hide
+         *
+         * @deprecated This API is only supported up to Android version
+         *      * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. On later versions,
+         * {@link ContactsContract.RawContacts.DefaultAccount#setDefaultAccountForNewContacts}
+         * should be used.
          */
+        @Deprecated
+        @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
         @SystemApi
         @RequiresPermission(android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS)
         public static void setDefaultAccount(@NonNull ContentResolver resolver,
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionFeature.aidl b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.aidl
new file mode 100644
index 0000000..3ecef02
--- /dev/null
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.aidl
@@ -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 android.security.advancedprotection;
+
+/**
+ * Represents an advanced protection feature providing protections
+ * @hide
+ */
+parcelable AdvancedProtectionFeature;
\ No newline at end of file
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java
new file mode 100644
index 0000000..a086bf7
--- /dev/null
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionFeature.java
@@ -0,0 +1,77 @@
+/*
+ * 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.security.advancedprotection;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.security.Flags;
+
+/**
+ * An advanced protection feature providing protections.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_AAPM_API)
+@SystemApi
+public final class AdvancedProtectionFeature implements Parcelable {
+    private final String mId;
+
+    /**
+     * Create an object identifying an Advanced Protection feature for AdvancedProtectionManager
+     * @param id A unique ID to identify this feature. It is used by Settings screens to display
+     *           information about this feature.
+     */
+    public AdvancedProtectionFeature(@NonNull String id) {
+        mId = id;
+    }
+
+    private AdvancedProtectionFeature(Parcel in) {
+        mId = in.readString8();
+    }
+
+    /**
+     * @return the unique ID representing this feature
+     */
+    @NonNull
+    public String getId() {
+        return mId;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mId);
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<AdvancedProtectionFeature> CREATOR =
+            new Parcelable.Creator<>() {
+                public AdvancedProtectionFeature createFromParcel(Parcel in) {
+                    return new AdvancedProtectionFeature(in);
+                }
+
+                public AdvancedProtectionFeature[] newArray(int size) {
+                    return new AdvancedProtectionFeature[size];
+                }
+            };
+}
diff --git a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
index 43b6ebe..6f3e3d8 100644
--- a/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
+++ b/core/java/android/security/advancedprotection/AdvancedProtectionManager.java
@@ -29,6 +29,7 @@
 import android.security.Flags;
 import android.util.Log;
 
+import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
@@ -41,7 +42,7 @@
  */
 @FlaggedApi(Flags.FLAG_AAPM_API)
 @SystemService(Context.ADVANCED_PROTECTION_SERVICE)
-public class AdvancedProtectionManager {
+public final class AdvancedProtectionManager {
     private static final String TAG = "AdvancedProtectionMgr";
 
     private final ConcurrentHashMap<Callback, IAdvancedProtectionCallback>
@@ -147,6 +148,22 @@
     }
 
     /**
+     * Returns the list of advanced protection features which are available on this device.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    @RequiresPermission(Manifest.permission.SET_ADVANCED_PROTECTION_MODE)
+    public List<AdvancedProtectionFeature> getAdvancedProtectionFeatures() {
+        try {
+            return mService.getAdvancedProtectionFeatures();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * A callback class for monitoring changes to Advanced Protection state
      *
      * <p>To register a callback, implement this interface, and register it with
diff --git a/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl b/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl
index ef0abf4..6830763 100644
--- a/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl
+++ b/core/java/android/security/advancedprotection/IAdvancedProtectionService.aidl
@@ -16,6 +16,7 @@
 
 package android.security.advancedprotection;
 
+import android.security.advancedprotection.AdvancedProtectionFeature;
 import android.security.advancedprotection.IAdvancedProtectionCallback;
 
 /**
@@ -32,4 +33,6 @@
     void unregisterAdvancedProtectionCallback(IAdvancedProtectionCallback callback);
     @EnforcePermission("SET_ADVANCED_PROTECTION_MODE")
     void setAdvancedProtectionEnabled(boolean enabled);
+    @EnforcePermission("SET_ADVANCED_PROTECTION_MODE")
+    List<AdvancedProtectionFeature> getAdvancedProtectionFeatures();
 }
\ No newline at end of file
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index dec28c3..5995760 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -87,6 +87,14 @@
 }
 
 flag {
+    name: "prevent_intent_redirect_show_toast"
+    namespace: "responsible_apis"
+    description: "Prevent intent redirect attacks by showing a toast when activity start is blocked"
+    bug: "361143368"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "enable_intent_matching_flags"
     is_exported: true
     namespace: "permissions"
diff --git a/core/java/android/service/contextualsearch/OWNERS b/core/java/android/service/contextualsearch/OWNERS
index b723872..c435bd8 100644
--- a/core/java/android/service/contextualsearch/OWNERS
+++ b/core/java/android/service/contextualsearch/OWNERS
@@ -1,2 +1,3 @@
 srazdan@google.com
-hackz@google.com
+hyunyoungs@google.com
+awickham@google.com
diff --git a/core/java/android/service/games/GameSession.java b/core/java/android/service/games/GameSession.java
index e8d53d3..531e0b1 100644
--- a/core/java/android/service/games/GameSession.java
+++ b/core/java/android/service/games/GameSession.java
@@ -516,6 +516,8 @@
                         options,
                         future);
 
+        trampolineIntent.collectExtraIntentKeys();
+
         try {
             int result = ActivityTaskManager.getService().startActivityFromGameSession(
                     mContext.getIApplicationThread(), mContext.getPackageName(), "GameSession",
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index f1ae22e..e9e2646 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -22,6 +22,7 @@
 import android.graphics.RectF;
 import android.view.MotionEvent;
 import android.view.SurfaceControl;
+import android.app.wallpaper.WallpaperDescription;
 import android.os.Bundle;
 
 /**
@@ -50,4 +51,5 @@
     SurfaceControl mirrorSurfaceControl();
     oneway void applyDimming(float dimAmount);
     oneway void setWallpaperFlags(int which);
+    @nullable WallpaperDescription onApplyWallpaper(int which);
 }
diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl
index 3262f3a..f76e6ce 100644
--- a/core/java/android/service/wallpaper/IWallpaperService.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperService.aidl
@@ -17,6 +17,7 @@
 package android.service.wallpaper;
 
 import android.app.WallpaperInfo;
+import android.app.wallpaper.WallpaperDescription;
 import android.graphics.Rect;
 import android.service.wallpaper.IWallpaperConnection;
 
@@ -27,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 WallpaperInfo info, in @nullable 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 2ab16e9..131fdc8 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -16,6 +16,7 @@
 
 package android.service.wallpaper;
 
+import static android.app.Flags.FLAG_LIVE_WALLPAPER_CONTENT_HANDLING;
 import static android.app.WallpaperManager.COMMAND_FREEZE;
 import static android.app.WallpaperManager.COMMAND_UNFREEZE;
 import static android.app.WallpaperManager.SetWallpaperFlags;
@@ -50,6 +51,7 @@
 import android.app.WallpaperInfo;
 import android.app.WallpaperManager;
 import android.app.compat.CompatChanges;
+import android.app.wallpaper.WallpaperDescription;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.EnabledSince;
@@ -221,14 +223,14 @@
 
     /**
      * Wear products currently force a slight scaling transition to wallpapers
-     * when the QSS is opened. However, on Wear 6 (SDK 35) and above, 1P watch faces
+     * when the QSS is opened. However, on Wear 7 (SDK 37) and above, 1P watch faces
      * will be expected to either implement their own scaling, or to override this
      * method to allow the WallpaperController to continue to scale for them.
      *
      * @hide
      */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
     public static final long WEAROS_WALLPAPER_HANDLES_SCALING = 272527315L;
 
     static final class WallpaperCommand {
@@ -922,6 +924,24 @@
         }
 
         /**
+         * Called when the wallpaper preview rendered by this engine is about to be persisted as
+         * a selected wallpaper. The returned WallpaperDescription (if any) will be persisted by
+         * the system and passed into subsequent calls to
+         * {@link WallpaperService#onCreateEngine(WallpaperDescription)}. This allows the Engine
+         * to perform any necessary bookkeeping before a wallpaper being previewed is set on
+         * the device, and update the description if necessary.
+         *
+         * @param which Specifies wallpaper destination: home, lock, or both
+         * @return the description of the applied wallpaper, or {@code null} if description is
+         * unchanged
+         */
+        @Nullable
+        @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+        public WallpaperDescription onApplyWallpaper(@SetWallpaperFlags int which) {
+            return null;
+        }
+
+        /**
          * Notifies the engine that wallpaper colors changed significantly.
          * This will trigger a {@link #onComputeColors()} call.
          */
@@ -2449,6 +2469,7 @@
         final Display mDisplay;
         final WallpaperManager mWallpaperManager;
         @Nullable final WallpaperInfo mInfo;
+        @NonNull final WallpaperDescription mDescription;
 
         Engine mEngine;
         @SetWallpaperFlags int mWhich;
@@ -2456,7 +2477,8 @@
         IWallpaperEngineWrapper(WallpaperService service,
                 IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
-                int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info) {
+                int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info,
+                @NonNull WallpaperDescription description) {
             mWallpaperManager = getSystemService(WallpaperManager.class);
             mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true);
             mConnection = conn;
@@ -2469,6 +2491,7 @@
             mDisplayId = displayId;
             mWhich = which;
             mInfo = info;
+            mDescription = description;
 
             // Create a display context before onCreateEngine.
             mDisplayManager = getSystemService(DisplayManager.class);
@@ -2593,9 +2616,19 @@
             return mEngine == null ? null : SurfaceControl.mirrorSurface(mEngine.mSurfaceControl);
         }
 
+        @Nullable
+        public WallpaperDescription onApplyWallpaper(@SetWallpaperFlags int which) {
+            return mEngine != null ? mEngine.onApplyWallpaper(which) : null;
+        }
+
         private void doAttachEngine() {
             Trace.beginSection("WPMS.onCreateEngine");
-            Engine engine = onCreateEngine();
+            Engine engine;
+            if (mDescription != null) {
+                engine = onCreateEngine(mDescription);
+            } else {
+                engine = onCreateEngine();
+            }
             Trace.endSection();
             mEngine = engine;
             Trace.beginSection("WPMS.mConnection.attachEngine-" + mDisplayId);
@@ -2804,11 +2837,13 @@
         @Override
         public void attach(IWallpaperConnection conn, IBinder windowToken,
                 int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding,
-                int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info) {
+                int displayId, @SetWallpaperFlags int which, WallpaperInfo info,
+                @NonNull WallpaperDescription description) {
             Trace.beginSection("WPMS.ServiceWrapper.attach");
             IWallpaperEngineWrapper engineWrapper =
                     new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType,
-                            isPreview, reqWidth, reqHeight, padding, displayId, which, info);
+                            isPreview, reqWidth, reqHeight, padding, displayId, which, info,
+                            description);
             synchronized (mActiveEngines) {
                 mActiveEngines.put(windowToken, engineWrapper);
             }
@@ -2883,6 +2918,19 @@
     @MainThread
     public abstract Engine onCreateEngine();
 
+    /**
+     * Creates a new engine instance to show the given content. See also {@link #onCreateEngine()}.
+     *
+     * @param description content to display
+     * @return the rendering engine
+     */
+    @FlaggedApi(FLAG_LIVE_WALLPAPER_CONTENT_HANDLING)
+    @MainThread
+    @Nullable
+    public Engine onCreateEngine(@NonNull WallpaperDescription description) {
+        return onCreateEngine();
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter out, String[] args) {
         out.print("State of wallpaper "); out.print(this); out.println(":");
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 46e27dc..64a5533 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -1715,11 +1715,10 @@
          * @see TelephonyManager#EMERGENCY_CALLBACK_MODE_SMS
          *
          * @param timerDuration is the time remaining in the emergency callback mode.
-         * @param subId The subscription ID used to start the emergency callback mode.
+         * @param subscriptionId The subscription ID used to start the emergency callback mode.
          */
-        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         void onCallbackModeStarted(@TelephonyManager.EmergencyCallbackModeType int type,
-                @NonNull Duration timerDuration, int subId);
+                @NonNull Duration timerDuration, int subscriptionId);
 
         /**
          * Indicates that emergency callback mode has been re-started.
@@ -1734,11 +1733,10 @@
          * @see TelephonyManager#EMERGENCY_CALLBACK_MODE_SMS
          *
          * @param timerDuration is the time remaining in the emergency callback mode.
-         * @param subId The subscription ID used to restart the emergency callback mode.
+         * @param subscriptionId The subscription ID used to restart the emergency callback mode.
          */
-        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         void onCallbackModeRestarted(@TelephonyManager.EmergencyCallbackModeType int type,
-                @NonNull Duration timerDuration, int subId);
+                @NonNull Duration timerDuration, int subscriptionId);
 
         /**
          * Indicates that emergency callback mode has been stopped.
@@ -1759,11 +1757,10 @@
          * @see TelephonyManager#STOP_REASON_TIMER_EXPIRED
          * @see TelephonyManager#STOP_REASON_USER_ACTION
          *
-         * @param subId is the current subscription used the emergency callback mode.
+         * @param subscriptionId is the current subscription used the emergency callback mode.
          */
-        @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         void onCallbackModeStopped(@TelephonyManager.EmergencyCallbackModeType int type,
-                @TelephonyManager.EmergencyCallbackModeStopReason int reason, int subId);
+                @TelephonyManager.EmergencyCallbackModeStopReason int reason, int subscriptionId);
     }
 
     /**
@@ -2192,7 +2189,7 @@
 
         @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         public void onCallbackModeStarted(@TelephonyManager.EmergencyCallbackModeType int type,
-                long durationMillis, int subId) {
+                long durationMillis, int subscriptionId) {
             if (!Flags.emergencyCallbackModeNotification()) return;
 
             EmergencyCallbackModeListener listener =
@@ -2203,12 +2200,12 @@
             final Duration timerDuration = Duration.ofMillis(durationMillis);
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> listener.onCallbackModeStarted(type,
-                            timerDuration, subId)));
+                            timerDuration, subscriptionId)));
         }
 
         @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         public void onCallbackModeRestarted(@TelephonyManager.EmergencyCallbackModeType int type,
-                long durationMillis, int subId) {
+                long durationMillis, int subscriptionId) {
             if (!Flags.emergencyCallbackModeNotification()) return;
 
             EmergencyCallbackModeListener listener =
@@ -2219,12 +2216,12 @@
             final Duration timerDuration = Duration.ofMillis(durationMillis);
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> listener.onCallbackModeRestarted(type,
-                            timerDuration, subId)));
+                            timerDuration, subscriptionId)));
         }
 
         @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
         public void onCallbackModeStopped(@TelephonyManager.EmergencyCallbackModeType int type,
-                @TelephonyManager.EmergencyCallbackModeStopReason int reason, int subId) {
+                @TelephonyManager.EmergencyCallbackModeStopReason int reason, int subscriptionId) {
             if (!Flags.emergencyCallbackModeNotification()) return;
 
             EmergencyCallbackModeListener listener =
@@ -2235,7 +2232,7 @@
 
             Binder.withCleanCallingIdentity(
                     () -> mExecutor.execute(() -> listener.onCallbackModeStopped(type, reason,
-                            subId)));
+                            subscriptionId)));
         }
 
         public void onCarrierRoamingNtnModeChanged(boolean active) {
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 4d50a45..1dab2cf 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -47,6 +47,8 @@
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.SatelliteStateChangeListener;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -55,12 +57,14 @@
 import com.android.internal.telephony.ICarrierConfigChangeListener;
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.server.telecom.flags.Flags;
 
 import java.lang.ref.WeakReference;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.WeakHashMap;
@@ -1482,6 +1486,111 @@
                 pkgName, attributionTag, callback, new int[0], notifyNow);
     }
 
+    @NonNull
+    @GuardedBy("sSatelliteStateChangeListeners")
+    private static final Map<SatelliteStateChangeListener,
+                WeakReference<SatelliteStateChangeListenerWrapper>>
+            sSatelliteStateChangeListeners = new ArrayMap<>();
+
+    /**
+     * Register a {@link SatelliteStateChangeListener} to receive notification when Satellite state
+     * has changed.
+     *
+     * @param executor The {@link Executor} where the {@code listener} will be invoked
+     * @param listener The listener to monitor the satellite state change
+     * @hide
+     */
+    public void addSatelliteStateChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull SatelliteStateChangeListener listener) {
+        if (listener == null || executor == null) {
+            throw new IllegalArgumentException("Listener and executor must be non-null");
+        }
+
+        synchronized (sSatelliteStateChangeListeners) {
+            WeakReference<SatelliteStateChangeListenerWrapper> existing =
+                    sSatelliteStateChangeListeners.get(listener);
+            if (existing != null && existing.get() != null) {
+                Log.d(TAG, "addSatelliteStateChangeListener: listener already registered");
+                return;
+            }
+            SatelliteStateChangeListenerWrapper wrapper =
+                    new SatelliteStateChangeListenerWrapper(executor, listener);
+            try {
+                sRegistry.addSatelliteStateChangeListener(
+                        wrapper,
+                        mContext.getOpPackageName(),
+                        mContext.getAttributionTag());
+                sSatelliteStateChangeListeners.put(listener, new WeakReference<>(wrapper));
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregister a {@link SatelliteStateChangeListener} to stop receiving notification when
+     * satellite state has changed.
+     *
+     * @param listener The listener previously registered with addSatelliteStateChangeListener.
+     * @hide
+     */
+    public void removeSatelliteStateChangeListener(@NonNull SatelliteStateChangeListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must be non-null");
+        }
+
+        synchronized (sSatelliteStateChangeListeners) {
+            WeakReference<SatelliteStateChangeListenerWrapper> ref =
+                    sSatelliteStateChangeListeners.get(listener);
+            if (ref == null) return;
+            SatelliteStateChangeListenerWrapper wrapper = ref.get();
+            if (wrapper == null) return;
+            try {
+                sRegistry.removeSatelliteStateChangeListener(wrapper, mContext.getOpPackageName());
+                sSatelliteStateChangeListeners.remove(listener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Notify the registrants that the satellite state has changed.
+     *
+     * @param isEnabled True if the satellite modem is enabled, false otherwise
+     * @hide
+     */
+    public void notifySatelliteStateChanged(boolean isEnabled) {
+        try {
+            sRegistry.notifySatelliteStateChanged(isEnabled);
+        } catch (RemoteException ex) {
+            // system process is dead
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    private static class SatelliteStateChangeListenerWrapper extends
+            ISatelliteStateChangeListener.Stub implements ListenerExecutor {
+        @NonNull private final WeakReference<SatelliteStateChangeListener> mListener;
+        @NonNull private final Executor mExecutor;
+
+        SatelliteStateChangeListenerWrapper(@NonNull Executor executor,
+                @NonNull SatelliteStateChangeListener listener) {
+            mExecutor = executor;
+            mListener = new WeakReference<>(listener);
+        }
+
+        @Override
+        public void onSatelliteEnabledStateChanged(boolean isEnabled) {
+            Binder.withCleanCallingIdentity(
+                    () ->
+                            executeSafely(
+                                    mExecutor,
+                                    mListener::get,
+                                    sscl -> sscl.onEnabledStateChanged(isEnabled)));
+        }
+    }
+
     private static class CarrierPrivilegesCallbackWrapper extends ICarrierPrivilegesCallback.Stub
             implements ListenerExecutor {
         @NonNull private final WeakReference<CarrierPrivilegesCallback> mCallback;
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 7877352..acbd95bf 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -212,8 +212,7 @@
                 && mInitiallyVisible == that.mInitiallyVisible
                 && mSurfacePosition.equals(that.mSurfacePosition)
                 && mInsetsHint.equals(that.mInsetsHint)
-                && mSkipAnimationOnce == that.mSkipAnimationOnce
-                && Objects.equals(mImeStatsToken, that.mImeStatsToken);
+                && mSkipAnimationOnce == that.mSkipAnimationOnce;
     }
 
     @Override
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 0d55544..b8b22e2 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -87,6 +87,7 @@
 import java.nio.ByteOrder;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -463,7 +464,7 @@
         /**
          * Called when new jank classifications are available.
          */
-        void onJankDataAvailable(JankData[] jankData);
+        void onJankDataAvailable(@NonNull List<JankData> jankData);
 
     }
 
@@ -2686,7 +2687,9 @@
      * Adds a callback to be informed about SF's jank classification for this surface.
      * @hide
      */
-    public OnJankDataListenerRegistration addJankDataListener(OnJankDataListener listener) {
+    @NonNull
+    public OnJankDataListenerRegistration addOnJankDataListener(
+            @NonNull OnJankDataListener listener) {
         return new OnJankDataListenerRegistration(this, listener);
     }
 
@@ -3459,15 +3462,15 @@
          * @return this This transaction for chaining
          * @hide
          */
-        public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, float top, float left,
-                float bottom, float right) {
+        public @NonNull Transaction setCrop(@NonNull SurfaceControl sc, float left, float top,
+                float right, float bottom) {
             checkPreconditions(sc);
             if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
                 SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
                         "setCrop", this, sc, "crop={" + top + ", " + left + ", " +
                         bottom + ", " + right + "}");
             }
-            nativeSetCrop(mNativeObject, sc.mNativeObject, top, left, bottom, right);
+            nativeSetCrop(mNativeObject, sc.mNativeObject, left, top, right, bottom);
             return this;
         }
 
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 9cad3e5..4df7649 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -40,6 +40,7 @@
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Region;
 import android.graphics.RenderNode;
 import android.hardware.input.InputManager;
@@ -1666,7 +1667,7 @@
     }
 
     private final Rect mRTLastReportedPosition = new Rect();
-    private final Rect mRTLastSetCrop = new Rect();
+    private final RectF mRTLastSetCrop = new RectF();
 
     private class SurfaceViewPositionUpdateListener implements RenderNode.PositionUpdateListener {
         private final int mRtSurfaceWidth;
@@ -1711,16 +1712,18 @@
 
         @Override
         public void positionChanged(long frameNumber, int left, int top, int right, int bottom,
-                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+                int clipLeft, int clipTop, int clipRight, int clipBottom,
+                int nodeWidth, int nodeHeight) {
             try {
                 if (DEBUG_POSITION) {
                     Log.d(TAG, String.format(
                             "%d updateSurfacePosition RenderWorker, frameNr = %d, "
                                     + "position = [%d, %d, %d, %d] clip = [%d, %d, %d, %d] "
-                                    + "surfaceSize = %dx%d",
+                                    + "surfaceSize = %dx%d renderNodeSize = %d%d",
                             System.identityHashCode(SurfaceView.this), frameNumber,
                             left, top, right, bottom, clipLeft, clipTop, clipRight, clipBottom,
-                            mRtSurfaceWidth, mRtSurfaceHeight));
+                            mRtSurfaceWidth, mRtSurfaceHeight,
+                            nodeWidth, nodeHeight));
                 }
                 synchronized (mSurfaceControlLock) {
                     if (mSurfaceControl == null) return;
@@ -1735,14 +1738,29 @@
                             mRTLastReportedPosition.top /*positionTop*/,
                             postScaleX, postScaleY);
 
-                    mRTLastSetCrop.set(clipLeft, clipTop, clipRight, clipBottom);
+                    // The computed crop is in view-relative dimensions, however we need it to be
+                    // in buffer-relative dimensions. So scale the crop by the ratio between
+                    // the view's unscaled width/height (nodeWidth/Height), and the surface's
+                    // width/height
+                    // That is, if the Surface has a fixed size of 50x50, the SurfaceView is laid
+                    // out to a size of 100x100, and the SurfaceView is ultimately scaled to
+                    // 1000x1000, then we need to scale the crop by just the 2x from surface
+                    // domain to SV domain.
+                    final float surfaceToNodeScaleX = (float) mRtSurfaceWidth / (float) nodeWidth;
+                    final float surfaceToNodeScaleY = (float) mRtSurfaceHeight / (float) nodeHeight;
+                    mRTLastSetCrop.set(clipLeft * surfaceToNodeScaleX,
+                            clipTop * surfaceToNodeScaleY,
+                            clipRight * surfaceToNodeScaleX,
+                            clipBottom * surfaceToNodeScaleY);
+
                     if (DEBUG_POSITION) {
-                        Log.d(TAG, String.format("Setting layer crop = [%d, %d, %d, %d] "
+                        Log.d(TAG, String.format("Setting layer crop = [%f, %f, %f, %f] "
                                         + "from scale %f, %f", mRTLastSetCrop.left,
                                 mRTLastSetCrop.top, mRTLastSetCrop.right, mRTLastSetCrop.bottom,
-                                postScaleX, postScaleY));
+                                surfaceToNodeScaleX, surfaceToNodeScaleY));
                     }
-                    mPositionChangedTransaction.setCrop(mSurfaceControl, mRTLastSetCrop);
+                    mPositionChangedTransaction.setCrop(mSurfaceControl, mRTLastSetCrop.left,
+                            mRTLastSetCrop.top, mRTLastSetCrop.right, mRTLastSetCrop.bottom);
                     if (mRTLastSetCrop.isEmpty()) {
                         mPositionChangedTransaction.hide(mSurfaceControl);
                     } else {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d5fc262..e49eec6 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30,7 +30,9 @@
 import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+import static android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION;
 import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration;
+import static android.view.accessibility.Flags.supplementalDescription;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
@@ -40,7 +42,6 @@
 import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
-import static android.view.flags.Flags.calculateBoundsInParentFromBoundsInScreen;
 import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
 import static android.view.flags.Flags.sensitiveContentAppProtection;
 import static android.view.flags.Flags.toolkitFrameRateAnimationBugfix25q1;
@@ -51,6 +52,7 @@
 import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly;
 import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
+import static android.view.flags.Flags.toolkitViewgroupSetRequestedFrameRateApi;
 import static android.view.flags.Flags.viewVelocityApi;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
 import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
@@ -968,13 +970,6 @@
     private static boolean sAlwaysRemeasureExactly = false;
 
     /**
-     * When true calculates the bounds in parent from bounds in screen relative to its parents.
-     * This addresses the deprecated API (setBoundsInParent) in Compose, which causes empty
-     * getBoundsInParent call for Compose apps.
-     */
-    private static boolean sCalculateBoundsInParentFromBoundsInScreenFlagValue = false;
-
-    /**
      * When true makes it possible to use onMeasure caches also when the force layout flag is
      * enabled. This helps avoiding multiple measures in the same frame with the same dimensions.
      */
@@ -2475,6 +2470,8 @@
             toolkitFrameRateVelocityMappingReadOnly();
     private static boolean sToolkitFrameRateAnimationBugfix25q1FlagValue =
             toolkitFrameRateAnimationBugfix25q1();
+    private static boolean sToolkitViewGroupFrameRateApiFlagValue =
+            toolkitViewgroupSetRequestedFrameRateApi();
 
     // Used to set frame rate compatibility.
     @Surface.FrameRateCompatibility int mFrameRateCompatibility =
@@ -2564,8 +2561,6 @@
 
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
         sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
-        sCalculateBoundsInParentFromBoundsInScreenFlagValue =
-                calculateBoundsInParentFromBoundsInScreen();
         sUseMeasureCacheDuringForceLayoutFlagValue = enableUseMeasureCacheDuringForceLayout();
     }
 
@@ -3815,6 +3810,8 @@
      *     1                            PFLAG4_HAS_DRAWN
      *    1                             PFLAG4_HAS_MOVED
      *   1                              PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION
+     *  1                               PFLAG4_FORCED_OVERRIDE_FRAME_RATE
+     * 1                                PFLAG4_SELF_REQUESTED_FRAME_RATE
      * |-------|-------|-------|-------|
      */
 
@@ -3965,6 +3962,17 @@
      */
     private static final int PFLAG4_HAS_VIEW_PROPERTY_INVALIDATION = 0x20000000;
 
+    /**
+     * When set, this indicates whether the frame rate of the children should be
+     * forcibly overridden, even if it has been explicitly configured by a user request.
+     */
+    private static final int PFLAG4_FORCED_OVERRIDE_FRAME_RATE = 0x40000000;
+
+    /**
+     * When set, this indicates that the frame rate is configured based on a user request.
+     */
+    private static final int PFLAG4_SELF_REQUESTED_FRAME_RATE = 0x80000000;
+
     /* End of masks for mPrivateFlags4 */
 
     /** @hide */
@@ -4874,6 +4882,11 @@
     private CharSequence mContentDescription;
 
     /**
+     * Brief supplemental information for view and is primarily used for accessibility support.
+     */
+    private CharSequence mSupplementalDescription;
+
+    /**
      * If this view represents a distinct part of the window, it can have a title that labels the
      * area.
      */
@@ -6544,6 +6557,13 @@
                 case R.styleable.View_contentSensitivity:
                     setContentSensitivity(a.getInt(attr, CONTENT_SENSITIVITY_AUTO));
                     break;
+                default: {
+                    if (supplementalDescription()) {
+                        if (attr == com.android.internal.R.styleable.View_supplementalDescription) {
+                            setSupplementalDescription(a.getString(attr));
+                        }
+                    }
+                }
             }
         }
 
@@ -9786,7 +9806,7 @@
             structure.setChildCount(1);
             final ViewStructure root = structure.newChild(0);
             if (info != null) {
-                populateVirtualStructure(root, provider, info, null, forAutofill);
+                populateVirtualStructure(root, provider, info, forAutofill);
                 info.recycle();
             } else {
                 Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null.");
@@ -11085,19 +11105,11 @@
 
     private void populateVirtualStructure(ViewStructure structure,
             AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
-            @Nullable AccessibilityNodeInfo parentInfo, boolean forAutofill) {
+            boolean forAutofill) {
         structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
                 null, null, info.getViewIdResourceName());
         Rect rect = structure.getTempRect();
-        // The bounds in parent for Jetpack Compose views aren't set as setBoundsInParent is
-        // deprecated, and only setBoundsInScreen is called.
-        // The bounds in parent can be calculated by diff'ing the child view's bounds in screen with
-        // the parent's.
-        if (sCalculateBoundsInParentFromBoundsInScreenFlagValue) {
-            getBoundsInParent(info, parentInfo, rect);
-        } else {
-            info.getBoundsInParent(rect);
-        }
+        info.getBoundsInParent(rect);
         structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
         structure.setVisibility(VISIBLE);
         structure.setEnabled(info.isEnabled());
@@ -11181,32 +11193,13 @@
                         AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
                 if (cinfo != null) {
                     ViewStructure child = structure.newChild(i);
-                    populateVirtualStructure(child, provider, cinfo, info, forAutofill);
+                    populateVirtualStructure(child, provider, cinfo, forAutofill);
                     cinfo.recycle();
                 }
             }
         }
     }
 
-    private void getBoundsInParent(@NonNull AccessibilityNodeInfo info,
-            @Nullable AccessibilityNodeInfo parentInfo, @NonNull Rect rect) {
-        info.getBoundsInParent(rect);
-        // Fallback to calculate bounds in parent by diffing the bounds in
-        // screen if it's all 0.
-        if ((rect.left | rect.top | rect.right | rect.bottom) == 0) {
-            if (parentInfo != null) {
-                Rect parentBoundsInScreen = parentInfo.getBoundsInScreen();
-                Rect boundsInScreen = info.getBoundsInScreen();
-                rect.set(boundsInScreen.left - parentBoundsInScreen.left,
-                        boundsInScreen.top - parentBoundsInScreen.top,
-                        boundsInScreen.right - parentBoundsInScreen.left,
-                        boundsInScreen.bottom - parentBoundsInScreen.top);
-            } else {
-                info.getBoundsInScreen(rect);
-            }
-        }
-    }
-
     /**
      * Dispatch creation of {@link ViewStructure} down the hierarchy.  The default
      * implementation calls {@link #onProvideStructure} and
@@ -11843,6 +11836,39 @@
     }
 
     /**
+     * Returns the {@link View}'s supplemental description.
+     * <p>
+     * A supplemental description provides
+     * brief supplemental information for this node, such as the purpose of the node when
+     * that purpose is not conveyed within its textual representation. For example, if a
+     * dropdown select has a purpose of setting font family, the supplemental description
+     * could be "font family". If this node has children, its supplemental description serves
+     * as additional information and is not intended to replace any existing information
+     * in the subtree. This is different from the {@link #getContentDescription()} in that
+     * this description is purely supplemental while a content description may be used
+     * to replace a description for a node or its subtree that an assistive technology
+     * would otherwise compute based on other properties of the node and its descendants.
+     *
+     * <p>
+     * <strong>Note:</strong> Do not override this method, as it will have no
+     * effect on the supplemental description presented to accessibility services.
+     * You must call {@link #setSupplementalDescription(CharSequence)} to modify the
+     * supplemental description.
+     *
+     * @return the supplemental description
+     * @see #setSupplementalDescription(CharSequence)
+     * @see #getContentDescription()
+     * @attr ref android.R.styleable#View_supplementalDescription
+     */
+    @FlaggedApi(FLAG_SUPPLEMENTAL_DESCRIPTION)
+    @ViewDebug.ExportedProperty(category = "accessibility")
+    @InspectableProperty
+    @Nullable
+    public CharSequence getSupplementalDescription() {
+        return mSupplementalDescription;
+    }
+
+    /**
      * Sets the {@link View}'s state description.
      * <p>
      * A state description briefly describes the states of the view and is primarily used
@@ -11929,6 +11955,53 @@
     }
 
     /**
+     * Sets the {@link View}'s supplemental description.
+     * <p>
+     * A supplemental description provides
+     * brief supplemental information for this node, such as the purpose of the node when
+     * that purpose is not conveyed within its textual representation. For example, if a
+     * dropdown select has a purpose of setting font family, the supplemental description
+     * could be "font family". If this node has children, its supplemental description serves
+     * as additional information and is not intended to replace any existing information
+     * in the subtree. This is different from the {@link #setContentDescription(CharSequence)}
+     * in that this description is purely supplemental while a content description may be used
+     * to replace a description for a node or its subtree that an assistive technology
+     * would otherwise compute based on other properties of the node and its descendants.
+     *
+     * <p>
+     * This should omit role or state. Role refers to the kind of user-interface element the View
+     * is, such as a Button or Checkbox. State refers to a frequently changing property of the View,
+     * such as an On/Off state of a button or the audio level of a volume slider.
+     *
+     * @param supplementalDescription The supplemental description.
+     * @see #getSupplementalDescription()
+     * @see #setContentDescription(CharSequence)
+     * @see #setStateDescription(CharSequence) for state changes.
+     * @attr ref android.R.styleable#View_supplementalDescription
+     */
+    @FlaggedApi(FLAG_SUPPLEMENTAL_DESCRIPTION)
+    @RemotableViewMethod
+    public void setSupplementalDescription(@Nullable CharSequence supplementalDescription) {
+        if (mSupplementalDescription == null) {
+            if (supplementalDescription == null) {
+                return;
+            }
+        } else if (mSupplementalDescription.equals(supplementalDescription)) {
+            return;
+        }
+        mSupplementalDescription = supplementalDescription;
+        final boolean nonEmptyDesc = supplementalDescription != null
+                && !supplementalDescription.isEmpty();
+        if (nonEmptyDesc && getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            notifySubtreeAccessibilityStateChangedIfNeeded();
+        } else {
+            notifyViewAccessibilityStateChangedIfNeeded(
+                    AccessibilityEvent.CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION);
+        }
+    }
+
+    /**
      * Sets the id of a view that screen readers are requested to visit after this view.
      *
      * <p>
@@ -34235,9 +34308,20 @@
      */
     @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
     public void setRequestedFrameRate(float frameRate) {
+        // Skip setting the frame rate if it's currently in forced override mode.
+        if (sToolkitViewGroupFrameRateApiFlagValue && getForcedOverrideFrameRateFlag()) {
+            return;
+        }
+
         if (sToolkitSetFrameRateReadOnlyFlagValue) {
             mPreferredFrameRate = frameRate;
         }
+
+        if (sToolkitViewGroupFrameRateApiFlagValue) {
+            // If frameRate is Float.NaN, it means it's set to the default value.
+            // We only want to make the flag true, when the value is not Float.nan
+            setSelfRequestedFrameRateFlag(!Float.isNaN(mPreferredFrameRate));
+        }
     }
 
     /**
@@ -34261,4 +34345,35 @@
         }
         return 0;
     }
+
+    void overrideFrameRate(float frameRate, boolean forceOverride) {
+        setForcedOverrideFrameRateFlag(forceOverride);
+        if (forceOverride || !getSelfRequestedFrameRateFlag()) {
+            mPreferredFrameRate = frameRate;
+        }
+    }
+
+    void setForcedOverrideFrameRateFlag(boolean forcedOverride) {
+        if (forcedOverride) {
+            mPrivateFlags4 |= PFLAG4_FORCED_OVERRIDE_FRAME_RATE;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_FORCED_OVERRIDE_FRAME_RATE;
+        }
+    }
+
+    boolean getForcedOverrideFrameRateFlag() {
+        return (mPrivateFlags4 & PFLAG4_FORCED_OVERRIDE_FRAME_RATE) != 0;
+    }
+
+    void setSelfRequestedFrameRateFlag(boolean forcedOverride) {
+        if (forcedOverride) {
+            mPrivateFlags4 |= PFLAG4_SELF_REQUESTED_FRAME_RATE;
+        } else {
+            mPrivateFlags4 &= ~PFLAG4_SELF_REQUESTED_FRAME_RATE;
+        }
+    }
+
+    boolean getSelfRequestedFrameRateFlag() {
+        return (mPrivateFlags4 & PFLAG4_SELF_REQUESTED_FRAME_RATE) != 0;
+    }
 }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 2237417..4a9916c 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -18,9 +18,12 @@
 
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE;
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+import static android.view.flags.Flags.FLAG_TOOLKIT_VIEWGROUP_SET_REQUESTED_FRAME_RATE_API;
+import static android.view.flags.Flags.toolkitViewgroupSetRequestedFrameRateApi;
 
 import android.animation.LayoutTransition;
 import android.annotation.CallSuper;
+import android.annotation.FlaggedApi;
 import android.annotation.IdRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -448,6 +451,14 @@
     private static final int FLAG_SHOW_CONTEXT_MENU_WITH_COORDS = 0x20000000;
 
     /**
+     * When set, this indicates that the frame rate is passed down from the parent.
+     */
+    private static final int FLAG_PROPAGATED_FRAME_RATE = 0x40000000;
+
+    private static boolean sToolkitViewGroupFrameRateApiFlagValue =
+            toolkitViewgroupSetRequestedFrameRateApi();
+
+    /**
      * Indicates which types of drawing caches are to be kept in memory.
      * This field should be made private, so it is hidden from the SDK.
      * {@hide}
@@ -5361,6 +5372,12 @@
         }
 
         touchAccessibilityNodeProviderIfNeeded(child);
+
+        // If a propagated value exists, pass it to the child.
+        if (sToolkitViewGroupFrameRateApiFlagValue && !Float.isNaN(getRequestedFrameRate())
+                && (mGroupFlags & FLAG_PROPAGATED_FRAME_RATE) != 0) {
+            child.overrideFrameRate(getRequestedFrameRate(), getForcedOverrideFrameRateFlag());
+        }
     }
 
     /**
@@ -9465,4 +9482,75 @@
         }
         return null;
     }
+
+    /**
+     * You can set the preferred frame rate for a ViewGroup using a positive number
+     * or by specifying the preferred frame rate category using constants, including
+     * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW,
+     * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, REQUESTED_FRAME_RATE_CATEGORY_HIGH.
+     * Keep in mind that the preferred frame rate affects the frame rate for the next frame,
+     * so use this method carefully. It's important to note that the preference is valid as
+     * long as the ViewGroup is invalidated. Please also be aware that the requested frame rate
+     * will not propagate to child views.
+     *
+     * @param frameRate the preferred frame rate of the ViewGroup.
+     */
+    @Override
+    @FlaggedApi(FLAG_TOOLKIT_VIEWGROUP_SET_REQUESTED_FRAME_RATE_API)
+    public void setRequestedFrameRate(float frameRate) {
+        if (sToolkitViewGroupFrameRateApiFlagValue) {
+            if (getForcedOverrideFrameRateFlag()) {
+                return;
+            }
+            super.setRequestedFrameRate(frameRate);
+            // If frameRate is Float.NaN, it means it's set to the default value.
+            // We only want to make the flag true, when the value is not Float.nan
+            setSelfRequestedFrameRateFlag(!Float.isNaN(getRequestedFrameRate()));
+            mGroupFlags &= ~FLAG_PROPAGATED_FRAME_RATE;
+        }
+    }
+
+    /**
+     * You can set the preferred frame rate for a ViewGroup and its children using a positive number
+     * or by specifying the preferred frame rate category using constants, including
+     * REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE, REQUESTED_FRAME_RATE_CATEGORY_LOW,
+     * REQUESTED_FRAME_RATE_CATEGORY_NORMAL, REQUESTED_FRAME_RATE_CATEGORY_HIGH.
+     * Keep in mind that the preferred frame rate affects the frame rate for the next frame,
+     * so use this method carefully. It's important to note that the preference is valid as
+     * long as the ViewGroup or any of its children is invalidated.
+     * To undo the frame rate propagation, call the API with REQUESTED_FRAME_RATE_CATEGORY_DEFAULT.
+     *
+     * @param frameRate the preferred frame rate of the ViewGroup.
+     * @param forceOverride indicate whether it should override the frame rate of
+     *        all the children with the given frame rate.
+     */
+    @FlaggedApi(FLAG_TOOLKIT_VIEWGROUP_SET_REQUESTED_FRAME_RATE_API)
+    public void propagateRequestedFrameRate(float frameRate, boolean forceOverride) {
+        if (sToolkitViewGroupFrameRateApiFlagValue) {
+            // Skip setting the frame rate if it's currently in forced override mode.
+            if (getForcedOverrideFrameRateFlag()) {
+                return;
+            }
+
+            // frame rate could be set previously with setRequestedFrameRate
+            // or propagateRequestedFrameRate
+            setSelfRequestedFrameRateFlag(false);
+            overrideFrameRate(frameRate, forceOverride);
+            setSelfRequestedFrameRateFlag(true);
+        }
+    }
+
+    @Override
+    void overrideFrameRate(float frameRate, boolean forceOverride) {
+        // if it's in forceOverrid mode or has no self requested frame rate,
+        // it will override the frame rate.
+        if (forceOverride || !getSelfRequestedFrameRateFlag()) {
+            super.overrideFrameRate(frameRate, forceOverride);
+            mGroupFlags |= FLAG_PROPAGATED_FRAME_RATE;
+
+            for (int i = 0; i < getChildCount(); i++) {
+                getChildAt(i).overrideFrameRate(frameRate, forceOverride);
+            }
+        }
+    }
 }
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index 4e9d054..c690787 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -810,6 +810,20 @@
     @FlaggedApi(Flags.FLAG_A11Y_EXPANSION_STATE_API)
     public static final int CONTENT_CHANGE_TYPE_EXPANDED = 1 << 14;
 
+    /**
+     * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+     * The source node changed its supplemental description, which is returned by
+     * {@link AccessibilityNodeInfo#getSupplementalDescription()}.
+     * The view changing its supplemental description should call
+     * {@link AccessibilityNodeInfo#setSupplementalDescription(CharSequence)} and
+     * then send this event.
+     *
+     * @see AccessibilityNodeInfo#getSupplementalDescription()
+     * @see AccessibilityNodeInfo#setSupplementalDescription(CharSequence)
+     */
+    @FlaggedApi(Flags.FLAG_SUPPLEMENTAL_DESCRIPTION)
+    public static final int CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION = 1 << 15;
+
     // Speech state change types.
 
     /** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
@@ -942,6 +956,7 @@
                 CONTENT_CHANGE_TYPE_CONTENT_INVALID,
                 CONTENT_CHANGE_TYPE_ERROR,
                 CONTENT_CHANGE_TYPE_ENABLED,
+                CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION,
             })
     public @interface ContentChangeTypes {}
 
@@ -1222,7 +1237,14 @@
                 return "CONTENT_CHANGE_TYPE_CONTENT_INVALID";
             case CONTENT_CHANGE_TYPE_ERROR: return "CONTENT_CHANGE_TYPE_ERROR";
             case CONTENT_CHANGE_TYPE_ENABLED: return "CONTENT_CHANGE_TYPE_ENABLED";
-            default: return Integer.toHexString(type);
+            default: {
+                if (Flags.supplementalDescription()) {
+                    if (type == CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION) {
+                        return "CONTENT_CHANGE_TYPE_SUPPLEMENTAL_DESCRIPTION";
+                    }
+                }
+                return Integer.toHexString(type);
+            }
         }
     }
 
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index f70f7e6..5e5f33e 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -997,6 +997,8 @@
 
     private static final int BOOLEAN_PROPERTY_SUPPORTS_GRANULAR_SCROLLING = 1 << 26;
 
+    private static final int BOOLEAN_PROPERTY_FIELD_REQUIRED = 1 << 27;
+
     /**
      * Bits that provide the id of a virtual descendant of a view.
      */
@@ -1085,6 +1087,7 @@
     private CharSequence mPaneTitle;
     private CharSequence mStateDescription;
     private CharSequence mContentDescription;
+    private CharSequence mSupplementalDescription;
     private CharSequence mTooltipText;
     private String mViewIdResourceName;
     private String mUniqueId;
@@ -2543,6 +2546,32 @@
     }
 
     /**
+     * Gets whether a node representing a form field requires input or selection.
+     *
+     * @return {@code true} if {@code this} node represents a form field that requires input or
+     *     selection, {@code false} otherwise.
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_IS_REQUIRED_API)
+    public boolean isFieldRequired() {
+        return getBooleanProperty(BOOLEAN_PROPERTY_FIELD_REQUIRED);
+    }
+
+    /**
+     * Sets whether {@code this} node represents a form field that requires input or selection.
+     *
+     * <p><strong>Note:</strong> Cannot be called from an AccessibilityService. This class is made
+     * immutable before being delivered to an AccessibilityService.
+     *
+     * @param required {@code true} if input or selection of this node should be required, {@code
+     *     false} otherwise.
+     * @throws IllegalStateException If called from an AccessibilityService
+     */
+    @FlaggedApi(Flags.FLAG_A11Y_IS_REQUIRED_API)
+    public void setFieldRequired(boolean required) {
+        setBooleanProperty(BOOLEAN_PROPERTY_FIELD_REQUIRED, required);
+    }
+
+    /**
      * Gets whether this node is focusable.
      *
      * <p>In the View system, this typically maps to {@link View#isFocusable()}.
@@ -3686,6 +3715,27 @@
         return mContentDescription;
     }
 
+    /**
+     * Gets the supplemental description of this node. A supplemental description provides
+     * brief supplemental information for this node, such as the purpose of the node when
+     * that purpose is not conveyed within its textual representation. For example, if a
+     * dropdown select has a purpose of setting font family, the supplemental description
+     * could be "font family". If this node has children, its supplemental description serves
+     * as additional information and is not intended to replace any existing information
+     * in the subtree. This is different from the {@link #getContentDescription()} in that
+     * this description is purely supplemental while a content description may be used
+     * to replace a description for a node or its subtree that an assistive technology
+     * would otherwise compute based on other properties of the node and its descendants.
+     *
+     * @return The supplemental description.
+     * @see #setSupplementalDescription(CharSequence)
+     * @see #getContentDescription()
+     */
+    @FlaggedApi(Flags.FLAG_SUPPLEMENTAL_DESCRIPTION)
+    @Nullable
+    public CharSequence getSupplementalDescription() {
+        return mSupplementalDescription;
+    }
 
     /**
      * Sets the state description of this node.
@@ -3724,6 +3774,35 @@
     }
 
     /**
+     * Sets the supplemental description of this node. A supplemental description provides
+     * brief supplemental information for this node, such as the purpose of the node when
+     * that purpose is not conveyed within its textual representation. For example, if a
+     * dropdown select has a purpose of setting font family, the supplemental description
+     * could be "font family". If this node has children, its supplemental description serves
+     * as additional information and is not intended to replace any existing information
+     * in the subtree. This is different from the {@link #setContentDescription(CharSequence)}
+     * in that this description is purely supplemental while a content description may be used
+     * to replace a description for a node or its subtree that an assistive technology
+     * would otherwise compute based on other properties of the node and its descendants.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     *
+     * @param supplementalDescription The supplemental description.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     * @see #getSupplementalDescription()
+     * @see #setContentDescription(CharSequence)
+     */
+    @FlaggedApi(Flags.FLAG_SUPPLEMENTAL_DESCRIPTION)
+    public void setSupplementalDescription(@Nullable CharSequence supplementalDescription) {
+        enforceNotSealed();
+        mSupplementalDescription = (supplementalDescription == null) ? null
+                : supplementalDescription.subSequence(0, supplementalDescription.length());
+    }
+
+    /**
      * Gets the tooltip text of this node.
      *
      * @return The tooltip text.
@@ -4657,6 +4736,10 @@
             nonDefaultFields |= bitAt(fieldIndex);
         }
         fieldIndex++;
+        if (!Objects.equals(mSupplementalDescription, DEFAULT.mSupplementalDescription)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (!Objects.equals(mPaneTitle, DEFAULT.mPaneTitle)) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
@@ -4843,6 +4926,9 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
             parcel.writeCharSequence(mContentDescription);
         }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            parcel.writeCharSequence(mSupplementalDescription);
+        }
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mContainerTitle);
@@ -4950,6 +5036,7 @@
         mError = other.mError;
         mStateDescription = other.mStateDescription;
         mContentDescription = other.mContentDescription;
+        mSupplementalDescription = other.mSupplementalDescription;
         mPaneTitle = other.mPaneTitle;
         mTooltipText = other.mTooltipText;
         mContainerTitle = other.mContainerTitle;
@@ -5119,6 +5206,9 @@
         if (isBitSet(nonDefaultFields, fieldIndex++)) {
             mContentDescription = parcel.readCharSequence();
         }
+        if (isBitSet(nonDefaultFields, fieldIndex++)) {
+            mSupplementalDescription = parcel.readCharSequence();
+        }
         if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mContainerTitle = parcel.readCharSequence();
@@ -5483,6 +5573,9 @@
         builder.append("; maxTextLength: ").append(mMaxTextLength);
         builder.append("; stateDescription: ").append(mStateDescription);
         builder.append("; contentDescription: ").append(mContentDescription);
+        if (Flags.supplementalDescription()) {
+            builder.append("; supplementalDescription: ").append(mSupplementalDescription);
+        }
         builder.append("; tooltipText: ").append(mTooltipText);
         builder.append("; containerTitle: ").append(mContainerTitle);
         builder.append("; viewIdResName: ").append(mViewIdResourceName);
@@ -5491,6 +5584,9 @@
 
         builder.append("; checkable: ").append(isCheckable());
         builder.append("; checked: ").append(isChecked());
+        if (Flags.a11yIsRequiredApi()) {
+            builder.append("; required: ").append(isFieldRequired());
+        }
         builder.append("; focusable: ").append(isFocusable());
         builder.append("; focused: ").append(isFocused());
         builder.append("; selected: ").append(isSelected());
@@ -6417,6 +6513,19 @@
         /** Range type: percent with values from zero to one hundred. */
         public static final int RANGE_TYPE_PERCENT = 2;
 
+        /**
+         * Range type: indeterminate.
+         *
+         * A {@link RangeInfo} type used to represent a node which may typically expose range
+         * information but is presently in an indeterminate state, such as a {@link
+         * android.widget.ProgressBar} representing a loading operation of unknown duration.
+         * When using this type, the {@code min}, {@code max}, and {@code current} values used to
+         * construct an instance may be ignored. It is recommended to use {@code Float.NaN} for
+         * these values.
+         */
+        @FlaggedApi(Flags.FLAG_INDETERMINATE_RANGE_INFO)
+        public static final int RANGE_TYPE_INDETERMINATE = 3;
+
         private int mType;
         private float mMin;
         private float mMax;
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index ffa819f..c07da41 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -222,6 +222,13 @@
 }
 
 flag {
+    name: "supplemental_description"
+    namespace: "accessibility"
+    description: "Feature flag for supplemental description api"
+    bug: "375266174"
+}
+
+flag {
     name: "support_multiple_labeledby"
     namespace: "accessibility"
     description: "Feature flag for supporting multiple labels in AccessibilityNodeInfo labeledby api"
@@ -251,3 +258,10 @@
         purpose: PURPOSE_BUGFIX
     }
  }
+
+ flag {
+    name: "indeterminate_range_info"
+    namespace: "accessibility"
+    description: "Creates a way to create an INDETERMINATE RangeInfo"
+    bug: "376108874"
+ }
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index c31df73..1c7570e 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -122,6 +122,14 @@
 }
 
 flag {
+    name: "toolkit_viewgroup_set_requested_frame_rate_api"
+    namespace: "toolkit"
+    description: "Feature flag to introduce new frame rate setting APIs on ViewGroup"
+    bug: "335874198"
+    is_fixed_read_only: true
+}
+
+flag {
     name: "toolkit_frame_rate_touch_boost_25q1"
     namespace: "toolkit"
     description: "Feature flag to not suppress touch boost for specific windowTypes in VRR V QPR2"
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index e6eec37..9b6311f 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -20,7 +20,6 @@
 import static android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO;
 import static android.appwidget.flags.Flags.drawDataParcel;
 import static android.appwidget.flags.Flags.remoteAdapterConversion;
-import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
 import static android.util.proto.ProtoInputStream.NO_MORE_FIELDS;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
 
@@ -55,10 +54,6 @@
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.ServiceConnection;
-import android.content.om.FabricatedOverlay;
-import android.content.om.OverlayInfo;
-import android.content.om.OverlayManager;
-import android.content.om.OverlayManagerTransaction;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.ColorStateList;
@@ -84,12 +79,14 @@
 import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.system.Os;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
@@ -131,8 +128,11 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -8552,8 +8552,12 @@
      * @hide
      */
     public static final class ColorResources {
-        private static final String OVERLAY_NAME = "remote_views_color_resources";
-        private static final String OVERLAY_TARGET_PACKAGE_NAME = "android";
+        // Set of valid colors resources.
+        private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
+        private static final int LAST_RESOURCE_COLOR_ID =
+            android.R.color.system_error_1000;
+        // Size, in bytes, of an entry in the array of colors in an ARSC file.
+        private static final int ARSC_ENTRY_SIZE = 16;
 
         private final ResourcesLoader mLoader;
         private final SparseIntArray mColorMapping;
@@ -8587,6 +8591,44 @@
         }
 
         /**
+         * Creates the compiled resources content from the asset stored in the APK.
+         *
+         * The asset is a compiled resource with the correct resources name and correct ids, only
+         * the values are incorrect. The last value is at the very end of the file. The resources
+         * are in an array, the array's entries are 16 bytes each. We use this to work out the
+         * location of all the positions of the various resources.
+         */
+        @Nullable
+        private static byte[] createCompiledResourcesContent(Context context,
+                SparseIntArray colorResources) throws IOException {
+            byte[] content;
+            try (InputStream input = context.getResources().openRawResource(
+                    com.android.internal.R.raw.remote_views_color_resources)) {
+                ByteArrayOutputStream rawContent = readFileContent(input);
+                content = rawContent.toByteArray();
+            }
+            int valuesOffset =
+                    content.length - (LAST_RESOURCE_COLOR_ID & 0xffff) * ARSC_ENTRY_SIZE - 4;
+            if (valuesOffset < 0) {
+                Log.e(LOG_TAG, "ARSC file for theme colors is invalid.");
+                return null;
+            }
+            for (int colorRes = FIRST_RESOURCE_COLOR_ID; colorRes <= LAST_RESOURCE_COLOR_ID;
+                    colorRes++) {
+                // The last 2 bytes are the index in the color array.
+                int index = colorRes & 0xffff;
+                int offset = valuesOffset + index * ARSC_ENTRY_SIZE;
+                int value = colorResources.get(colorRes, context.getColor(colorRes));
+                // Write the 32 bit integer in little endian
+                for (int b = 0; b < 4; b++) {
+                    content[offset + b] = (byte) (value & 0xff);
+                    value >>= 8;
+                }
+            }
+            return content;
+        }
+
+        /**
          *  Adds a resource loader for theme colors to the given context.
          *
          * @param context Context of the view hosting the widget.
@@ -8597,38 +8639,31 @@
         @Nullable
         public static ColorResources create(Context context, SparseIntArray colorMapping) {
             try {
-                String owningPackage = context.getPackageName();
-                FabricatedOverlay overlay = new FabricatedOverlay.Builder(owningPackage,
-                        OVERLAY_NAME, OVERLAY_TARGET_PACKAGE_NAME).build();
-
-                for (int i = 0; i < colorMapping.size(); i++) {
-                    overlay.setResourceValue(
-                            context.getResources().getResourceName(colorMapping.keyAt(i)),
-                            TYPE_INT_COLOR_ARGB8, colorMapping.valueAt(i), null);
-                }
-                OverlayManager overlayManager = context.getSystemService(OverlayManager.class);
-                OverlayManagerTransaction.Builder transaction =
-                        new OverlayManagerTransaction.Builder()
-                                .registerFabricatedOverlay(overlay)
-                                .setSelfTargeting(true);
-                overlayManager.commit(transaction.build());
-
-                OverlayInfo overlayInfo =
-                        overlayManager.getOverlayInfosForTarget(OVERLAY_TARGET_PACKAGE_NAME)
-                                .stream()
-                                .filter(info -> TextUtils.equals(info.overlayName, OVERLAY_NAME)
-                                        && TextUtils.equals(info.packageName, owningPackage))
-                                .findFirst()
-                                .orElse(null);
-                if (overlayInfo == null) {
-                    Log.e(LOG_TAG, "Failed to get overlay info ", new Throwable());
+                byte[] contentBytes = createCompiledResourcesContent(context, colorMapping);
+                if (contentBytes == null) {
                     return null;
                 }
-                ResourcesLoader colorsLoader = new ResourcesLoader();
-                colorsLoader.addProvider(ResourcesProvider.loadOverlay(overlayInfo));
-                return new ColorResources(colorsLoader, colorMapping.clone());
-            } catch (Exception e) {
-                Log.e(LOG_TAG, "Failed to add theme color overlay into loader", e);
+                FileDescriptor arscFile = null;
+                try {
+                    arscFile = Os.memfd_create("remote_views_theme_colors.arsc", 0 /* flags */);
+                    // Note: This must not be closed through the OutputStream.
+                    try (OutputStream pipeWriter = new FileOutputStream(arscFile)) {
+                        pipeWriter.write(contentBytes);
+
+                        try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(arscFile)) {
+                            ResourcesLoader colorsLoader = new ResourcesLoader();
+                            colorsLoader.addProvider(ResourcesProvider
+                                    .loadFromTable(pfd, null /* assetsProvider */));
+                            return new ColorResources(colorsLoader, colorMapping.clone());
+                        }
+                    }
+                } finally {
+                    if (arscFile != null) {
+                        Os.close(arscFile);
+                    }
+                }
+            } catch (Exception ex) {
+                Log.e(LOG_TAG, "Failed to setup the context for theme colors", ex);
             }
             return null;
         }
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 255bd67..65cd190 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -45,6 +45,7 @@
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
 
+import java.time.DateTimeException;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.ZoneId;
@@ -291,11 +292,26 @@
     }
 
     private void createTime(String timeZone) {
-        if (timeZone != null) {
-            mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
+        TimeZone tz = null;
+        if (timeZone == null) {
+            tz = TimeZone.getDefault();
+            // Note that mTimeZone should always be null if timeZone is.
         } else {
-            mTime = Calendar.getInstance();
+            tz = TimeZone.getTimeZone(timeZone);
+            try {
+                // Try converting this TZ to a zoneId to make sure it's valid. This
+                // performs a different set of checks than TimeZone.getTimeZone so
+                // we can avoid exceptions later when we do need this conversion.
+                tz.toZoneId();
+            } catch (DateTimeException ex) {
+                // If we're here, the user supplied timezone is invalid, so reset
+                // mTimeZone to something sane.
+                tz = TimeZone.getDefault();
+                mTimeZone = tz.getID();
+            }
         }
+
+        mTime = Calendar.getInstance(tz);
     }
 
     /**
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 24c27c2..dae87dd 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -74,7 +74,8 @@
     ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS(
             Flags::enableDesktopAppLaunchAlttabTransitions, false),
     ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS(
-            Flags::enableDesktopAppLaunchTransitions, false);
+            Flags::enableDesktopAppLaunchTransitions, false),
+    ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false);
 
     private static final String TAG = "DesktopModeFlagsUtil";
     // Function called to obtain aconfig flag value.
diff --git a/core/java/android/window/OnBackInvokedCallbackInfo.java b/core/java/android/window/OnBackInvokedCallbackInfo.java
index bb5fe96..44c7bd9 100644
--- a/core/java/android/window/OnBackInvokedCallbackInfo.java
+++ b/core/java/android/window/OnBackInvokedCallbackInfo.java
@@ -16,6 +16,8 @@
 
 package android.window;
 
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
+
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -29,19 +31,23 @@
     private final IOnBackInvokedCallback mCallback;
     private @OnBackInvokedDispatcher.Priority int mPriority;
     private final boolean mIsAnimationCallback;
+    private final @SystemOverrideOnBackInvokedCallback.OverrideBehavior int mOverrideBehavior;
 
     public OnBackInvokedCallbackInfo(@NonNull IOnBackInvokedCallback callback,
             int priority,
-            boolean isAnimationCallback) {
+            boolean isAnimationCallback,
+            int overrideBehavior) {
         mCallback = callback;
         mPriority = priority;
         mIsAnimationCallback = isAnimationCallback;
+        mOverrideBehavior = overrideBehavior;
     }
 
     private OnBackInvokedCallbackInfo(@NonNull Parcel in) {
         mCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
         mPriority = in.readInt();
         mIsAnimationCallback = in.readBoolean();
+        mOverrideBehavior = in.readInt();
     }
 
     @Override
@@ -54,6 +60,7 @@
         dest.writeStrongInterface(mCallback);
         dest.writeInt(mPriority);
         dest.writeBoolean(mIsAnimationCallback);
+        dest.writeInt(mOverrideBehavior);
     }
 
     public static final Creator<OnBackInvokedCallbackInfo> CREATOR =
@@ -70,7 +77,8 @@
             };
 
     public boolean isSystemCallback() {
-        return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM;
+        return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM
+                || mOverrideBehavior != OVERRIDE_UNDEFINED;
     }
 
     @NonNull
@@ -87,12 +95,18 @@
         return mIsAnimationCallback;
     }
 
+    @SystemOverrideOnBackInvokedCallback.OverrideBehavior
+    public int getOverrideBehavior() {
+        return mOverrideBehavior;
+    }
+
     @Override
     public String toString() {
         return "OnBackInvokedCallbackInfo{"
                 + "mCallback=" + mCallback
                 + ", mPriority=" + mPriority
                 + ", mIsAnimationCallback=" + mIsAnimationCallback
+                + ", mOverrideBehavior=" + mOverrideBehavior
                 + '}';
     }
 }
diff --git a/core/java/android/window/SystemOnBackInvokedCallbacks.java b/core/java/android/window/SystemOnBackInvokedCallbacks.java
new file mode 100644
index 0000000..f67520b
--- /dev/null
+++ b/core/java/android/window/SystemOnBackInvokedCallbacks.java
@@ -0,0 +1,168 @@
+/*
+ * 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.window;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.Activity;
+import android.util.ArrayMap;
+
+import com.android.window.flags.Flags;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * Utility class providing {@link OnBackInvokedCallback}s to override the default behavior when
+ * system back is invoked. e.g. {@link Activity#finish}
+ *
+ * <p>By registering these callbacks with the {@link OnBackInvokedDispatcher}, the system can
+ * trigger specific behaviors and play corresponding ahead-of-time animations when the back
+ * gesture is invoked.
+ *
+ * <p>For example, to trigger the {@link Activity#moveTaskToBack} behavior:
+ * <pre>
+ *   OnBackInvokedDispatcher dispatcher = activity.getOnBackInvokedDispatcher();
+ *   dispatcher.registerOnBackInvokedCallback(
+ *       OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ *       SystemOnBackInvokedCallbacks.moveTaskToBackCallback(activity));
+ * </pre>
+ */
+@SuppressWarnings("SingularCallback")
+@FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+public final class SystemOnBackInvokedCallbacks {
+    private static final OverrideCallbackFactory<Activity> sMoveTaskToBackFactory = new
+            MoveTaskToBackCallbackFactory();
+    private static final OverrideCallbackFactory<Activity> sFinishAndRemoveTaskFactory = new
+            FinishAndRemoveTaskCallbackFactory();
+
+    private SystemOnBackInvokedCallbacks() {
+        throw new UnsupportedOperationException("This is a utility class and cannot be "
+                + "instantiated");
+    }
+
+    /**
+     * <p>Get a callback to triggers {@link Activity#moveTaskToBack(boolean)} on the associated
+     * {@link Activity}, moving the task containing the activity to the background. The system
+     * will play the corresponding transition animation, regardless of whether the activity
+     * is the root activity of the task.</p>
+     *
+     * @param activity The associated {@link Activity}
+     * @see Activity#moveTaskToBack(boolean)
+     */
+    @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+    @NonNull
+    public static OnBackInvokedCallback moveTaskToBackCallback(@NonNull Activity activity) {
+        return sMoveTaskToBackFactory.getOverrideCallback(activity);
+    }
+
+    /**
+     * <p>Get a callback to triggers {@link Activity#finishAndRemoveTask()} on the associated
+     * {@link Activity}. If the activity is the root activity of its task, the entire task
+     * will be removed from the recents task. The activity will be finished in all cases.
+     * The system will play the corresponding transition animation.</p>
+     *
+     * @param activity The associated {@link Activity}
+     * @see Activity#finishAndRemoveTask()
+     */
+    @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK)
+    @NonNull
+    public static OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull Activity activity) {
+        return sFinishAndRemoveTaskFactory.getOverrideCallback(activity);
+    }
+
+    /**
+     * Abstract factory for creating system override {@link SystemOverrideOnBackInvokedCallback}
+     * instances.
+     *
+     * <p>Concrete implementations of this factory are responsible for creating callbacks that
+     * override the default system back navigation behavior. These callbacks should be used
+     * exclusively for system overrides and should never be invoked directly.</p>
+     */
+    private abstract static class OverrideCallbackFactory<TYPE> {
+        private final ArrayMap<WeakReference<TYPE>,
+                WeakReference<SystemOverrideOnBackInvokedCallback>> mObjectMap = new ArrayMap<>();
+
+        protected abstract SystemOverrideOnBackInvokedCallback createCallback(
+                @NonNull TYPE context);
+
+        @NonNull SystemOverrideOnBackInvokedCallback getOverrideCallback(@NonNull TYPE object) {
+            if (object == null) {
+                throw new NullPointerException("Input object cannot be null");
+            }
+            synchronized (mObjectMap) {
+                WeakReference<SystemOverrideOnBackInvokedCallback> callback = null;
+                for (int i = mObjectMap.size() - 1; i >= 0; --i) {
+                    final WeakReference<TYPE> next = mObjectMap.keyAt(i);
+                    if (next.get() == object) {
+                        callback = mObjectMap.get(next);
+                        break;
+                    }
+                }
+                if (callback != null) {
+                    return callback.get();
+                }
+                final SystemOverrideOnBackInvokedCallback contextCallback = createCallback(object);
+                if (contextCallback != null) {
+                    mObjectMap.put(new WeakReference<>(object),
+                            new WeakReference<>(contextCallback));
+                }
+                return contextCallback;
+            }
+        }
+    }
+
+    private static class MoveTaskToBackCallbackFactory extends OverrideCallbackFactory<Activity> {
+        @Override
+        protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) {
+            final WeakReference<Activity> activityRef = new WeakReference<>(activity);
+            return new SystemOverrideOnBackInvokedCallback() {
+                @Override
+                public void onBackInvoked() {
+                    if (activityRef.get() != null) {
+                        activityRef.get().moveTaskToBack(true /* nonRoot */);
+                    }
+                }
+
+                @Override
+                public int overrideBehavior() {
+                    return OVERRIDE_MOVE_TASK_TO_BACK;
+                }
+            };
+        }
+    }
+
+    private static class FinishAndRemoveTaskCallbackFactory extends
+            OverrideCallbackFactory<Activity> {
+        @Override
+        protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) {
+            final WeakReference<Activity> activityRef = new WeakReference<>(activity);
+            return new SystemOverrideOnBackInvokedCallback() {
+                @Override
+                public void onBackInvoked() {
+                    if (activityRef.get() != null) {
+                        activityRef.get().finishAndRemoveTask();
+                    }
+                }
+
+                @Override
+                public int overrideBehavior() {
+                    return OVERRIDE_FINISH_AND_REMOVE_TASK;
+                }
+            };
+        }
+    }
+}
diff --git a/core/java/android/window/SystemOverrideOnBackInvokedCallback.java b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java
new file mode 100644
index 0000000..3360a19
--- /dev/null
+++ b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java
@@ -0,0 +1,61 @@
+/*
+ * 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.window;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Non-default ahead-of-time system OnBackInvokedCallback.
+ * @hide
+ */
+public interface SystemOverrideOnBackInvokedCallback extends OnBackInvokedCallback {
+    /**
+     * No override request
+     */
+    int OVERRIDE_UNDEFINED = 0;
+
+    /**
+     * Navigating back will bring the task to back
+     */
+    int OVERRIDE_MOVE_TASK_TO_BACK = 1;
+
+    /**
+     * Navigating back will finish activity, and remove the task if this activity is root activity.
+     */
+    int OVERRIDE_FINISH_AND_REMOVE_TASK = 2;
+
+    /** @hide */
+    @IntDef({
+            OVERRIDE_UNDEFINED,
+            OVERRIDE_MOVE_TASK_TO_BACK,
+            OVERRIDE_FINISH_AND_REMOVE_TASK,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface OverrideBehavior {
+    }
+
+    /**
+     * @return Override type of this callback.
+     */
+    @OverrideBehavior
+    default int overrideBehavior() {
+        return OVERRIDE_UNDEFINED;
+    }
+}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 34abf31..3fe63ab 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -1867,27 +1867,33 @@
             switch (type) {
                 case HIERARCHY_OP_TYPE_REPARENT: return "reparent";
                 case HIERARCHY_OP_TYPE_REORDER: return "reorder";
-                case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "ChildrenTasksReparent";
-                case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "SetLaunchRoot";
-                case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "SetAdjacentRoot";
-                case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "LaunchTask";
-                case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "SetAdjacentFlagRoot";
+                case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: return "childrenTasksReparent";
+                case HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT: return "setLaunchRoot";
+                case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "setAdjacentRoot";
+                case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "launchTask";
+                case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "setAdjacentFlagRoot";
                 case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT:
-                    return "SetDisableLaunchAdjacent";
-                case HIERARCHY_OP_TYPE_PENDING_INTENT: return "PendingIntent";
-                case HIERARCHY_OP_TYPE_START_SHORTCUT: return "StartShortcut";
+                    return "setDisableLaunchAdjacent";
+                case HIERARCHY_OP_TYPE_PENDING_INTENT: return "pendingIntent";
+                case HIERARCHY_OP_TYPE_START_SHORTCUT: return "startShortcut";
+                case HIERARCHY_OP_TYPE_RESTORE_TRANSIENT_ORDER: return "restoreTransientOrder";
                 case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: return "addInsetsFrameProvider";
                 case HIERARCHY_OP_TYPE_REMOVE_INSETS_FRAME_PROVIDER:
                     return "removeInsetsFrameProvider";
                 case HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP: return "setAlwaysOnTop";
-                case HIERARCHY_OP_TYPE_REMOVE_TASK: return "RemoveTask";
+                case HIERARCHY_OP_TYPE_REMOVE_TASK: return "removeTask";
                 case HIERARCHY_OP_TYPE_FINISH_ACTIVITY: return "finishActivity";
-                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "ClearAdjacentRoot";
+                case HIERARCHY_OP_TYPE_CLEAR_ADJACENT_ROOTS: return "clearAdjacentRoot";
                 case HIERARCHY_OP_TYPE_SET_REPARENT_LEAF_TASK_IF_RELAUNCH:
                     return "setReparentLeafTaskIfRelaunch";
                 case HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION:
                     return "addTaskFragmentOperation";
+                case HIERARCHY_OP_TYPE_MOVE_PIP_ACTIVITY_TO_PINNED_TASK:
+                    return "movePipActivityToPinnedTask";
+                case HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE: return "setIsTrimmable";
+                case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: return "restoreBackNav";
                 case HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES: return "setExcludeInsetsTypes";
+                case HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE: return "setKeyguardState";
                 default: return "HOP(" + type + ")";
             }
         }
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index c9d458f..0ea4bb4 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -16,6 +16,9 @@
 
 package android.window;
 
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
+
+import static com.android.window.flags.Flags.predictiveBackSystemOverrideCallback;
 import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver;
 import static com.android.window.flags.Flags.predictiveBackTimestampApi;
 
@@ -201,6 +204,15 @@
                 mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
                 return;
             }
+            if (predictiveBackPrioritySystemNavigationObserver()
+                    && predictiveBackSystemOverrideCallback()) {
+                if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER
+                        && callback instanceof SystemOverrideOnBackInvokedCallback) {
+                    Log.e(TAG, "System override callbacks cannot be registered to "
+                            + "NAVIGATION_OBSERVER");
+                    return;
+                }
+            }
             if (predictiveBackPrioritySystemNavigationObserver()) {
                 if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) {
                     registerSystemNavigationObserverCallback(callback);
@@ -365,7 +377,8 @@
     public void tryInvokeSystemNavigationObserverCallback() {
         OnBackInvokedCallback topCallback = getTopCallback();
         Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null);
-        if (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) {
+        final boolean isSystemOverride = topCallback instanceof SystemOverrideOnBackInvokedCallback;
+        if ((callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) || isSystemOverride) {
             invokeSystemNavigationObserverCallback();
         }
     }
@@ -384,14 +397,22 @@
             OnBackInvokedCallbackInfo callbackInfo = null;
             if (callback != null) {
                 int priority = mAllCallbacks.get(callback);
+                int overrideAnimation = OVERRIDE_UNDEFINED;
+                if (callback instanceof SystemOverrideOnBackInvokedCallback) {
+                    overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback)
+                            .overrideBehavior();
+                }
+                final boolean isSystemCallback = priority == PRIORITY_SYSTEM
+                        || overrideAnimation != OVERRIDE_UNDEFINED;
                 final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback,
                         mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme,
                         this::invokeSystemNavigationObserverCallback,
-                        /*isSystemCallback*/ priority == PRIORITY_SYSTEM);
+                        isSystemCallback /*isSystemCallback*/);
                 callbackInfo = new OnBackInvokedCallbackInfo(
                         iCallback,
                         priority,
-                        callback instanceof OnBackAnimationCallback);
+                        callback instanceof OnBackAnimationCallback,
+                        overrideAnimation);
             }
             mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
         } catch (RemoteException e) {
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index a2201bb..fd5de91 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -9,13 +9,6 @@
 }
 
 flag {
-    name: "bal_require_opt_in_same_uid"
-    namespace: "responsible_apis"
-    description: "Require the PendingIntent creator/sender to opt in if it is the same UID"
-    bug: "296478951"
-}
-
-flag {
     name: "bal_dont_bring_existing_background_task_stack_to_fg"
     namespace: "responsible_apis"
     description: "When starting a PendingIntent with ONLY creator privileges, don't bring the existing task stack to foreground"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 3a03508..11f6849 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -49,6 +49,17 @@
 }
 
 flag {
+  name: "respect_animation_clip"
+  namespace: "windowing_frontend"
+  description: "Fix missing clip transformation of animation"
+  bug: "376601866"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "edge_to_edge_by_default"
   namespace: "windowing_frontend"
   description: "Make app go edge-to-edge by default when targeting SDK 35 or greater"
@@ -350,6 +361,17 @@
 }
 
 flag {
+  name: "defer_predictive_animation_if_no_snapshot"
+  namespace: "windowing_frontend"
+  description: "If no snapshot for previous window, start animation until the client has draw."
+  bug: "374621014"
+  is_fixed_read_only: true
+  metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
   name: "disallow_app_progress_embedded_window"
   namespace: "windowing_frontend"
   description: "Pilfer pointers when app transfer input gesture to embedded window."
@@ -358,4 +380,12 @@
   metadata {
     purpose: PURPOSE_BUGFIX
   }
+}
+
+flag {
+    name: "predictive_back_system_override_callback"
+    namespace: "windowing_frontend"
+    description: "Provide pre-make predictive back API extension"
+    is_fixed_read_only: true
+    bug: "362938401"
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/content/om/OverlayConfig.java b/core/java/com/android/internal/content/om/OverlayConfig.java
index 38593b4..07e178c 100644
--- a/core/java/com/android/internal/content/om/OverlayConfig.java
+++ b/core/java/com/android/internal/content/om/OverlayConfig.java
@@ -19,7 +19,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.PackagePartitions;
-import android.content.res.AssetManager;
 import android.os.Build;
 import android.os.Trace;
 import android.util.ArrayMap;
@@ -534,7 +533,7 @@
      */
     @NonNull
     public String[] createImmutableFrameworkIdmapsInZygote() {
-        final String targetPath = AssetManager.FRAMEWORK_APK_PATH;
+        final String targetPath = "/system/framework/framework-res.apk";
         final ArrayList<String> idmapPaths = new ArrayList<>();
         final ArrayList<IdmapInvocation> idmapInvocations =
                 getImmutableFrameworkOverlayIdmapInvocations();
diff --git a/core/java/com/android/internal/content/om/OverlayManagerImpl.java b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
index fa5cf2a..c462449 100644
--- a/core/java/com/android/internal/content/om/OverlayManagerImpl.java
+++ b/core/java/com/android/internal/content/om/OverlayManagerImpl.java
@@ -35,7 +35,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
-import android.content.res.AssetManager;
 import android.os.FabricatedOverlayInfo;
 import android.os.FabricatedOverlayInternal;
 import android.os.FabricatedOverlayInternalEntry;
@@ -61,8 +60,8 @@
 import java.util.Objects;
 
 /**
- * This class provides the functionalities for managing self-targeting overlays, including
- * registering an overlay, unregistering an overlay, and getting the list of overlays information.
+ * This class provides the functionalities of registering an overlay, unregistering an overlay, and
+ * getting the list of overlays information.
  */
 public class OverlayManagerImpl {
     private static final String TAG = "OverlayManagerImpl";
@@ -235,17 +234,14 @@
         Preconditions.checkArgument(!entryList.isEmpty(), "overlay entries shouldn't be empty");
         final String overlayName = checkOverlayNameValid(overlayInternal.overlayName);
         checkPackageName(overlayInternal.packageName);
-        Preconditions.checkStringNotEmpty(overlayInternal.targetPackageName);
+        checkPackageName(overlayInternal.targetPackageName);
+        Preconditions.checkStringNotEmpty(
+                overlayInternal.targetOverlayable,
+                "Target overlayable should be neither null nor empty string.");
 
         final ApplicationInfo applicationInfo = mContext.getApplicationInfo();
-        String targetPackage = null;
-        if (TextUtils.equals(overlayInternal.targetPackageName, "android")) {
-            targetPackage = AssetManager.FRAMEWORK_APK_PATH;
-        } else {
-            targetPackage = Preconditions.checkStringNotEmpty(
-                    applicationInfo.getBaseCodePath());
-        }
-
+        final String targetPackage = Preconditions.checkStringNotEmpty(
+                applicationInfo.getBaseCodePath());
         final Path frroPath = mBasePath.resolve(overlayName + FRRO_EXTENSION);
         final Path idmapPath = mBasePath.resolve(overlayName + IDMAP_EXTENSION);
 
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 0af4bea..44c0bd0 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -54,6 +54,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -448,7 +449,7 @@
     }
 
     @Override
-    public void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
+    public void onJankDataAvailable(List<SurfaceControl.JankData> jankData) {
         postCallback(() -> {
             try {
                 Trace.beginSection("FrameTracker#onJankDataAvailable");
@@ -832,7 +833,7 @@
         /** adds the jank listener to the given surface */
         public SurfaceControl.OnJankDataListenerRegistration addJankStatsListener(
                 SurfaceControl.OnJankDataListener listener, SurfaceControl surfaceControl) {
-            return surfaceControl.addJankDataListener(listener);
+            return surfaceControl.addOnJankDataListener(listener);
         }
     }
 
diff --git a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index c182b4a..6b6b81f 100644
--- a/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -423,6 +423,8 @@
 
     private Map<String, Boolean> mFeatureFlagState = new ArrayMap<>();
 
+    private int mIntentMatchingFlags;
+
     @NonNull
     public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
             @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp,
@@ -3270,6 +3272,7 @@
         dest.writeLong(this.mBooleans);
         dest.writeLong(this.mBooleans2);
         dest.writeBoolean(this.mAllowCrossUidActivitySwitchFromBelow);
+        dest.writeInt(this.mIntentMatchingFlags);
     }
 
     private void writeFeatureFlagState(@NonNull Parcel dest) {
@@ -3461,6 +3464,7 @@
         this.mBooleans = in.readLong();
         this.mBooleans2 = in.readLong();
         this.mAllowCrossUidActivitySwitchFromBelow = in.readBoolean();
+        this.mIntentMatchingFlags = in.readInt();
 
         assignDerivedFields();
         assignDerivedFields2();
@@ -3699,6 +3703,17 @@
         return this;
     }
 
+    @Override
+    public ParsingPackage setIntentMatchingFlags(int intentMatchingFlags) {
+        mIntentMatchingFlags = intentMatchingFlags;
+        return this;
+    }
+
+    @Override
+    public int getIntentMatchingFlags() {
+        return mIntentMatchingFlags;
+    }
+
     // The following methods are explicitly not inside any interface. These are hidden under
     // PackageImpl which is only accessible to the system server. This is to prevent/discourage
     // usage of these fields outside of the utility classes.
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index a35150b..219e885 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -109,7 +109,8 @@
                             R.styleable.AndroidManifestActivity_process,
                             R.styleable.AndroidManifestActivity_roundIcon,
                             R.styleable.AndroidManifestActivity_splitName,
-                            R.styleable.AndroidManifestActivity_attributionTags);
+                            R.styleable.AndroidManifestActivity_attributionTags,
+                            R.styleable.AndroidManifestActivity_intentMatchingFlags);
             if (result.isError()) {
                 return input.error(result);
             }
@@ -310,7 +311,8 @@
                     NOT_SET /*processAttr*/,
                     R.styleable.AndroidManifestActivityAlias_roundIcon,
                     NOT_SET /*splitNameAttr*/,
-                    R.styleable.AndroidManifestActivityAlias_attributionTags);
+                    R.styleable.AndroidManifestActivityAlias_attributionTags,
+                    R.styleable.AndroidManifestActivityAlias_intentMatchingFlags);
             if (result.isError()) {
                 return input.error(result);
             }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java
index 291ed0c..54e67360 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponent.java
@@ -45,4 +45,9 @@
 
     @Nullable
     String getSplitName();
+
+    /**
+     * Returns the intent matching flags.
+     */
+    int getIntentMatchingFlags();
 }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
index bb8f565..678e999 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
@@ -24,7 +24,6 @@
 import android.os.Parcelable;
 import android.text.TextUtils;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
@@ -34,7 +33,6 @@
  * @hide
  */
 @DataClass(genGetters = true, genSetters = true, genBuilder = false, genParcelable = false)
-@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public class ParsedMainComponentImpl extends ParsedComponentImpl implements ParsedMainComponent,
         Parcelable {
 
@@ -51,6 +49,30 @@
     @Nullable
     private String[] attributionTags;
 
+    private int mIntentMatchingFlags;
+
+    /**
+     * Opt-out of all intent filter matching rules. The value corresponds to the <code>none</code>
+     * value of {@link android.R.attr#intentMatchingFlags}
+     * @hide
+     */
+    public static final int INTENT_MATCHING_FLAGS_NONE = 1;
+
+    /**
+     * Opt-in to enforce intent filter matching. The value corresponds to the
+     * <code>enforceIntentFilter</code> value of {@link android.R.attr#intentMatchingFlags}
+     * @hide
+     */
+    public static final int INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER = 1 << 1;
+
+    /**
+     * Allows intent filters to match actions even when the action value is null. The value
+     * corresponds to the <code>allowNullAction</code> value of
+     * {@link android.R.attr#intentMatchingFlags}
+     * @hide
+     */
+    public static final int INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION = 1 << 2;
+
     public ParsedMainComponentImpl() {
     }
 
@@ -83,6 +105,20 @@
         return attributionTags == null ? EmptyArray.STRING : attributionTags;
     }
 
+    /**
+     * Sets the intent matching flags. This value is intended to be set from the "component" tags.
+     * @see android.R.styleable#AndroidManifestApplication_intentMatchingFlags
+     */
+    public ParsedMainComponent setIntentMatchingFlags(int intentMatchingFlags) {
+        mIntentMatchingFlags = intentMatchingFlags;
+        return this;
+    }
+
+    @Override
+    public int getIntentMatchingFlags() {
+        return this.mIntentMatchingFlags;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -98,6 +134,7 @@
         dest.writeInt(this.order);
         dest.writeString(this.splitName);
         dest.writeString8Array(this.attributionTags);
+        dest.writeInt(this.mIntentMatchingFlags);
     }
 
     protected ParsedMainComponentImpl(Parcel in) {
@@ -109,6 +146,7 @@
         this.order = in.readInt();
         this.splitName = in.readString();
         this.attributionTags = in.createString8Array();
+        this.mIntentMatchingFlags = in.readInt();
     }
 
     public static final Parcelable.Creator<ParsedMainComponentImpl> CREATOR =
@@ -139,6 +177,28 @@
     //@formatter:off
 
 
+    @android.annotation.IntDef(prefix = "INTENT_MATCHING_FLAGS_", value = {
+        INTENT_MATCHING_FLAGS_NONE,
+        INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER,
+        INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION
+    })
+    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface IntentMatchingFlags {}
+
+    @DataClass.Generated.Member
+    public static String intentMatchingFlagsToString(@IntentMatchingFlags int value) {
+        switch (value) {
+            case INTENT_MATCHING_FLAGS_NONE:
+                    return "INTENT_MATCHING_FLAGS_NONE";
+            case INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER:
+                    return "INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER";
+            case INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION:
+                    return "INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION";
+            default: return Integer.toHexString(value);
+        }
+    }
+
     @DataClass.Generated.Member
     public ParsedMainComponentImpl(
             @Nullable String processName,
@@ -147,7 +207,8 @@
             boolean exported,
             int order,
             @Nullable String splitName,
-            @Nullable String[] attributionTags) {
+            @Nullable String[] attributionTags,
+            int intentMatchingFlags) {
         this.processName = processName;
         this.directBootAware = directBootAware;
         this.enabled = enabled;
@@ -155,6 +216,7 @@
         this.order = order;
         this.splitName = splitName;
         this.attributionTags = attributionTags;
+        this.mIntentMatchingFlags = intentMatchingFlags;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -226,10 +288,10 @@
     }
 
     @DataClass.Generated(
-            time = 1701447884766L,
+            time = 1729613643190L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final  android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedMainComponentImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\nprivate  int mIntentMatchingFlags\npublic static final  int INTENT_MATCHING_FLAGS_NONE\npublic static final  int INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER\npublic static final  int INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION\npublic static final  android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedMainComponentImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic  com.android.internal.pm.pkg.component.ParsedMainComponent setIntentMatchingFlags(int)\npublic @java.lang.Override int getIntentMatchingFlags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
index 7e56180..4feb894 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.IntentFilter;
@@ -39,7 +40,8 @@
 import java.io.IOException;
 
 /** @hide */
-class ParsedMainComponentUtils {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class ParsedMainComponentUtils {
 
     private static final String TAG = ParsingUtils.TAG;
 
@@ -47,10 +49,11 @@
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     static <Component extends ParsedMainComponentImpl> ParseResult<Component> parseMainComponent(
             Component component, String tag, String[] separateProcesses, ParsingPackage pkg,
-            TypedArray array, int flags, boolean useRoundIcon,  @Nullable String defaultSplitName,
+            TypedArray array, int flags, boolean useRoundIcon, @Nullable String defaultSplitName,
             @NonNull ParseInput input, int bannerAttr, int descriptionAttr, int directBootAwareAttr,
             int enabledAttr, int iconAttr, int labelAttr, int logoAttr, int nameAttr,
-            int processAttr, int roundIconAttr, int splitNameAttr, int attributionTagsAttr) {
+            int processAttr, int roundIconAttr, int splitNameAttr, int attributionTagsAttr,
+            int intentMatchingFlagsAttr) {
         ParseResult<Component> result = ParsedComponentUtils.parseComponent(component, tag, pkg,
                 array, useRoundIcon, input, bannerAttr, descriptionAttr, iconAttr, labelAttr,
                 logoAttr, nameAttr, roundIconAttr);
@@ -107,6 +110,12 @@
             }
         }
 
+        if (android.security.Flags.enableIntentMatchingFlags()) {
+            int resolvedFlags = resolveIntentMatchingFlags(
+                        pkg.getIntentMatchingFlags(), array.getInt(intentMatchingFlagsAttr, 0));
+            component.setIntentMatchingFlags(resolvedFlags);
+        }
+
         return input.success(component);
     }
 
@@ -147,4 +156,21 @@
         return input.success(intentResult.getResult());
     }
 
+    /**
+     * Resolves intent matching flags from the application and a component, prioritizing the
+     * component's flags.
+     *
+     * @param applicationFlags The flag value from the "application" tag.
+     * @param componentFlags   The flag value from the "component" tags.
+     * @return The resolved intent matching flags.
+     */
+    @FlaggedApi(android.security.Flags.FLAG_ENABLE_INTENT_MATCHING_FLAGS)
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static int resolveIntentMatchingFlags(int applicationFlags, int componentFlags) {
+        if (componentFlags == 0) {
+            return applicationFlags;
+        } else {
+            return componentFlags;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 3726b42..5c39827 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -79,7 +79,8 @@
                             R.styleable.AndroidManifestProvider_process,
                             R.styleable.AndroidManifestProvider_roundIcon,
                             R.styleable.AndroidManifestProvider_splitName,
-                            R.styleable.AndroidManifestProvider_attributionTags);
+                            R.styleable.AndroidManifestProvider_attributionTags,
+                            R.styleable.AndroidManifestProvider_intentMatchingFlags);
             if (result.isError()) {
                 return input.error(result);
             }
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index 9601813..c469a7a 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -74,7 +74,8 @@
                     R.styleable.AndroidManifestService_process,
                     R.styleable.AndroidManifestService_roundIcon,
                     R.styleable.AndroidManifestService_splitName,
-                    R.styleable.AndroidManifestService_attributionTags
+                    R.styleable.AndroidManifestService_attributionTags,
+                    R.styleable.AndroidManifestService_intentMatchingFlags
             );
 
             if (result.isError()) {
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
index 341beed..f4bceb8 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java
@@ -553,4 +553,15 @@
     boolean isNormalScreensSupported();
 
     boolean isSmallScreensSupported();
+
+    /**
+     * Sets the intent matching flags. This value is intended to be set from the "application" tag.
+     * @see android.R.styleable#AndroidManifestApplication_intentMatchingFlags
+     */
+    ParsingPackage setIntentMatchingFlags(int intentMatchingFlags);
+
+    /**
+     * Returns the intent matching flags.
+     */
+    int getIntentMatchingFlags();
 }
diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index bb733f2..5db7b41 100644
--- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -2187,6 +2187,8 @@
                     pkg.setKnownActivityEmbeddingCerts(knownActivityEmbeddingCerts);
                 }
             }
+            pkg.setIntentMatchingFlags(
+                    sa.getInt(R.styleable.AndroidManifestApplication_intentMatchingFlags, 0));
         } finally {
             sa.recycle();
         }
diff --git a/core/java/com/android/internal/telephony/ISatelliteStateChangeListener.aidl b/core/java/com/android/internal/telephony/ISatelliteStateChangeListener.aidl
new file mode 100644
index 0000000..4d195c2
--- /dev/null
+++ b/core/java/com/android/internal/telephony/ISatelliteStateChangeListener.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 com.android.internal.telephony;
+
+oneway interface ISatelliteStateChangeListener {
+    void onSatelliteEnabledStateChanged(boolean isEnabled);
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index ca75abd..1c76a6c 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -38,6 +38,7 @@
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IPhoneStateListener;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
 
 interface ITelephonyRegistry {
     void addOnSubscriptionsChangedListener(String pkg, String featureId,
@@ -124,4 +125,8 @@
     void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
     void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
     void notifyCarrierRoamingNtnAvailableServicesChanged(int subId, in int[] availableServices);
+
+    void addSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg, String featureId);
+    void removeSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg);
+    void notifySatelliteStateChanged(boolean isEnabled);
 }
diff --git a/core/java/com/android/server/FgThread.java b/core/java/com/android/server/FgThread.java
index f54ee5e..f8a6bb0 100644
--- a/core/java/com/android/server/FgThread.java
+++ b/core/java/com/android/server/FgThread.java
@@ -33,6 +33,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class FgThread extends ServiceThread {
     private static final long SLOW_DISPATCH_THRESHOLD_MS = 100;
     private static final long SLOW_DELIVERY_THRESHOLD_MS = 200;
diff --git a/core/java/com/android/server/ServiceThread.java b/core/java/com/android/server/ServiceThread.java
index 0eff1fc..86e507b 100644
--- a/core/java/com/android/server/ServiceThread.java
+++ b/core/java/com/android/server/ServiceThread.java
@@ -27,6 +27,7 @@
  *
  * @hide
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ServiceThread extends HandlerThread {
     private static final String TAG = "ServiceThread";
 
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index d430fe3..5350059 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -1514,4 +1514,10 @@
      * @hide
      */
     boolean isAllowCrossUidActivitySwitchFromBelow();
+
+    /**
+     * Returns the intent matching flags.
+     * @hide
+     */
+    int getIntentMatchingFlags();
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 2bb6e71..2541258 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -76,7 +76,6 @@
         "android_content_res_ApkAssets.cpp",
         "android_os_SystemClock.cpp",
         "android_os_SystemProperties.cpp",
-        "android_os_Trace.cpp",
         "android_text_AndroidCharacter.cpp",
         "android_util_AssetManager.cpp",
         "android_util_EventLog.cpp",
@@ -104,10 +103,6 @@
         "system/media/private/camera/include",
     ],
 
-    shared_libs: [
-        "libtracing_perfetto",
-    ],
-
     static_libs: [
         "libziparchive_for_incfs",
         "libguiflags",
@@ -190,6 +185,7 @@
                 "android_os_ServiceManagerNative.cpp",
                 "android_os_SharedMemory.cpp",
                 "android_os_storage_StorageManager.cpp",
+                "android_os_Trace.cpp",
                 "android_os_UEventObserver.cpp",
                 "android_os_incremental_IncrementalManager.cpp",
                 "android_net_LocalSocketImpl.cpp",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 7fefe17..df87a69 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -298,6 +298,11 @@
     jmethodID ctor;
 } gFrameRateCategoryRateClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID asList;
+} gUtilArrays;
+
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
         case ui::ColorMode::DISPLAY_P3:
@@ -2206,10 +2211,13 @@
             env->SetObjectArrayElement(jJankDataArray, i, jJankData);
             env->DeleteLocalRef(jJankData);
         }
-        env->CallVoidMethod(target,
-                gJankDataListenerClassInfo.onJankDataAvailable,
-                jJankDataArray);
+
+        jobject jJankDataList =
+                env->CallStaticObjectMethod(gUtilArrays.clazz, gUtilArrays.asList, jJankDataArray);
         env->DeleteLocalRef(jJankDataArray);
+
+        env->CallVoidMethod(target, gJankDataListenerClassInfo.onJankDataAvailable, jJankDataList);
+        env->DeleteLocalRef(jJankDataList);
         env->DeleteLocalRef(target);
 
         return true;
@@ -2858,7 +2866,7 @@
     gJankDataListenerClassInfo.clazz = MakeGlobalRefOrDie(env, onJankDataListenerClazz);
     gJankDataListenerClassInfo.onJankDataAvailable =
             GetMethodIDOrDie(env, onJankDataListenerClazz, "onJankDataAvailable",
-                             "([Landroid/view/SurfaceControl$JankData;)V");
+                             "(Ljava/util/List;)V");
 
     jclass transactionCommittedListenerClazz =
             FindClassOrDie(env, "android/view/SurfaceControl$TransactionCommittedListener");
@@ -2933,6 +2941,10 @@
     gStalledTransactionInfoClassInfo.frameNumber =
             GetFieldIDOrDie(env, stalledTransactionInfoClazz, "frameNumber", "J");
 
+    jclass utilArrays = FindClassOrDie(env, "java/util/Arrays");
+    gUtilArrays.clazz = MakeGlobalRefOrDie(env, utilArrays);
+    gUtilArrays.asList = GetStaticMethodIDOrDie(env, utilArrays, "asList",
+                                                "([Ljava/lang/Object;)Ljava/util/List;");
     return err;
 }
 
diff --git a/core/jni/platform/host/HostRuntime.cpp b/core/jni/platform/host/HostRuntime.cpp
index 3747299..7fca117 100644
--- a/core/jni/platform/host/HostRuntime.cpp
+++ b/core/jni/platform/host/HostRuntime.cpp
@@ -87,7 +87,6 @@
 extern int register_android_os_Parcel(JNIEnv* env);
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv* env);
-extern int register_android_os_Trace(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv* env);
 extern int register_android_util_EventLog(JNIEnv* env);
 extern int register_android_util_Log(JNIEnv* env);
@@ -133,7 +132,6 @@
 #endif
         {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
         {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
-        {"android.os.Trace", REG_JNI(register_android_os_Trace)},
         {"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)},
         {"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
         {"android.util.Log", REG_JNI(register_android_util_Log)},
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 97682c8..0479318 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8535,10 +8535,26 @@
         @hide
     -->
     <permission
-        android:name="android.permission.RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
+        android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
         android:protectionLevel="internal"
         android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
 
+    <uses-permission
+        android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
+        android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
+
+    <!-- @SystemApi
+        @FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing")
+        This permission is required when accessing information related to
+        singleUser-ed TIS session.
+        <p>This should only be used by OEM.
+        <p>Protection level: signature|privileged|vendorPrivileged
+        @hide
+    -->
+    <permission android:name="android.permission.SINGLE_USER_TIS_ACCESS"
+        android:protectionLevel="signature|privileged|vendorPrivileged"
+        android:featureFlag="android.media.tv.flags.kids_mode_tvdb_sharing"/>
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index ef420eb..3fac736 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -1943,8 +1943,7 @@
     <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string>
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
-    <!-- no translation found for zen_mode_trigger_summary_range_words (7228261413029290750) -->
-    <skip />
+    <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> to <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Any calendar"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> is muting some sounds"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"There\'s an internal problem with your device, and it may be unstable until you factory data reset."</string>
@@ -2426,14 +2425,10 @@
     <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Send and receive messages without a mobile or Wi-Fi network"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Open Messages"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"How it works"</string>
-    <!-- no translation found for satellite_manual_selection_state_popup_title (8545991934926661974) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_message (1928101658551382450) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_ok (2459664752624985095) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_cancel (973605633339469252) -->
-    <skip />
+    <string name="satellite_manual_selection_state_popup_title" msgid="8545991934926661974">"Turn on \"Automatically select network\""</string>
+    <string name="satellite_manual_selection_state_popup_message" msgid="1928101658551382450">"Turn on \"Automatically select network\" in Settings so your phone can find a network that works with satellite"</string>
+    <string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Turn on"</string>
+    <string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Go back"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pending..."</string>
     <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Set up Fingerprint Unlock again"</string>
     <string name="fingerprint_dangling_notification_msg_1" msgid="5851784577768803510">"<xliff:g id="FINGERPRINT">%s</xliff:g> can no longer be recognized."</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 051c61a..4070f11 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1944,8 +1944,7 @@
     <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Off"</string>
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> - <xliff:g id="END">%2$s</xliff:g>"</string>
-    <!-- no translation found for zen_mode_trigger_summary_range_words (7228261413029290750) -->
-    <skip />
+    <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"Da <xliff:g id="START">%1$s</xliff:g> a <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualsiasi calendario"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> sta disattivando alcuni suoni"</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Si è verificato un problema interno con il dispositivo, che potrebbe essere instabile fino al ripristino dei dati di fabbrica."</string>
@@ -2427,14 +2426,10 @@
     <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Invia e ricevi messaggi senza una rete mobile o Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Apri Messaggi"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Come funziona"</string>
-    <!-- no translation found for satellite_manual_selection_state_popup_title (8545991934926661974) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_message (1928101658551382450) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_ok (2459664752624985095) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_cancel (973605633339469252) -->
-    <skip />
+    <string name="satellite_manual_selection_state_popup_title" msgid="8545991934926661974">"Attiva \"Seleziona rete automaticamente\""</string>
+    <string name="satellite_manual_selection_state_popup_message" msgid="1928101658551382450">"Attiva \"Seleziona rete automaticamente\" nelle Impostazioni in modo che lo smartphone possa trovare una rete compatibile con il satellite"</string>
+    <string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Attiva"</string>
+    <string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Indietro"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"In attesa…"</string>
     <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Riconfigura lo Sblocco con l\'Impronta"</string>
     <string name="fingerprint_dangling_notification_msg_1" msgid="5851784577768803510">"<xliff:g id="FINGERPRINT">%s</xliff:g> non può più essere riconosciuto."</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 2fea962..5684d78 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -590,7 +590,7 @@
     <string name="permdesc_changeWifiMulticastState" product="default" msgid="8296627590220222740">"तपाईँको फोन मात्र होइन, मल्टिकास्ट ठेगानाहरूको प्रयोग गरे Wi-Fi नेटवर्कका सबै उपकरणहरूमा पठाइएका प्याकेटहरू प्राप्त गर्न एपलाई अनुमति दिन्छ। यसले गैर-मल्टिकास्ट मोडभन्दा बढी उर्जा प्रयोग गर्छ।"</string>
     <string name="permlab_bluetoothAdmin" msgid="6490373569441946064">"ब्लुटुथ सेटिङहरूमा पहुँच गर्नुहोस्"</string>
     <string name="permdesc_bluetoothAdmin" product="tablet" msgid="5370837055438574863">"स्थानीय ब्लुटुथ ट्याब्लेटलाई कन्फिगर गर्नको लागि र टाढाका उपकरणहरूलाई पत्ता लगाउन र जोड्नको लागि एपलाई अनुमति दिन्छ।"</string>
-    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"एपलाई तपाईंको Android टिभी डिभाइसको ब्लुटुथ कन्फिगर गर्ने तथा टाढा रहेका यन्त्रहरू पत्ता लगाई ती यन्त्रहरूसँग जोडा बनाउने अनुमति दिन्छ।"</string>
+    <string name="permdesc_bluetoothAdmin" product="tv" msgid="1623992984547014588">"एपलाई तपाईंको Android टिभी डिभाइसको ब्लुटुथ कन्फिगर गर्ने तथा टाढा रहेका यन्त्रहरू पत्ता लगाई ती यन्त्रहरूसँग कनेक्ट गर्ने अनुमति दिन्छ।"</string>
     <string name="permdesc_bluetoothAdmin" product="default" msgid="7381341743021234863">"एपलाई स्थानीय ब्लुटुथ फोन कन्फिगर गर्न र टाढाका उपकरणहरूसँग खोज गर्न र जोडी गर्न अनुमति दिन्छ।"</string>
     <string name="permlab_accessWimaxState" msgid="7029563339012437434">"WiMAXसँग जोड्नुहोस् वा छुटाउनुहोस्"</string>
     <string name="permdesc_accessWimaxState" msgid="5372734776802067708">"एपलाई वाइम्याक्स सक्षम छ कि छैन र जडान भएको कुनै पनि वाइम्याक्स नेटवर्कहरूको बारेमा जानकारी निर्धारिण गर्न अनुमति दिन्छ।"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 388c5a9..86e0fcd 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1944,8 +1944,7 @@
     <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"Desativada"</string>
     <string name="zen_mode_trigger_summary_divider_text" msgid="7461583466043698862">", "</string>
     <string name="zen_mode_trigger_summary_range_symbol_combination" msgid="1804900738798069619">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
-    <!-- no translation found for zen_mode_trigger_summary_range_words (7228261413029290750) -->
-    <skip />
+    <string name="zen_mode_trigger_summary_range_words" msgid="7228261413029290750">"<xliff:g id="START">%1$s</xliff:g> – <xliff:g id="END">%2$s</xliff:g>"</string>
     <string name="zen_mode_trigger_event_calendar_any" msgid="2086784607921121803">"Qualquer calendário"</string>
     <string name="muted_by" msgid="91464083490094950">"<xliff:g id="THIRD_PARTY">%1$s</xliff:g> está a desativar alguns sons."</string>
     <string name="system_error_wipe_data" msgid="5910572292172208493">"Existe um problema interno no seu dispositivo e pode ficar instável até efetuar uma reposição de dados de fábrica."</string>
@@ -2427,14 +2426,10 @@
     <string name="satellite_notification_manual_summary" msgid="901206289846283445">"Envie e receba mensagens sem uma rede móvel ou Wi-Fi"</string>
     <string name="satellite_notification_open_message" msgid="4149234979688273729">"Abre a app Mensagens"</string>
     <string name="satellite_notification_how_it_works" msgid="3132069321977520519">"Como funciona"</string>
-    <!-- no translation found for satellite_manual_selection_state_popup_title (8545991934926661974) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_message (1928101658551382450) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_ok (2459664752624985095) -->
-    <skip />
-    <!-- no translation found for satellite_manual_selection_state_popup_cancel (973605633339469252) -->
-    <skip />
+    <string name="satellite_manual_selection_state_popup_title" msgid="8545991934926661974">"Ative a opção \"Selecionar rede automaticamente\""</string>
+    <string name="satellite_manual_selection_state_popup_message" msgid="1928101658551382450">"Ative a opção \"Selecionar rede automaticamente\" nas Definições para que o telemóvel possa encontrar uma rede que funcione com o satélite"</string>
+    <string name="satellite_manual_selection_state_popup_ok" msgid="2459664752624985095">"Ativar"</string>
+    <string name="satellite_manual_selection_state_popup_cancel" msgid="973605633339469252">"Retroceder"</string>
     <string name="unarchival_session_app_label" msgid="6811856981546348205">"Pendente…"</string>
     <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configure o Desbloqueio por impressão digital novamente"</string>
     <string name="fingerprint_dangling_notification_msg_1" msgid="5851784577768803510">"Já não é possível reconhecer <xliff:g id="FINGERPRINT">%s</xliff:g>."</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 492dca7..1d9e00c 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1937,7 +1937,7 @@
     <string name="zen_mode_default_weeknights_name" msgid="7902108149994062847">"వారపు రోజుల్లో రాత్రి"</string>
     <string name="zen_mode_default_weekends_name" msgid="4707200272709377930">"వారాంతం"</string>
     <string name="zen_mode_default_events_name" msgid="2280682960128512257">"ఈవెంట్"</string>
-    <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"నిద్ర"</string>
+    <string name="zen_mode_default_every_night_name" msgid="1467765312174275823">"స్లీప్ మోడ్"</string>
     <string name="zen_mode_implicit_trigger_description" msgid="5714956693073007111">"<xliff:g id="APP_NAME">%1$s</xliff:g> ద్వారా మేనేజ్ చేయబడుతోంది"</string>
     <string name="zen_mode_implicit_activated" msgid="2634285680776672994">"ఆన్‌లో ఉంది"</string>
     <string name="zen_mode_implicit_deactivated" msgid="8688441768371501750">"ఆఫ్‌లో ఉంది"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d750ff6..092d2a72 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3486,6 +3486,12 @@
              representation this attribute can be used for providing such. -->
         <attr name="contentDescription" format="string" localization="suggested" />
 
+        <!-- Provides brief supplemental information for the view, such as the purpose of
+             the view when that purpose is not conveyed within its textual representation.
+             This property is used primarily for accessibility. -->
+        <!-- @FlaggedApi("android.view.accessibility.supplemental_description") -->
+        <attr name="supplementalDescription" format="string" localization="suggested" />
+
         <!-- Sets the id of a view that screen readers are requested to visit after this view.
              Requests that a screen-reader visits the content of this view before the content of the
              one it precedes. This does nothing if either view is not important for accessibility.
@@ -9238,6 +9244,8 @@
             <flag name="home_screen" value="0x1" />
             <flag name="keyguard" value="0x2" />
             <flag name="searchbox" value="0x4" />
+            <!-- @FlaggedApi("android.appwidget.flags.not_keyguard_category") -->
+            <flag name="not_keyguard" value="0x8" />
         </attr>
         <!-- Flags indicating various features supported by the widget. These are hints to the
          widget host, and do not actually change the behavior of the widget. -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 0be33c2..4d73f22 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1876,6 +1876,34 @@
          @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
     <attr name="featureFlag" format="string" />
 
+    <!-- This attribute provides a way to fine-tune how incoming intents are matched to application
+    components. By default, no special matching rules are applied. This attribute can be specified
+    on the {@code <application>} tag as well as at the component tags such as {@code <activity>},
+    {@code <activity-alias>}, {@code <receiver>}, {@code <service>}, {@code <provider>} and the
+    attribute on the component can be used to override what's on the {@code <application>} tag. -->
+    <attr name="intentMatchingFlags">
+        <!-- Disables all special matching rules for incoming intents. When specifying multiple
+        flags, conflicting values are resolved by giving precedence to the "none" flag. -->
+        <flag name="none" value="0x0001" />
+
+        <!-- Enforces stricter matching for incoming intents:
+        <ul>
+             <li>Explicit intents should match the target component's intent filter
+             <li>Intents without an action should not match any intent filter
+        </ul>
+        -->
+        <flag name="enforceIntentFilter" value="0x0002" />
+
+        <!-- Relaxes the matching rules to allow intents without a action to match. This flag to be
+        used in conjunction with enforceIntentFilter to achieve a specific behavior:
+        <ul>
+            <li>Explicit intents should match the target component's intent filter
+            <li>Intents without an action are allowed to match any intent filter
+        </ul>
+        -->
+        <flag name="allowNullAction" value="0x0004" />
+    </attr>
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
@@ -2243,6 +2271,8 @@
              {@link android.window.OnBackInvokedCallback#onBackInvoked
              OnBackInvokedCallback.onBackInvoked()} on the focused window. -->
         <attr name="enableOnBackInvokedCallback" format="boolean"/>
+
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- An attribution is a logical part of an app and is identified by a tag.
@@ -2930,6 +2960,7 @@
              contained here. -->
         <attr name="attributionTags" />
         <attr name="systemUserOnly" format="boolean" />
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- Attributes that can be supplied in an AndroidManifest.xml
@@ -3089,6 +3120,7 @@
         -->
         <attr name="allowSharedIsolatedProcess" format="boolean" />
         <attr name="systemUserOnly" format="boolean" />
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- @hide The <code>apex-system-service</code> tag declares an apex system service
@@ -3156,6 +3188,7 @@
              Context.createAttributionContext() using the first attribution tag
              contained here. -->
         <attr name="attributionTags" />
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- The <code>activity</code> tag declares an
@@ -3350,6 +3383,7 @@
                  URIs. -->
             <enum name="readAndWrite" value="4" />
         </attr>
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- The <code>activity-alias</code> tag declares a new
@@ -3392,6 +3426,7 @@
         <attr name="attributionTags" />
         <attr name="allowUntrustedActivityEmbedding" />
         <attr name="knownActivityEmbeddingCerts" />
+        <attr name="intentMatchingFlags"/>
     </declare-styleable>
 
     <!-- The <code>meta-data</code> tag is used to attach additional
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ac9bb93..7402a2f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3087,6 +3087,12 @@
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
+    <!-- Indicates the boot strategy in Headless System User Mode (HSUM)
+         This config has no effect if the device is not in HSUM.
+         0 (Default) : boot to the previous foreground user if there is one, otherwise the first switchable user.
+         1 : boot to the first switchable full user for initial boot (unprovisioned device), else to the headless system user, i.e. user 0. -->
+    <integer name="config_hsumBootStrategy">0</integer>
+
     <!-- Whether to boot system with the headless system user, i.e. user 0. If set to true,
          system will be booted with the headless system user, or user 0. It has no effect if device
          is not in Headless System User Mode (HSUM). -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 8121545..0c28ea4 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -121,6 +121,10 @@
     <!-- @FlaggedApi("android.permission.flags.replace_body_sensor_permission_enabled")
          @hide @SystemApi -->
     <public name="backgroundPermission"/>
+    <!-- @FlaggedApi(android.view.accessibility.supplemental_description) -->
+    <public name="supplementalDescription"/>
+    <!-- @FlaggedApi("android.security.enable_intent_matching_flags") -->
+    <public name="intentMatchingFlags"/>
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01b60000">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 515ebd5..b7cb198 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -312,6 +312,7 @@
   <java-symbol type="bool" name="config_disableLockscreenByDefault" />
   <java-symbol type="bool" name="config_enableBurnInProtection" />
   <java-symbol type="bool" name="config_hotswapCapable" />
+  <java-symbol type="integer" name="config_hsumBootStrategy" />
   <java-symbol type="bool" name="config_mms_content_disposition_support" />
   <java-symbol type="bool" name="config_networkSamplingWakesDevice" />
   <java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index c2d8f91..da1fffa 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -17,6 +17,9 @@
 package android.app;
 
 import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
+import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH;
+import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
+import static android.app.PropertyInvalidatedCache.MODULE_TEST;
 import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDEX;
 import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
 
@@ -27,6 +30,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.PropertyInvalidatedCache.Args;
+import android.annotation.SuppressLint;
 import com.android.internal.os.ApplicationSharedMemory;
 
 import android.platform.test.annotations.IgnoreUnderRavenwood;
@@ -57,7 +62,7 @@
             DeviceFlagsValueProvider.createCheckFlagsRule();
 
     // Configuration for creating caches
-    private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
+    private static final String MODULE = MODULE_TEST;
     private static final String API = "testApi";
 
     // This class is a proxy for binder calls.  It contains a counter that increments
@@ -245,6 +250,12 @@
             mQuery = query;
         }
 
+        // Create a cache from the args.  The name of the cache is the api.
+        TestCache(Args args, TestQuery query) {
+            super(args, args.mApi(), query);
+            mQuery = query;
+        }
+
         public int getRecomputeCount() {
             return mQuery.getRecomputeCount();
         }
@@ -374,14 +385,11 @@
     @Test
     public void testPropertyNames() {
         String n1;
-        n1 = PropertyInvalidatedCache.createPropertyName(
-            PropertyInvalidatedCache.MODULE_SYSTEM, "getPackageInfo");
+        n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "getPackageInfo");
         assertEquals(n1, "cache_key.system_server.get_package_info");
-        n1 = PropertyInvalidatedCache.createPropertyName(
-            PropertyInvalidatedCache.MODULE_SYSTEM, "get_package_info");
+        n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "get_package_info");
         assertEquals(n1, "cache_key.system_server.get_package_info");
-        n1 = PropertyInvalidatedCache.createPropertyName(
-            PropertyInvalidatedCache.MODULE_BLUETOOTH, "getState");
+        n1 = PropertyInvalidatedCache.createPropertyName(MODULE_BLUETOOTH, "getState");
         assertEquals(n1, "cache_key.bluetooth.get_state");
     }
 
@@ -391,7 +399,7 @@
             reason = "SystemProperties doesn't have permission check")
     public void testPermissionFailure() {
         // Create a cache that will write a system nonce.
-        TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1");
+        TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
         try {
             // Invalidate the cache, which writes the system property.  There must be a permission
             // failure.
@@ -407,7 +415,7 @@
     @Test
     public void testTestMode() {
         // Create a cache that will write a system nonce.
-        TestCache sysCache = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode1");
+        TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1");
 
         sysCache.testPropertyName();
         // Invalidate the cache.  This must succeed because the property has been marked for
@@ -416,7 +424,7 @@
 
         // Create a cache that uses MODULE_TEST.  Invalidation succeeds whether or not the
         // property is tagged as being tested.
-        TestCache testCache = new TestCache(PropertyInvalidatedCache.MODULE_TEST, "mode2");
+        TestCache testCache = new TestCache(MODULE_TEST, "mode2");
         testCache.invalidateCache();
         testCache.testPropertyName();
         testCache.invalidateCache();
@@ -432,7 +440,7 @@
             // The expected exception.
         }
         // Configuring a property for testing must fail if test mode is false.
-        TestCache cache2 = new TestCache(PropertyInvalidatedCache.MODULE_SYSTEM, "mode3");
+        TestCache cache2 = new TestCache(MODULE_SYSTEM, "mode3");
         try {
             cache2.testPropertyName();
             fail("expected an IllegalStateException");
@@ -444,6 +452,34 @@
         PropertyInvalidatedCache.setTestMode(true);
     }
 
+    // Test the Args-style constructor.
+    @Test
+    public void testArgsConstructor() {
+        // Create a cache with a maximum of four entries.
+        TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4),
+                new TestQuery());
+
+        cache.invalidateCache();
+        for (int i = 1; i <= 4; i++) {
+            assertEquals("foo" + i, cache.query(i));
+            assertEquals(i, cache.getRecomputeCount());
+        }
+        // Everything is in the cache.  The recompute count must not increase.
+        for (int i = 1; i <= 4; i++) {
+            assertEquals("foo" + i, cache.query(i));
+            assertEquals(4, cache.getRecomputeCount());
+        }
+        // Overflow the max entries.  The recompute count increases by one.
+        assertEquals("foo5", cache.query(5));
+        assertEquals(5, cache.getRecomputeCount());
+        // The oldest entry (1) has been evicted.  Iterating through the first four entries will
+        // sequentially evict them all because the loop is proceeding oldest to newest.
+        for (int i = 1; i <= 4; i++) {
+            assertEquals("foo" + i, cache.query(i));
+            assertEquals(5+i, cache.getRecomputeCount());
+        }
+    }
+
     // Verify the behavior of shared memory nonce storage.  This does not directly test the cache
     // storing nonces in shared memory.
     @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
@@ -495,4 +531,43 @@
 
         shmem.close();
     }
+
+    // Verify that an invalid module causes an exception.
+    private void testInvalidModule(String module) {
+        try {
+            @SuppressLint("UnusedVariable")
+            Args arg = new Args(module);
+            fail("expected an invalid module exception: module=" + module);
+        } catch (IllegalArgumentException e) {
+            // Expected exception.
+        }
+    }
+
+    // Test various instantiation errors.  The good path is tested in other methods.
+    @Test
+    public void testArgumentErrors() {
+        // Verify that an illegal module throws an exception.
+        testInvalidModule(MODULE_SYSTEM.substring(0, MODULE_SYSTEM.length() - 1));
+        testInvalidModule(MODULE_SYSTEM + "x");
+        testInvalidModule("mymodule");
+
+        // Verify that a negative max entries throws.
+        Args arg = new Args(MODULE_SYSTEM);
+        try {
+            arg.maxEntries(0);
+            fail("expected an invalid maxEntries exception");
+        } catch (IllegalArgumentException e) {
+            // Expected exception.
+        }
+
+        // Verify that creating a cache with an invalid property string throws.
+        try {
+            final String badKey = "cache_key.volume_list";
+            @SuppressLint("UnusedVariable")
+            var cache = new PropertyInvalidatedCache<Integer, Void>(4, badKey);
+            fail("expected bad property exception: prop=" + badKey);
+        } catch (IllegalArgumentException e) {
+            // Expected exception.
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 930e03d..3b0eab4 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
     // The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
     // See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
     // and assertAccessibilityNodeInfoCleared in that class.
-    private static final int NUM_MARSHALLED_PROPERTIES = 46;
+    private static final int NUM_MARSHALLED_PROPERTIES = 47;
 
     /**
      * The number of properties that are purposely not marshalled
@@ -58,7 +58,7 @@
 
     // The number of flags held in boolean properties. Their values should also be double-checked
     // in the methods above.
-    private static final int NUM_BOOLEAN_PROPERTIES = 27;
+    private static final int NUM_BOOLEAN_PROPERTIES = 28;
 
     @Test
     public void testStandardActions_serializationFlagIsValid() {
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 60b5a42..e7a6cb7 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -67,6 +67,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
 
+import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 
 @SmallTest
@@ -690,10 +691,9 @@
             FrameTracker tracker, long durationMillis, long vsyncId, @JankType int jankType) {
         final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
         doNothing().when(tracker).postCallback(captor.capture());
-        mListenerCapture.getValue().onJankDataAvailable(new JankData[] {
-                new JankData(vsyncId, jankType, FRAME_TIME_60Hz, FRAME_TIME_60Hz,
-                TimeUnit.MILLISECONDS.toNanos(durationMillis))
-        });
+        mListenerCapture.getValue().onJankDataAvailable(Arrays.asList(new JankData(
+                vsyncId, jankType, FRAME_TIME_60Hz, FRAME_TIME_60Hz,
+                TimeUnit.MILLISECONDS.toNanos(durationMillis))));
         captor.getValue().run();
     }
 }
diff --git a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
index 28d6545..40d0bef 100644
--- a/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
+++ b/core/tests/overlaytests/device_self_targeting/src/com/android/overlaytest/OverlayManagerImplTest.java
@@ -210,6 +210,21 @@
     }
 
     @Test
+    public void registerOverlay_forAndroidPackage_shouldFail() {
+        FabricatedOverlayInternal overlayInternal =
+                createOverlayWithName(
+                        mOverlayName,
+                        SYSTEM_APP_OVERLAYABLE,
+                        "android",
+                        List.of(Pair.create("color/white", Pair.create(null, Color.BLACK))));
+
+        assertThrows(
+                "Wrong target package name",
+                IllegalArgumentException.class,
+                () -> mOverlayManagerImpl.registerFabricatedOverlay(overlayInternal));
+    }
+
+    @Test
     public void getOverlayInfosForTarget_defaultShouldBeZero() {
         List<OverlayInfo> overlayInfos =
                 mOverlayManagerImpl.getOverlayInfosForTarget(mContext.getPackageName());
diff --git a/core/tests/packagemanagertests/Android.bp b/core/tests/packagemanagertests/Android.bp
index 8ff4998..c2713ad 100644
--- a/core/tests/packagemanagertests/Android.bp
+++ b/core/tests/packagemanagertests/Android.bp
@@ -16,6 +16,7 @@
         "androidx.test.rules",
         "frameworks-base-testutils",
         "mockito-target-minus-junit4",
+        "platform-test-annotations",
     ],
     libs: ["android.test.runner.stubs.system"],
     platform_apis: true,
diff --git a/core/tests/packagemanagertests/src/com/android/internal/pm/pkg/component/ParsedMainComponentUtilsTest.java b/core/tests/packagemanagertests/src/com/android/internal/pm/pkg/component/ParsedMainComponentUtilsTest.java
new file mode 100644
index 0000000..23bcb71
--- /dev/null
+++ b/core/tests/packagemanagertests/src/com/android/internal/pm/pkg/component/ParsedMainComponentUtilsTest.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.pm.pkg.component;
+
+import static android.security.Flags.FLAG_ENABLE_INTENT_MATCHING_FLAGS;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+public class ParsedMainComponentUtilsTest {
+
+    private final Map<String, Integer> mStringToFlagMap = new HashMap<>();
+
+    {
+        mStringToFlagMap.put("none", ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_NONE);
+        mStringToFlagMap.put("enforceIntentFilter",
+                ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER);
+        mStringToFlagMap.put("allowNullAction",
+                ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(FLAG_ENABLE_INTENT_MATCHING_FLAGS)
+    public void testResolveIntentMatchingFlags() {
+        assertResolution("", "", "");
+        assertResolution("none", "none", "none");
+
+        assertResolution("none", "enforceIntentFilter", "enforceIntentFilter");
+        assertResolution("enforceIntentFilter", "none", "none");
+        assertResolution("enforceIntentFilter|allowNullAction", "none",
+                "none");
+        assertResolution("enforceIntentFilter|allowNullAction", "enforceIntentFilter",
+                "enforceIntentFilter");
+
+        assertResolution("none", "", "none");
+        assertResolution("enforceIntentFilter", "", "enforceIntentFilter");
+        assertResolution("enforceIntentFilter|allowNullAction", "",
+                "enforceIntentFilter|allowNullAction");
+
+        assertResolution("", "none", "none");
+        assertResolution("", "enforceIntentFilter", "enforceIntentFilter");
+        assertResolution("", "enforceIntentFilter|allowNullAction",
+                "enforceIntentFilter|allowNullAction");
+    }
+
+    private void assertResolution(String applicationStringFlags, String componentStringFlags,
+            String expectedStringFlags) {
+        int applicationFlag = stringToFlag(applicationStringFlags);
+        int componentFlag = stringToFlag(componentStringFlags);
+
+        int expectedFlag = stringToFlag(expectedStringFlags);
+        int resolvedFlag = ParsedMainComponentUtils.resolveIntentMatchingFlags(applicationFlag,
+                componentFlag);
+
+        assertEquals(expectedFlag, resolvedFlag);
+    }
+
+    private int stringToFlag(String flags) {
+        int result = 0;
+        String[] flagList = flags.split("\\|");
+        for (String flag : flagList) {
+            String trimmedFlag = flag.trim();
+            result |= mStringToFlagMap.getOrDefault(trimmedFlag, 0);
+        }
+        return result;
+    }
+}
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index beb6985..1cd1190 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -425,6 +425,15 @@
                 .build();
 
         assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
+
+        effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 60)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40)
+                .build();
+
+        assertNull(effect.computeCreateWaveformOffOnTimingsOrNull());
     }
 
     @Test
@@ -643,6 +652,14 @@
                 .build()
                 .validate();
 
+        VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 50)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 80)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 40)
+                .build()
+                .validate();
+
         VibrationEffect.createRepeatingEffect(
                 /*preamble=*/ VibrationEffect.startWaveformEnvelope()
                         .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f,
@@ -693,6 +710,34 @@
                                 /*timeMillis=*/ 0)
                         .build()
                         .validate());
+
+        assertThrows(IllegalStateException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .build().validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .addControlPoint(/*amplitude=*/ -1.0f, /*frequencyHz=*/ 60f,
+                                /*timeMillis=*/ 20)
+                        .build()
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .addControlPoint(/*amplitude=*/ 1.1f, /*frequencyHz=*/ 60f,
+                                /*timeMillis=*/ 20)
+                        .build()
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 0f,
+                                /*timeMillis=*/ 20)
+                        .build()
+                        .validate());
+        assertThrows(IllegalArgumentException.class,
+                () -> VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/30)
+                        .addControlPoint(/*amplitude=*/ 0.8f, /*frequencyHz=*/ 100f,
+                                /*timeMillis=*/ 0)
+                        .build()
+                        .validate());
     }
 
     @Test
@@ -1331,6 +1376,11 @@
                 .addTransition(Duration.ofMillis(500), targetAmplitude(0))
                 .build()
                 .isHapticFeedbackCandidate());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testIsHapticFeedbackCandidate_longEnvelopeEffects_notCandidates() {
         assertFalse(VibrationEffect.startWaveformEnvelope()
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
@@ -1338,6 +1388,13 @@
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
                 .build()
                 .isHapticFeedbackCandidate());
+        assertFalse(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 40)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 200)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 800)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
+                .build()
+                .isHapticFeedbackCandidate());
     }
 
     @Test
@@ -1351,12 +1408,23 @@
                 .addTransition(Duration.ofMillis(300), targetAmplitude(0))
                 .build()
                 .isHapticFeedbackCandidate());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void testIsHapticFeedbackCandidate_shortEnvelopeEffects_areCandidates() {
         assertTrue(VibrationEffect.startWaveformEnvelope()
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100)
                 .build()
                 .isHapticFeedbackCandidate());
+        assertTrue(VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 500)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 400)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 100)
+                .build()
+                .isHapticFeedbackCandidate());
     }
 
     @Test
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 211f74a..03a8b30 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -279,7 +279,8 @@
          * @hide
          */
         default void positionChanged(long frameNumber, int left, int top, int right, int bottom,
-                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+                int clipLeft, int clipTop, int clipRight, int clipBottom,
+                int nodeWidth, int nodeHeight) {
             positionChanged(frameNumber, left, top, right, bottom);
         }
 
@@ -304,11 +305,12 @@
          * @hide */
         static boolean callPositionChanged2(WeakReference<PositionUpdateListener> weakListener,
                 long frameNumber, int left, int top, int right, int bottom,
-                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+                int clipLeft, int clipTop, int clipRight, int clipBottom,
+                int nodeWidth, int nodeHeight) {
             final PositionUpdateListener listener = weakListener.get();
             if (listener != null) {
                 listener.positionChanged(frameNumber, left, top, right, bottom, clipLeft,
-                        clipTop, clipRight, clipBottom);
+                        clipTop, clipRight, clipBottom, nodeWidth, nodeHeight);
                 return true;
             } else {
                 return false;
@@ -401,10 +403,11 @@
 
         @Override
         public void positionChanged(long frameNumber, int left, int top, int right, int bottom,
-                int clipLeft, int clipTop, int clipRight, int clipBottom) {
+                int clipLeft, int clipTop, int clipRight, int clipBottom,
+                int nodeWidth, int nodeHeight) {
             for (PositionUpdateListener pul : mListeners) {
                 pul.positionChanged(frameNumber, left, top, right, bottom, clipLeft, clipTop,
-                        clipRight, clipBottom);
+                        clipRight, clipBottom, nodeWidth, nodeHeight);
             }
         }
 
diff --git a/graphics/java/android/graphics/RuntimeColorFilter.java b/graphics/java/android/graphics/RuntimeColorFilter.java
new file mode 100644
index 0000000..52724ce
--- /dev/null
+++ b/graphics/java/android/graphics/RuntimeColorFilter.java
@@ -0,0 +1,305 @@
+/*
+ * 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.graphics;
+
+import android.annotation.ColorInt;
+import android.annotation.ColorLong;
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.graphics.hwui.flags.Flags;
+
+
+/**
+ * <p>A {@link RuntimeColorFilter} calculates a per-pixel color based on the output of a user
+ *  * defined Android Graphics Shading Language (AGSL) function.</p>
+ *
+ * <p>This AGSL function takes in an input color to be operated on. This color is in sRGB and the
+ *  * output is also interpreted as sRGB. The AGSL function signature expects a single input
+ *  * of color (packed as a half4 or float4 or vec4).</p>
+ *
+ * <pre class="prettyprint">
+ * vec4 main(half4 in_color);
+ * </pre>
+ */
+@FlaggedApi(Flags.FLAG_RUNTIME_COLOR_FILTERS_BLENDERS)
+public class RuntimeColorFilter extends ColorFilter {
+
+    private String mAgsl;
+
+    /**
+     * Creates a new RuntimeColorFilter.
+     *
+     * @param agsl The text of AGSL color filter program to run.
+     */
+    public RuntimeColorFilter(@NonNull String agsl) {
+        if (agsl == null) {
+            throw new NullPointerException("RuntimeColorFilter requires a non-null AGSL string");
+        }
+        mAgsl = agsl;
+        // call to parent class to register native RuntimeColorFilter
+        // TODO: find way to get super class to create native instance without requiring the storage
+        // of agsl string
+        getNativeInstance();
+
+    }
+    /**
+     * Sets the uniform color value corresponding to this color filter.  If the effect does not have
+     * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
+     * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the color uniform declared in the AGSL program
+     * @param color the provided sRGB color
+     */
+    public void setColorUniform(@NonNull String uniformName, @ColorInt int color) {
+        setUniform(uniformName, Color.valueOf(color).getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to this color filter.  If the effect does not have
+     * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
+     * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the color uniform declared in the AGSL program
+     * @param color the provided sRGB color
+     */
+    public void setColorUniform(@NonNull String uniformName, @ColorLong long color) {
+        Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+        setUniform(uniformName, exSRGB.getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform color value corresponding to this color filter.  If the effect does not have
+     * a uniform with that name or if the uniform is declared with a type other than vec3 or vec4
+     * and corresponding layout(color) annotation then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the color uniform declared in the AGSL program
+     * @param color the provided sRGB color
+     */
+    public void setColorUniform(@NonNull String uniformName, @NonNull Color color) {
+        if (color == null) {
+            throw new NullPointerException("The color parameter must not be null");
+        }
+        Color exSRGB = color.convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
+        setUniform(uniformName, exSRGB.getComponents(), true);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a float or
+     * float[1] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value) {
+        setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a vec2 or
+     * float[2] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2) {
+        setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a vec3 or
+     * float[3] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+            float value3) {
+        setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
+
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a vec4 or
+     * float[4] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+            float value3, float value4) {
+        setFloatUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than a float
+     * (for N=1), vecN, or float[N] where N is the length of the values param then an
+     * IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) {
+        setUniform(uniformName, values, false);
+    }
+
+    private void setFloatUniform(@NonNull String uniformName, float value1, float value2,
+            float value3, float value4, int count) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        nativeUpdateUniforms(getNativeInstance(), uniformName, value1, value2, value3, value4,
+                count);
+    }
+
+    private void setUniform(@NonNull String uniformName, @NonNull float[] values, boolean isColor) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        if (values == null) {
+            throw new NullPointerException("The uniform values parameter must not be null");
+        }
+        nativeUpdateUniforms(getNativeInstance(), uniformName, values, isColor);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an int or int[1]
+     * then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value) {
+        setIntUniform(uniformName, value, 0, 0, 0, 1);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an ivec2 or
+     * int[2] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2) {
+        setIntUniform(uniformName, value1, value2, 0, 0, 2);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an ivec3 or
+     * int[3] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) {
+        setIntUniform(uniformName, value1, value2, value3, 0, 3);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an ivec4 or
+     * int[4] then an IllegalArgumentException is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2,
+            int value3, int value4) {
+        setIntUniform(uniformName, value1, value2, value3, value4, 4);
+    }
+
+    /**
+     * Sets the uniform value corresponding to this color filter.  If the effect does not have a
+     * uniform with that name or if the uniform is declared with a type other than an int (for N=1),
+     * ivecN, or int[N] where N is the length of the values param then an IllegalArgumentException
+     * is thrown.
+     *
+     * @param uniformName name matching the uniform declared in the AGSL program
+     */
+    public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        if (values == null) {
+            throw new NullPointerException("The uniform values parameter must not be null");
+        }
+        nativeUpdateUniforms(getNativeInstance(), uniformName, values);
+    }
+
+    private void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3,
+            int value4, int count) {
+        if (uniformName == null) {
+            throw new NullPointerException("The uniformName parameter must not be null");
+        }
+        nativeUpdateUniforms(getNativeInstance(), uniformName, value1, value2, value3, value4,
+                count);
+    }
+
+    /**
+     * Assigns the uniform shader to the provided shader parameter.  If the shader program does not
+     * have a uniform shader with that name then an IllegalArgumentException is thrown.
+     *
+     * @param shaderName name matching the uniform declared in the AGSL program
+     * @param shader shader passed into the AGSL program for sampling
+     */
+    public void setInputShader(@NonNull String shaderName, @NonNull Shader shader) {
+        if (shaderName == null) {
+            throw new NullPointerException("The shaderName parameter must not be null");
+        }
+        if (shader == null) {
+            throw new NullPointerException("The shader parameter must not be null");
+        }
+        nativeUpdateChild(getNativeInstance(), shaderName, shader.getNativeInstance());
+    }
+
+    /**
+     * Assigns the uniform color filter to the provided color filter parameter.  If the shader
+     * program does not have a uniform color filter with that name then an IllegalArgumentException
+     * is thrown.
+     *
+     * @param filterName name matching the uniform declared in the AGSL program
+     * @param colorFilter filter passed into the AGSL program for sampling
+     */
+    public void setInputColorFilter(@NonNull String filterName, @NonNull ColorFilter colorFilter) {
+        if (filterName == null) {
+            throw new NullPointerException("The filterName parameter must not be null");
+        }
+        if (colorFilter == null) {
+            throw new NullPointerException("The colorFilter parameter must not be null");
+        }
+        nativeUpdateChild(getNativeInstance(), filterName, colorFilter.getNativeInstance());
+    }
+
+    /** @hide */
+    @Override
+    protected long createNativeInstance() {
+        return nativeCreateRuntimeColorFilter(mAgsl);
+    }
+
+    private static native long nativeCreateRuntimeColorFilter(String agsl);
+    private static native void nativeUpdateUniforms(
+            long colorFilter, String uniformName, float[] uniforms, boolean isColor);
+    private static native void nativeUpdateUniforms(
+            long colorFilter, String uniformName, float value1, float value2, float value3,
+            float value4, int count);
+    private static native void nativeUpdateUniforms(
+            long colorFilter, String uniformName, int[] uniforms);
+    private static native void nativeUpdateUniforms(
+            long colorFilter, String uniformName, int value1, int value2, int value3,
+            int value4, int count);
+    private static native void nativeUpdateChild(long colorFilter, String childName, long child);
+
+}
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 82b3f68..71baed8 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -37,6 +37,7 @@
 import android.util.TypedValue;
 import android.view.View;
 
+import com.android.graphics.hwui.flags.Flags;
 import com.android.internal.R;
 
 import dalvik.annotation.optimization.FastNative;
@@ -525,6 +526,35 @@
         }
     }
 
+    @Override
+    public void setFilterBitmap(boolean filterBitmap) {
+        if (!Flags.animatedImageDrawableFilterBitmap()) {
+            super.setFilterBitmap(filterBitmap);
+            return;
+        }
+        if (mState.mNativePtr == 0) {
+            throw new IllegalStateException(
+              "called setFilterBitmap on empty AnimatedImageDrawable"
+            );
+        }
+        if (nSetFilterBitmap(mState.mNativePtr, filterBitmap)) {
+            invalidateSelf();
+        }
+    }
+
+    @Override
+    public boolean isFilterBitmap() {
+        if (!Flags.animatedImageDrawableFilterBitmap()) {
+            return super.isFilterBitmap();
+        }
+        if (mState.mNativePtr == 0) {
+            throw new IllegalStateException(
+                "called isFilterBitmap on empty AnimatedImageDrawable"
+            );
+        }
+        return nGetFilterBitmap(mState.mNativePtr);
+    }
+
     private void postOnAnimationStart() {
         if (mAnimationCallbacks == null) {
             return;
@@ -618,4 +648,8 @@
     private static native void nSetMirrored(long nativePtr, boolean mirror);
     @FastNative
     private static native void nSetBounds(long nativePtr, Rect rect);
+    @FastNative
+    private static native boolean nSetFilterBitmap(long nativePtr, boolean filterBitmap);
+    @FastNative
+    private static native boolean nGetFilterBitmap(long nativePtr);
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index ad194f7..6398c7a2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -39,6 +39,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
+import android.annotation.ColorInt;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityThread;
@@ -1394,10 +1395,14 @@
         }
 
         private void showVeils(@NonNull SurfaceControl.Transaction t) {
-            final Color primaryVeilColor = getContainerBackgroundColor(
-                    mProperties.mPrimaryContainer, DEFAULT_PRIMARY_VEIL_COLOR);
-            final Color secondaryVeilColor = getContainerBackgroundColor(
-                    mProperties.mSecondaryContainer, DEFAULT_SECONDARY_VEIL_COLOR);
+            final Color primaryVeilColor = getVeilColor(
+                    mProperties.mDividerAttributes.getPrimaryVeilColor(),
+                    mProperties.mPrimaryContainer,
+                    DEFAULT_PRIMARY_VEIL_COLOR);
+            final Color secondaryVeilColor = getVeilColor(
+                    mProperties.mDividerAttributes.getSecondaryVeilColor(),
+                    mProperties.mSecondaryContainer,
+                    DEFAULT_SECONDARY_VEIL_COLOR);
             t.setColor(mPrimaryVeil, colorToFloatArray(primaryVeilColor))
                     .setColor(mSecondaryVeil, colorToFloatArray(secondaryVeilColor))
                     .setLayer(mDividerSurface, DIVIDER_LAYER)
@@ -1444,6 +1449,21 @@
             }
         }
 
+        /**
+         * Returns the veil color.
+         *
+         * If the configured color is not transparent, we use the configured color, otherwise we use
+         * the window background color of the top activity. If the background color of the top
+         * activity is unavailable, the default color is used.
+         */
+        @NonNull
+        private static Color getVeilColor(@ColorInt int configuredColor,
+                @NonNull TaskFragmentContainer container, @NonNull Color defaultColor) {
+            return configuredColor != Color.TRANSPARENT
+                    ? Color.valueOf(configuredColor)
+                    : getContainerBackgroundColor(container, defaultColor);
+        }
+
         private static float[] colorToFloatArray(@NonNull Color color) {
             return new float[]{color.red(), color.green(), color.blue()};
         }
diff --git a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
index 61c09f2..7f54c75 100644
--- a/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
+++ b/libs/WindowManager/Shell/multivalentScreenshotTests/Android.bp
@@ -22,42 +22,6 @@
     default_team: "trendy_team_multitasking_windowing",
 }
 
-android_app {
-    name: "WMShellRobolectricScreenshotTestApp",
-    platform_apis: true,
-    certificate: "platform",
-    static_libs: [
-        "WindowManager-Shell",
-        "platform-screenshot-diff-core",
-        "ScreenshotComposeUtilsLib", // ComposableScreenshotTestRule & Theme.PlatformUi.Screenshot
-        "SystemUI-res", // Theme.SystemUI (dragged in by ScreenshotComposeUtilsLib)
-    ],
-    asset_dirs: ["goldens/robolectric"],
-    manifest: "AndroidManifestRobolectric.xml",
-    use_resource_processor: true,
-}
-
-android_robolectric_test {
-    name: "WMShellRobolectricScreenshotTests",
-    instrumentation_for: "WMShellRobolectricScreenshotTestApp",
-    upstream: true,
-    java_resource_dirs: [
-        "robolectric/config",
-    ],
-    srcs: [
-        "src/**/*.kt",
-    ],
-    static_libs: [
-        "junit",
-        "androidx.test.runner",
-        "androidx.test.rules",
-        "androidx.test.ext.junit",
-        "truth",
-        "platform-parametric-runner-lib",
-    ],
-    auto_gen_config: true,
-}
-
 android_test {
     name: "WMShellMultivalentScreenshotTestsOnDevice",
     srcs: [
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
index 52ce8cb..0b515f5 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt
@@ -23,14 +23,15 @@
 import android.graphics.Color
 import android.graphics.drawable.Icon
 import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
-import android.view.IWindowManager
 import android.view.WindowManager
-import android.view.WindowManagerGlobal
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.protolog.ProtoLog
 import com.android.launcher3.icons.BubbleIconFactory
@@ -41,6 +42,7 @@
 import com.android.wm.shell.common.FloatingContentCoordinator
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 import com.android.wm.shell.taskview.TaskView
 import com.android.wm.shell.taskview.TaskViewTaskController
 import com.google.common.truth.Truth.assertThat
@@ -51,9 +53,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.mock
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
-import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import org.mockito.kotlin.verify
 import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit
 import java.util.function.Consumer
@@ -72,7 +72,7 @@
     private lateinit var expandedViewManager: FakeBubbleExpandedViewManager
     private lateinit var bubbleStackView: BubbleStackView
     private lateinit var shellExecutor: ShellExecutor
-    private lateinit var windowManager: IWindowManager
+    private lateinit var windowManager: WindowManager
     private lateinit var bubbleTaskViewFactory: BubbleTaskViewFactory
     private lateinit var bubbleData: BubbleData
     private lateinit var bubbleStackViewManager: FakeBubbleStackViewManager
@@ -83,9 +83,8 @@
         PhysicsAnimatorTestUtils.prepareForTest()
         // Disable protolog tool when running the tests from studio
         ProtoLog.REQUIRE_PROTOLOGTOOL = false
-        windowManager = WindowManagerGlobal.getWindowManagerService()!!
         shellExecutor = TestShellExecutor()
-        val windowManager = context.getSystemService(WindowManager::class.java)
+        windowManager = context.getSystemService(WindowManager::class.java)
         iconFactory =
             BubbleIconFactory(
                 context,
@@ -354,6 +353,16 @@
         assertThat(bubbleStackView.getBubbleIndex(bubbleOverflow)).isGreaterThan(-1)
     }
 
+    @Test
+    fun removeFromWindow_stopMonitoringSwipeUpGesture() {
+        spyOn(bubbleStackView)
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            // No way to add to window in the test environment right now so just pretend
+            bubbleStackView.onDetachedFromWindow()
+        }
+        verify(bubbleStackView).stopMonitoringSwipeUpGesture()
+    }
+
     private fun createAndInflateChatBubble(key: String): Bubble {
         val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
         val shortcutInfo = ShortcutInfo.Builder(context, "fakeId").setIcon(icon).build()
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml
new file mode 100644
index 0000000..4442e9d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_change_aspect_ratio.xml
@@ -0,0 +1,25 @@
+<?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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@color/compat_controls_text"
+        android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_promo_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_promo_background.xml
new file mode 100644
index 0000000..645d24d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_education_promo_background.xml
@@ -0,0 +1,22 @@
+<?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.
+  -->
+
+<shape android:shape="rectangle"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <solid android:color="?androidprv:attr/materialColorSurfaceContainerLow" />
+    <corners android:radius="@dimen/desktop_windowing_education_promo_corner_radius" />
+</shape>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index 5609663..f90e165 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -157,6 +157,14 @@
             android:drawableStart="@drawable/desktop_mode_ic_handle_menu_manage_windows"
             android:drawableTint="?androidprv:attr/materialColorOnSurface"
             style="@style/DesktopModeHandleMenuActionButton" />
+
+        <Button
+            android:id="@+id/change_aspect_ratio_button"
+            android:contentDescription="@string/change_aspect_ratio_text"
+            android:text="@string/change_aspect_ratio_text"
+            android:drawableStart="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
+            android:drawableTint="?androidprv:attr/materialColorOnSurface"
+            style="@style/DesktopModeHandleMenuActionButton" />
     </LinearLayout>
 
     <LinearLayout
diff --git a/libs/WindowManager/Shell/res/layout/desktop_windowing_education_promo.xml b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_promo.xml
new file mode 100644
index 0000000..eebfd4b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_windowing_education_promo.xml
@@ -0,0 +1,36 @@
+<?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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/education_container"
+    android:layout_width="@dimen/desktop_windowing_education_promo_width"
+    android:layout_height="@dimen/desktop_windowing_education_promo_height"
+    android:elevation="1dp"
+    android:orientation="vertical"
+    android:paddingHorizontal="32dp"
+    android:paddingVertical="24dp"
+    android:background="@drawable/desktop_windowing_education_promo_background">
+    <TextView
+        android:id="@+id/education_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="24sp"
+        android:lineHeight="32dp"
+        android:textFontWeight="400"
+        android:fontFamily="google-sans-text"
+        android:layout_gravity="center_horizontal"
+        android:gravity="center"/>
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values-ar/strings.xml b/libs/WindowManager/Shell/res/values-ar/strings.xml
index c36783f..a8febc8 100644
--- a/libs/WindowManager/Shell/res/values-ar/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ar/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"تكبير الشاشة إلى أقصى حدّ"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"التقاط صورة للشاشة"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"لا يمكن نقل التطبيق إلى هنا"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"مجسَّم"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"استعادة"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"تكبير"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"استعادة"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"المحاذاة إلى اليسار"</string>
diff --git a/libs/WindowManager/Shell/res/values-as/strings.xml b/libs/WindowManager/Shell/res/values-as/strings.xml
index 71d95ed..8c924e3 100644
--- a/libs/WindowManager/Shell/res/values-as/strings.xml
+++ b/libs/WindowManager/Shell/res/values-as/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্ৰীন মেক্সিমাইজ কৰক"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"স্ক্ৰীন স্নেপ কৰক"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ইয়ালৈ এপ্‌টো আনিব নোৱাৰি"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমাৰ্ছিভ"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"পুনঃস্থাপন কৰক"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"মেক্সিমাইজ কৰক"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"পুনঃস্থাপন কৰক"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"বাওঁফাললৈ স্নেপ কৰক"</string>
diff --git a/libs/WindowManager/Shell/res/values-bn/strings.xml b/libs/WindowManager/Shell/res/values-bn/strings.xml
index 4412497..22a445f 100644
--- a/libs/WindowManager/Shell/res/values-bn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bn/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"স্ক্রিন বড় করুন"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"স্ক্রিনে অ্যাপ মানানসই হিসেবে ছোট বড় করুন"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"অ্যাপটি এখানে সরানো যাবে না"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ইমারসিভ"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ফিরিয়ে আনুন"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"বড় করুন"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ফিরিয়ে আনুন"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"বাঁদিকে স্ন্যাপ করুন"</string>
diff --git a/libs/WindowManager/Shell/res/values-bs/strings.xml b/libs/WindowManager/Shell/res/values-bs/strings.xml
index 674022b..73f30d7 100644
--- a/libs/WindowManager/Shell/res/values-bs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-bs/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiziraj ekran"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snimi ekran"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ne možete premjestiti aplikaciju ovdje"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Interaktivno"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiziranje"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Vraćanje"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pomicanje ulijevo"</string>
diff --git a/libs/WindowManager/Shell/res/values-cs/strings.xml b/libs/WindowManager/Shell/res/values-cs/strings.xml
index fc6b679..6a5780e 100644
--- a/libs/WindowManager/Shell/res/values-cs/strings.xml
+++ b/libs/WindowManager/Shell/res/values-cs/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximalizovat obrazovku"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Rozpůlit obrazovku"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikaci sem nelze přesunout"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Pohlcující"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovit"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximalizovat"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Obnovit"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Přichytit vlevo"</string>
diff --git a/libs/WindowManager/Shell/res/values-el/strings.xml b/libs/WindowManager/Shell/res/values-el/strings.xml
index d06b46f..d02fae2 100644
--- a/libs/WindowManager/Shell/res/values-el/strings.xml
+++ b/libs/WindowManager/Shell/res/values-el/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Μεγιστοποίηση οθόνης"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Προβολή στο μισό της οθόνης"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Δεν είναι δυνατή η μετακίνηση της εφαρμογής εδώ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Καθηλωτικό"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Επαναφορά"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Μεγιστοποίηση"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Επαναφορά"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Κούμπωμα αριστερά"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
index c1a42448..f991145 100644
--- a/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rAU/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
index c1a42448..f991145 100644
--- a/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rGB/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
index c1a42448..f991145 100644
--- a/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-en-rIN/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximise screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Snap screen"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"App can\'t be moved here"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restore"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximise"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restore"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Snap left"</string>
diff --git a/libs/WindowManager/Shell/res/values-hi/strings.xml b/libs/WindowManager/Shell/res/values-hi/strings.xml
index ff675a9..527793e 100644
--- a/libs/WindowManager/Shell/res/values-hi/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hi/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन को बड़ा करें"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्नैप स्क्रीन"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ऐप्लिकेशन को यहां मूव नहीं किया जा सकता"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"वापस लाएं"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"बड़ा करें"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"पहले जैसा करें"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"बाईं ओर स्नैप करें"</string>
diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml
index 16fbcd0..659d1ec 100644
--- a/libs/WindowManager/Shell/res/values-hr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-hr/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimalno povećaj zaslon"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Izradi snimku zaslona"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacija se ne može premjestiti ovdje"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Interaktivno"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Vrati"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiziraj"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Vrati"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Poravnaj lijevo"</string>
diff --git a/libs/WindowManager/Shell/res/values-is/strings.xml b/libs/WindowManager/Shell/res/values-is/strings.xml
index f595765..ca1bc15 100644
--- a/libs/WindowManager/Shell/res/values-is/strings.xml
+++ b/libs/WindowManager/Shell/res/values-is/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Stækka skjá"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Smelluskjár"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Ekki er hægt að færa forritið hingað"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Umlykjandi"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Endurheimta"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Stækka"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Endurheimta"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Smella til vinstri"</string>
diff --git a/libs/WindowManager/Shell/res/values-it/strings.xml b/libs/WindowManager/Shell/res/values-it/strings.xml
index 8b7c15f..87919b5 100644
--- a/libs/WindowManager/Shell/res/values-it/strings.xml
+++ b/libs/WindowManager/Shell/res/values-it/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Massimizza schermo"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Aggancia schermo"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Impossibile spostare l\'app qui"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersivo"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Ripristina"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Ingrandisci"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Ripristina"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Aggancia a sinistra"</string>
diff --git a/libs/WindowManager/Shell/res/values-ja/strings.xml b/libs/WindowManager/Shell/res/values-ja/strings.xml
index 5464cfa..c7a77d9 100644
--- a/libs/WindowManager/Shell/res/values-ja/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ja/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"画面の最大化"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"画面のスナップ"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"アプリはここに移動できません"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"没入モード"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"復元"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"復元"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"左にスナップ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ka/strings.xml b/libs/WindowManager/Shell/res/values-ka/strings.xml
index 7ea9daf..39362ef 100644
--- a/libs/WindowManager/Shell/res/values-ka/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ka/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"აპლიკაციის გაშლა სრულ ეკრანზე"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"აპლიკაციის დაპატარავება ეკრანზე"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"აპის აქ გადატანა შეუძლებელია"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"იმერსიული"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"აღდგენა"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"მაქსიმალურად გაშლა"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"აღდგენა"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"მარცხნივ გადატანა"</string>
diff --git a/libs/WindowManager/Shell/res/values-km/strings.xml b/libs/WindowManager/Shell/res/values-km/strings.xml
index 83bd374..9c4ae05 100644
--- a/libs/WindowManager/Shell/res/values-km/strings.xml
+++ b/libs/WindowManager/Shell/res/values-km/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ពង្រីកអេក្រង់"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ថតអេក្រង់"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"មិនអាចផ្លាស់ទីកម្មវិធីមកទីនេះបានទេ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ជក់ចិត្ត"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ស្ដារ"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ពង្រីក"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ស្ដារ"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ផ្លាស់ទីទៅឆ្វេង"</string>
diff --git a/libs/WindowManager/Shell/res/values-kn/strings.xml b/libs/WindowManager/Shell/res/values-kn/strings.xml
index 502e0c9..f365cfb 100644
--- a/libs/WindowManager/Shell/res/values-kn/strings.xml
+++ b/libs/WindowManager/Shell/res/values-kn/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ಸ್ಕ್ರೀನ್ ಅನ್ನು ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"ಸ್ನ್ಯಾಪ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ಆ್ಯಪ್ ಅನ್ನು ಇಲ್ಲಿಗೆ ಸರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ಇಮ್ಮರ್ಸಿವ್"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"ಮರುಸ್ಥಾಪಿಸಿ"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ಮ್ಯಾಕ್ಸಿಮೈಸ್ ಮಾಡಿ"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"ಮರುಸ್ಥಾಪಿಸಿ"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ಎಡಕ್ಕೆ ಸ್ನ್ಯಾಪ್ ಮಾಡಿ"</string>
diff --git a/libs/WindowManager/Shell/res/values-lt/strings.xml b/libs/WindowManager/Shell/res/values-lt/strings.xml
index 4551f72..5a7f58e 100644
--- a/libs/WindowManager/Shell/res/values-lt/strings.xml
+++ b/libs/WindowManager/Shell/res/values-lt/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Išskleisti ekraną"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Sutraukti ekraną"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Programos negalima perkelti čia"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Įtraukiantis"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Atkurti"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Padidinti"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Atkurti"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pritraukti kairėje"</string>
diff --git a/libs/WindowManager/Shell/res/values-mr/strings.xml b/libs/WindowManager/Shell/res/values-mr/strings.xml
index a2c610e..871bc3f 100644
--- a/libs/WindowManager/Shell/res/values-mr/strings.xml
+++ b/libs/WindowManager/Shell/res/values-mr/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रीन मोठी करा"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्क्रीन स्नॅप करा"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"अ‍ॅप इथे हलवू शकत नाही"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिव्ह"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोअर करा"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"मोठे करा"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"रिस्टोअर करा"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"डावीकडे स्नॅप करा"</string>
diff --git a/libs/WindowManager/Shell/res/values-ms/strings.xml b/libs/WindowManager/Shell/res/values-ms/strings.xml
index 2102a38..71666ca 100644
--- a/libs/WindowManager/Shell/res/values-ms/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ms/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimumkan Skrin"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Tangkap Skrin"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Apl tidak boleh dialihkan ke sini"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Mengasyikkan"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Pulihkan"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimumkan"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Pulihkan"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Autojajar ke kiri"</string>
diff --git a/libs/WindowManager/Shell/res/values-ne/strings.xml b/libs/WindowManager/Shell/res/values-ne/strings.xml
index e9a5306..7015b2c 100644
--- a/libs/WindowManager/Shell/res/values-ne/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ne/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"स्क्रिन ठुलो बनाउनुहोस्"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"स्क्रिन स्न्याप गर्नुहोस्"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"एप सारेर यहाँ ल्याउन सकिएन"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"इमर्सिभ"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"रिस्टोर गर्नुहोस्"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ठुलो बनाउनुहोस्"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"रिस्टोर गर्नुहोस्"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"बायाँतिर स्न्याप गर्नुहोस्"</string>
diff --git a/libs/WindowManager/Shell/res/values-pl/strings.xml b/libs/WindowManager/Shell/res/values-pl/strings.xml
index 14b7a09..5f78b13 100644
--- a/libs/WindowManager/Shell/res/values-pl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pl/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksymalizuj ekran"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Przyciągnij ekran"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Nie można przenieść aplikacji tutaj"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Tryb immersyjny"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Przywróć"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksymalizuj"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Przywróć"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Przyciągnij do lewej"</string>
diff --git a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
index 9631c45..cd78ef9 100644
--- a/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
+++ b/libs/WindowManager/Shell/res/values-pt-rPT/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maximizar ecrã"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Encaixar ecrã"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Não é possível mover a app para aqui"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Envolvente"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Restaurar"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maximizar"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Restaurar"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Encaixar à esquerda"</string>
diff --git a/libs/WindowManager/Shell/res/values-sl/strings.xml b/libs/WindowManager/Shell/res/values-sl/strings.xml
index 2012c8a..ae7e524 100644
--- a/libs/WindowManager/Shell/res/values-sl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-sl/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"Maksimiraj zaslon"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"Pripni zaslon"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Aplikacije ni mogoče premakniti sem"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Poglobljeno"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"Obnovi"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"Maksimiraj"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"Obnovi"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"Pripni levo"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index c4243f7..2c73d3a 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"திரையைப் பெரிதாக்கு"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"திரையை ஸ்னாப் செய்"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ஆப்ஸை இங்கே நகர்த்த முடியாது"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"ஈடுபட வைக்கும்"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"மீட்டெடுக்கும்"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"பெரிதாக்கும்"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"மீட்டெடுக்கும்"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"இடதுபுறம் நகர்த்தும்"</string>
diff --git a/libs/WindowManager/Shell/res/values-te/strings.xml b/libs/WindowManager/Shell/res/values-te/strings.xml
index 47fc48d..b17d4d1 100644
--- a/libs/WindowManager/Shell/res/values-te/strings.xml
+++ b/libs/WindowManager/Shell/res/values-te/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"స్క్రీన్ సైజ్‌ను పెంచండి"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"స్క్రీన్‌ను స్నాప్ చేయండి"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"యాప్‌ను ఇక్కడకి తరలించడం సాధ్యం కాదు"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"లీనమయ్యే"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"రీస్టోర్ చేయండి"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"మ్యాగ్జిమైజ్ చేయండి"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"రీస్టోర్ చేయండి"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"ఎడమ వైపున స్నాప్ చేయండి"</string>
diff --git a/libs/WindowManager/Shell/res/values-th/strings.xml b/libs/WindowManager/Shell/res/values-th/strings.xml
index 2608075..43cee41 100644
--- a/libs/WindowManager/Shell/res/values-th/strings.xml
+++ b/libs/WindowManager/Shell/res/values-th/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"ขยายหน้าจอให้ใหญ่สุด"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"สแนปหน้าจอ"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"ย้ายแอปมาที่นี่ไม่ได้"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"สมจริง"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"คืนค่า"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"ขยายใหญ่สุด"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"คืนค่า"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"จัดพอดีกับทางซ้าย"</string>
diff --git a/libs/WindowManager/Shell/res/values-tl/strings.xml b/libs/WindowManager/Shell/res/values-tl/strings.xml
index 94166ef..4284995 100644
--- a/libs/WindowManager/Shell/res/values-tl/strings.xml
+++ b/libs/WindowManager/Shell/res/values-tl/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"I-maximize ang Screen"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"I-snap ang Screen"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"Hindi mailipat dito ang app"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"Immersive"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"I-restore"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"I-maximize"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"I-restore"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"I-snap pakaliwa"</string>
diff --git a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
index ad50a04..2fb3f5a 100644
--- a/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
+++ b/libs/WindowManager/Shell/res/values-zh-rCN/strings.xml
@@ -133,10 +133,8 @@
     <string name="desktop_mode_maximize_menu_maximize_text" msgid="3275717276171114411">"最大化屏幕"</string>
     <string name="desktop_mode_maximize_menu_snap_text" msgid="2065251022783880154">"屏幕快照"</string>
     <string name="desktop_mode_non_resizable_snap_text" msgid="3771776422751387878">"无法将应用移至此处"</string>
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_button_text (559492223133829481) -->
-    <skip />
-    <!-- no translation found for desktop_mode_maximize_menu_immersive_restore_button_text (4900114367354709257) -->
-    <skip />
+    <string name="desktop_mode_maximize_menu_immersive_button_text" msgid="559492223133829481">"沉浸式"</string>
+    <string name="desktop_mode_maximize_menu_immersive_restore_button_text" msgid="4900114367354709257">"恢复"</string>
     <string name="desktop_mode_maximize_menu_maximize_button_text" msgid="3090199175564175845">"最大化"</string>
     <string name="desktop_mode_maximize_menu_restore_button_text" msgid="4234449220944704387">"恢复"</string>
     <string name="desktop_mode_maximize_menu_snap_left_button_text" msgid="8077452201179893424">"贴靠左侧"</string>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index a14461a..59e6c77 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -186,4 +186,6 @@
 
     <!-- Apps that can trigger Desktop Windowing App handle Education -->
     <string-array name="desktop_windowing_app_handle_education_allowlist_apps"></string-array>
+    <!-- Apps that can trigger Desktop Windowing App-To-Web Education -->
+    <string-array name="desktop_windowing_app_to_web_education_allowlist_apps"></string-array>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index fa1aa19..249e9a2 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -523,8 +523,9 @@
     <dimen name="desktop_mode_handle_menu_width">216dp</dimen>
 
     <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each,
-         additional actions pill 156dp, plus 2dp spacing between them plus 4dp top padding. -->
-    <dimen name="desktop_mode_handle_menu_height">322dp</dimen>
+         additional actions pill 208dp, plus 2dp spacing between them plus 4dp top padding.
+         52*3 + 52*4 + (4-1)*2 + 4 = 374 -->
+    <dimen name="desktop_mode_handle_menu_height">374dp</dimen>
 
     <!-- The elevation set on the handle menu pills. -->
     <dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
@@ -547,6 +548,9 @@
     <!-- The height of the handle menu's "Open in browser" pill in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_open_in_browser_pill_height">52dp</dimen>
 
+    <!-- The height of the handle menu's "Change aspect ratio" pill in desktop mode. -->
+    <dimen name="desktop_mode_handle_menu_change_aspect_ratio_height">52dp</dimen>
+
     <!-- The margin between pills of the handle menu in desktop mode. -->
     <dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
 
@@ -650,4 +654,11 @@
     <dimen name="open_by_default_settings_dialog_radio_button_height">64dp</dimen>
     <!-- The width of radio buttons in the open by default settings dialog. -->
     <dimen name="open_by_default_settings_dialog_radio_button_width">316dp</dimen>
+
+    <!-- The width of the desktop windowing education promo. -->
+    <dimen name="desktop_windowing_education_promo_width">412dp</dimen>
+    <!-- The height of the desktop windowing education promo. -->
+    <dimen name="desktop_windowing_education_promo_height">352dp</dimen>
+    <!-- The corner radius of the desktop windowing education promo. -->
+    <dimen name="desktop_windowing_education_promo_corner_radius">28dp</dimen>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index ef0386a..8f1ef6c 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -305,6 +305,8 @@
     <string name="new_window_text">New Window</string>
     <!-- Accessibility text for the handle menu new window button [CHAR LIMIT=NONE] -->
     <string name="manage_windows_text">Manage Windows</string>
+    <!-- Accessibility text for the handle menu change aspect ratio button [CHAR LIMIT=NONE] -->
+    <string name="change_aspect_ratio_text">Change aspect ratio</string>
     <!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
     <string name="close_text">Close</string>
     <!-- Accessibility text for the handle menu close menu button [CHAR LIMIT=NONE] -->
@@ -339,4 +341,7 @@
     <string name="open_by_default_dialog_in_browser_text">In your browser</string>
     <!-- Text for open by default settings dialog dismiss button. -->
     <string name="open_by_default_dialog_dismiss_button_text">OK</string>
+
+    <!-- Text for the App-to-Web education promo. -->
+    <string name="desktop_windowing_app_to_web_education_text">Quickly open apps in your browser here</string>
 </resources>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
index eb7ef14..62ca5c6 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/PipContentOverlay.java
@@ -18,6 +18,7 @@
 
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -170,26 +171,34 @@
 
         private final Context mContext;
         private final int mAppIconSizePx;
-        private final Rect mAppBounds;
+        /**
+         * The bounds of the application window relative to the task leash.
+         */
+        private final Rect mRelativeAppBounds;
         private final int mOverlayHalfSize;
         private final Matrix mTmpTransform = new Matrix();
         private final float[] mTmpFloat9 = new float[9];
 
         private Bitmap mBitmap;
 
-        public PipAppIconOverlay(Context context, Rect appBounds, Rect destinationBounds,
-                Drawable appIcon, int appIconSizePx) {
+        // TODO(b/356277166): add non-match_parent support on PIP2.
+        /**
+         * @param context the {@link Context} that contains the icon information
+         * @param relativeAppBounds the bounds of the app window frame relative to the task leash
+         * @param destinationBounds the bounds for rhe PIP task
+         * @param appIcon the app icon {@link Drawable}
+         * @param appIconSizePx the icon dimension in pixel
+         */
+        public PipAppIconOverlay(@NonNull Context context, @NonNull Rect relativeAppBounds,
+                @NonNull Rect destinationBounds, @NonNull Drawable appIcon, int appIconSizePx) {
             mContext = context;
             final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP,
                     MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics());
             mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx);
 
-            final int overlaySize = getOverlaySize(appBounds, destinationBounds);
+            final int overlaySize = getOverlaySize(relativeAppBounds, destinationBounds);
             mOverlayHalfSize = overlaySize >> 1;
-
-            // When the activity is in the secondary split, make sure the scaling center is not
-            // offset.
-            mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height());
+            mRelativeAppBounds = relativeAppBounds;
 
             mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888);
             prepareAppIconOverlay(appIcon);
@@ -206,9 +215,9 @@
          * the overlay will be drawn with the max size of the start and end bounds in different
          * rotation.
          */
-        public static int getOverlaySize(Rect appBounds, Rect destinationBounds) {
-            final int appWidth = appBounds.width();
-            final int appHeight = appBounds.height();
+        public static int getOverlaySize(Rect overlayBounds, Rect destinationBounds) {
+            final int appWidth = overlayBounds.width();
+            final int appHeight = overlayBounds.height();
 
             return Math.max(Math.max(appWidth, appHeight),
                     Math.max(destinationBounds.width(), destinationBounds.height())) + 1;
@@ -230,15 +239,15 @@
             mTmpTransform.reset();
             // In order for the overlay to always cover the pip window, the overlay may have a
             // size larger than the pip window. Make sure that app icon is at the center.
-            final int appBoundsCenterX = mAppBounds.centerX();
-            final int appBoundsCenterY = mAppBounds.centerY();
+            final int appBoundsCenterX = mRelativeAppBounds.centerX();
+            final int appBoundsCenterY = mRelativeAppBounds.centerY();
             mTmpTransform.setTranslate(
                     appBoundsCenterX - mOverlayHalfSize,
                     appBoundsCenterY - mOverlayHalfSize);
             // Scale back the bitmap with the pivot point at center.
             final float scale = Math.min(
-                    (float) mAppBounds.width() / currentBounds.width(),
-                    (float) mAppBounds.height() / currentBounds.height());
+                    (float) mRelativeAppBounds.width() / currentBounds.width(),
+                    (float) mRelativeAppBounds.height() / currentBounds.height());
             mTmpTransform.postScale(scale, scale, appBoundsCenterX, appBoundsCenterY);
             atomicTx.setMatrix(mLeash, mTmpTransform, mTmpFloat9)
                     .setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
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 ae0485f..b9a3050 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
@@ -29,6 +29,7 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
 import static com.android.window.flags.Flags.migratePredictiveBackTransition;
 import static com.android.window.flags.Flags.predictiveBackSystemAnims;
+import static com.android.window.flags.Flags.unifyBackNavigationTransition;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
 
@@ -1279,6 +1280,13 @@
             if (transition == mClosePrepareTransition && aborted) {
                 mClosePrepareTransition = null;
                 applyFinishOpenTransition();
+            } else if (!aborted && unifyBackNavigationTransition()) {
+                // Since the closing target participates in the predictive back transition, the
+                // merged transition must be applied with the first transition to ensure a seamless
+                // animation.
+                if (mFinishOpenTransaction != null && finishTransaction != null) {
+                    mFinishOpenTransaction.merge(finishTransaction);
+                }
             }
         }
 
@@ -1300,7 +1308,7 @@
             final TransitionInfo init = mOpenTransitionInfo;
             // Find prepare open target
             boolean openShowWallpaper = false;
-            final ArrayList<OpenChangeInfo> targets = new ArrayList<>();
+            final ArrayList<SurfaceControl> openSurfaces = new ArrayList<>();
             int tmpSize;
             for (int j = init.getChanges().size() - 1; j >= 0; --j) {
                 final TransitionInfo.Change change = init.getChanges().get(j);
@@ -1313,13 +1321,13 @@
                             && openToken == null) {
                         continue;
                     }
-                    targets.add(new OpenChangeInfo(openComponent, openTaskId, openToken));
+                    openSurfaces.add(change.getLeash());
                     if (change.hasFlags(FLAG_SHOW_WALLPAPER)) {
                         openShowWallpaper = true;
                     }
                 }
             }
-            if (targets.isEmpty()) {
+            if (openSurfaces.isEmpty()) {
                 // This shouldn't happen, but if that happen, consume the initial transition anyway.
                 Log.e(TAG, "Unable to merge following transition, cannot find the gesture "
                         + "animated target from the open transition=" + mOpenTransitionInfo);
@@ -1331,7 +1339,7 @@
             tmpSize = info.getChanges().size();
             for (int j = 0; j < tmpSize; ++j) {
                 final TransitionInfo.Change change = info.getChanges().get(j);
-                if (isOpenChangeMatched(targets, change)) {
+                if (isOpenSurfaceMatched(openSurfaces, change)) {
                     // This is original close target, potential be close, but cannot determine
                     // from it.
                     if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) {
@@ -1352,7 +1360,7 @@
                 boolean mergePredictive = false;
                 for (int j = info.getChanges().size() - 1; j >= 0; --j) {
                     final TransitionInfo.Change change = info.getChanges().get(j);
-                    if (isOpenChangeMatched(targets, change)) {
+                    if (isOpenSurfaceMatched(openSurfaces, change)) {
                         if (TransitionUtil.isClosingMode(change.getMode())) {
                             excludeOpenTarget = true;
                         }
@@ -1373,7 +1381,7 @@
                         if (change.hasFlags(FLAG_IS_WALLPAPER)) {
                             continue;
                         }
-                        if (isOpenChangeMatched(targets, change)) {
+                        if (isOpenSurfaceMatched(openSurfaces, change)) {
                             if (excludeOpenTarget) {
                                 // App has triggered another change during predictive back
                                 // transition, filter out predictive back target.
@@ -1408,7 +1416,7 @@
                 if (nonBackClose && nonBackOpen) {
                     for (int j = info.getChanges().size() - 1; j >= 0; --j) {
                         final TransitionInfo.Change change = info.getChanges().get(j);
-                        if (isOpenChangeMatched(targets, change)) {
+                        if (isOpenSurfaceMatched(openSurfaces, change)) {
                             info.getChanges().remove(j);
                         } else if ((openShowWallpaper && change.hasFlags(FLAG_IS_WALLPAPER))) {
                             info.getChanges().remove(j);
@@ -1692,22 +1700,10 @@
         return INVALID_TASK_ID;
     }
 
-    private static boolean isSameChangeTarget(ComponentName topActivity, int taskId,
-            WindowContainerToken token, TransitionInfo.Change change) {
-        final ComponentName openChange = findComponentName(change);
-        final int firstTaskId = findTaskId(change);
-        final WindowContainerToken openToken = findToken(change);
-        return (openChange != null && openChange.equals(topActivity))
-                || (firstTaskId != INVALID_TASK_ID && firstTaskId == taskId)
-                || (openToken != null && openToken.equals(token));
-    }
-
-    static boolean isOpenChangeMatched(@NonNull ArrayList<OpenChangeInfo> targets,
+    static boolean isOpenSurfaceMatched(@NonNull ArrayList<SurfaceControl> openSurfaces,
             TransitionInfo.Change change) {
-        for (int i = targets.size() - 1; i >= 0; --i) {
-            final OpenChangeInfo next = targets.get(i);
-            if (isSameChangeTarget(next.mOpenComponent, next.mOpenTaskId, next.mOpenToken,
-                    change)) {
+        for (int i = openSurfaces.size() - 1; i >= 0; --i) {
+            if (openSurfaces.get(i).isSameSurface(change.getLeash())) {
                 return true;
             }
         }
@@ -1771,16 +1767,4 @@
             }
         }
     }
-
-    static class OpenChangeInfo {
-        final ComponentName mOpenComponent;
-        final int mOpenTaskId;
-        final WindowContainerToken mOpenToken;
-        OpenChangeInfo(ComponentName openComponent, int openTaskId,
-                WindowContainerToken openToken) {
-            mOpenComponent = openComponent;
-            mOpenTaskId = openTaskId;
-            mOpenToken = openToken;
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index d1246d3..14f8cc7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1212,7 +1212,7 @@
      */
     public void startBubbleDrag(String bubbleKey) {
         if (mBubbleData.getSelectedBubble() != null) {
-            mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ false);
+            collapseExpandedViewForBubbleBar();
         }
         if (mBubbleStateListener != null) {
             boolean overflow = BubbleOverflow.KEY.equals(bubbleKey);
@@ -1304,6 +1304,7 @@
         if (BubbleOverflow.KEY.equals(key)) {
             mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow());
             mLayerView.showExpandedView(mBubbleData.getOverflow());
+            mLogger.log(BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
             return;
         }
 
@@ -1315,6 +1316,7 @@
             // already in the stack
             mBubbleData.setSelectedBubbleFromLauncher(b);
             mLayerView.showExpandedView(b);
+            mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED);
         } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
             // TODO: (b/271468319) handle overflow
         } else {
@@ -1630,6 +1632,7 @@
         if (!isShowingAsBubbleBar()) {
             callback = b -> {
                 if (mStackView != null) {
+                    b.setSuppressFlyout(true);
                     mStackView.addBubble(b);
                     mStackView.setSelectedBubble(b);
                 } else {
@@ -2024,12 +2027,16 @@
         public void expansionChanged(boolean isExpanded) {
             // in bubble bar mode, let the request to show the expanded view come from launcher.
             // only collapse here if we're collapsing.
-            if (mLayerView != null && !isExpanded) {
-                if (mBubblePositioner.isImeVisible()) {
-                    // If we're collapsing, hide the IME
-                    hideCurrentInputMethod();
-                }
-                mLayerView.collapse();
+            if (!isExpanded) {
+                collapseExpandedViewForBubbleBar();
+            }
+
+            BubbleLogger.Event event = isExpanded ? BubbleLogger.Event.BUBBLE_BAR_EXPANDED
+                    : BubbleLogger.Event.BUBBLE_BAR_COLLAPSED;
+            if (mBubbleData.getSelectedBubble() instanceof Bubble bubble) {
+                mLogger.log(bubble, event);
+            } else {
+                mLogger.log(event);
             }
         }
 
@@ -2182,6 +2189,16 @@
         }
     }
 
+    private void collapseExpandedViewForBubbleBar() {
+        if (mLayerView != null && mLayerView.isExpanded()) {
+            if (mBubblePositioner.isImeVisible()) {
+                // If we're collapsing, hide the IME
+                hideCurrentInputMethod();
+            }
+            mLayerView.collapse();
+        }
+    }
+
     private void updateOverflowButtonDot() {
         BubbleOverflow overflow = mBubbleData.getOverflow();
         if (overflow == null) return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 35a0d07..88f55b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -1704,6 +1704,7 @@
         getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
         getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        stopMonitoringSwipeUpGesture();
     }
 
     @Override
@@ -2313,7 +2314,8 @@
     /**
      * Stop monitoring for swipe up gesture
      */
-    void stopMonitoringSwipeUpGesture() {
+    @VisibleForTesting
+    public void stopMonitoringSwipeUpGesture() {
         stopMonitoringSwipeUpGestureInternal();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index b29e49a..813772f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -71,6 +71,11 @@
      */
     private static final int SNAP_MODE_MINIMIZED = 3;
 
+    /**
+     * A mode where apps can be "flexibly offscreen" on smaller displays.
+     */
+    private static final int SNAP_FLEXIBLE_SPLIT = 4;
+
     private final float mMinFlingVelocityPxPerSecond;
     private final float mMinDismissVelocityPxPerSecond;
     private final int mDisplayWidth;
@@ -78,6 +83,7 @@
     private final int mDividerSize;
     private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
     private final Rect mInsets = new Rect();
+    private final Rect mPinnedTaskbarInsets = new Rect();
     private final int mSnapMode;
     private final boolean mFreeSnapMode;
     private final int mMinimalSizeResizableTask;
@@ -88,6 +94,8 @@
     /** Allows split ratios that go offscreen (a.k.a. "flexible split") */
     private final boolean mAllowOffscreenRatios;
     private final boolean mIsLeftRightSplit;
+    /** In SNAP_MODE_MINIMIZED, the side of the screen on which an app will "dock" when minimized */
+    private final int mDockSide;
 
     /** The first target which is still splitting the screen */
     private final SnapTarget mFirstSplitTarget;
@@ -101,14 +109,14 @@
 
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-            boolean isLeftRightSplit, Rect insets, int dockSide) {
+            boolean isLeftRightSplit, Rect insets, Rect pinnedTaskbarInsets, int dockSide) {
         this(res, displayWidth, displayHeight, dividerSize, isLeftRightSplit, insets,
-                dockSide, false /* minimized */, true /* resizable */);
+                pinnedTaskbarInsets, dockSide, false /* minimized */, true /* resizable */);
     }
 
     public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
-            boolean isLeftRightSplit, Rect insets, int dockSide, boolean isMinimizedMode,
-            boolean isHomeResizable) {
+            boolean isLeftRightSplit, Rect insets, Rect pinnedTaskbarInsets, int dockSide,
+            boolean isMinimizedMode, boolean isHomeResizable) {
         mMinFlingVelocityPxPerSecond =
                 MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
         mMinDismissVelocityPxPerSecond =
@@ -117,10 +125,11 @@
         mDisplayWidth = displayWidth;
         mDisplayHeight = displayHeight;
         mIsLeftRightSplit = isLeftRightSplit;
+        mDockSide = dockSide;
         mInsets.set(insets);
+        mPinnedTaskbarInsets.set(pinnedTaskbarInsets);
         if (Flags.enableFlexibleTwoAppSplit()) {
-            // In flexible split, we always use a fixed ratio (50%, 66%, or 90%) for splitting
-            mSnapMode = SNAP_FIXED_RATIO;
+            mSnapMode = SNAP_FLEXIBLE_SPLIT;
         } else {
             // Set SNAP_MODE_MINIMIZED, SNAP_MODE_16_9, or SNAP_FIXED_RATIO depending on config
             mSnapMode = isMinimizedMode
@@ -140,7 +149,7 @@
         mAllowOffscreenRatios = SplitScreenUtils.allowOffscreenRatios(res);
         mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
                 com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
-        calculateTargets(isLeftRightSplit, dockSide);
+        calculateTargets();
         mFirstSplitTarget = mTargets.get(1);
         mLastSplitTarget = mTargets.get(mTargets.size() - 2);
         mDismissStartTarget = mTargets.get(0);
@@ -276,28 +285,31 @@
         return mTargets.get(minIndex);
     }
 
-    private void calculateTargets(boolean isLeftRightSplit, int dockedSide) {
+    private void calculateTargets() {
         mTargets.clear();
-        int dividerMax = isLeftRightSplit
+        int dividerMax = mIsLeftRightSplit
                 ? mDisplayWidth
                 : mDisplayHeight;
         int startPos = -mDividerSize;
-        if (dockedSide == DOCKED_RIGHT) {
+        if (mDockSide == DOCKED_RIGHT) {
             startPos += mInsets.left;
         }
         mTargets.add(new SnapTarget(startPos, SNAP_TO_START_AND_DISMISS, 0.35f));
         switch (mSnapMode) {
             case SNAP_MODE_16_9:
-                addRatio16_9Targets(isLeftRightSplit, dividerMax);
+                addRatio16_9Targets(mIsLeftRightSplit, dividerMax);
                 break;
             case SNAP_FIXED_RATIO:
-                addFixedDivisionTargets(isLeftRightSplit, dividerMax);
+                addFixedDivisionTargets(mIsLeftRightSplit, dividerMax);
                 break;
             case SNAP_ONLY_1_1:
-                addMiddleTarget(isLeftRightSplit);
+                addMiddleTarget(mIsLeftRightSplit);
                 break;
             case SNAP_MODE_MINIMIZED:
-                addMinimizedTarget(isLeftRightSplit, dockedSide);
+                addMinimizedTarget(mIsLeftRightSplit, mDockSide);
+                break;
+            case SNAP_FLEXIBLE_SPLIT:
+                addFlexSplitTargets(mIsLeftRightSplit, dividerMax);
                 break;
         }
         mTargets.add(new SnapTarget(dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f));
@@ -321,18 +333,9 @@
                 ? mDisplayWidth - mInsets.right
                 : mDisplayHeight - mInsets.bottom;
 
-        int size;
-        if (Flags.enableFlexibleTwoAppSplit()) {
-            float ratio = areOffscreenRatiosSupported()
-                    ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO
-                    : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
-            size = (int) (ratio * (end - start)) - mDividerSize / 2;
-        } else {
-            size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
-
-            if (mCalculateRatiosBasedOnAvailableSpace) {
-                size = Math.max(size, mMinimalSizeResizableTask);
-            }
+        int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
+        if (mCalculateRatiosBasedOnAvailableSpace) {
+            size = Math.max(size, mMinimalSizeResizableTask);
         }
 
         int topPosition = start + size;
@@ -340,6 +343,24 @@
         addNonDismissingTargets(isLeftRightSplit, topPosition, bottomPosition, dividerMax);
     }
 
+    private void addFlexSplitTargets(boolean isLeftRightSplit, int dividerMax) {
+        int start = 0;
+        int end = isLeftRightSplit ? mDisplayWidth : mDisplayHeight;
+        int pinnedTaskbarShiftStart = isLeftRightSplit
+                ? mPinnedTaskbarInsets.left : mPinnedTaskbarInsets.top;
+        int pinnedTaskbarShiftEnd = isLeftRightSplit
+                ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom;
+
+        float ratio = areOffscreenRatiosSupported()
+                ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO
+                : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
+        int size = (int) (ratio * (end - start)) - mDividerSize / 2;
+
+        int leftTopPosition = start + pinnedTaskbarShiftStart + size;
+        int rightBottomPosition = end - pinnedTaskbarShiftEnd - size - mDividerSize;
+        addNonDismissingTargets(isLeftRightSplit, leftTopPosition, rightBottomPosition, dividerMax);
+    }
+
     private void addRatio16_9Targets(boolean isLeftRightSplit, int dividerMax) {
         int start = isLeftRightSplit ? mInsets.left : mInsets.top;
         int end = isLeftRightSplit
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index b1e0e9e..dab30b0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -49,10 +49,13 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.view.Display;
+import android.view.InsetsController;
+import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.RoundedCorner;
@@ -153,6 +156,7 @@
     private final ResizingEffectPolicy mSurfaceEffectPolicy;
     private final ShellTaskOrganizer mTaskOrganizer;
     private final InsetsState mInsetsState = new InsetsState();
+    private Insets mPinnedTaskbarInsets = Insets.NONE;
 
     private Context mContext;
     @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
@@ -523,6 +527,7 @@
     @Override
     public void insetsChanged(InsetsState insetsState) {
         mInsetsState.set(insetsState);
+
         if (!mInitialized) {
             return;
         }
@@ -531,9 +536,51 @@
             // flicker.
             return;
         }
+
+        // Check to see if insets changed in such a way that the divider algorithm needs to be
+        // recalculated.
+        Insets pinnedTaskbarInsets = calculatePinnedTaskbarInsets(insetsState);
+        if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) {
+            mPinnedTaskbarInsets = pinnedTaskbarInsets;
+            // Refresh the DividerSnapAlgorithm.
+            mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
+            // If the divider is no longer placed on a snap point, animate it to the nearest one.
+            DividerSnapAlgorithm.SnapTarget snapTarget =
+                    findSnapTarget(mDividerPosition, 0, false /* hardDismiss */);
+            if (snapTarget.position != mDividerPosition) {
+                snapToTarget(mDividerPosition, snapTarget,
+                        InsetsController.ANIMATION_DURATION_RESIZE,
+                        InsetsController.RESIZE_INTERPOLATOR);
+            }
+        }
+
         mSplitWindowManager.onInsetsChanged(insetsState);
     }
 
+    /**
+     * Calculates the insets that might trigger a divider algorithm recalculation. Currently, only
+     * pinned Taskbar does this, and only when the IME is not showing.
+     */
+    private Insets calculatePinnedTaskbarInsets(InsetsState insetsState) {
+        if (insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())) {
+            return Insets.NONE;
+        }
+
+        // If IME is not showing...
+        for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = insetsState.sourceAt(i);
+            // and Taskbar is pinned...
+            if (source.getType() == WindowInsets.Type.navigationBars()
+                    && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) {
+                // Return Insets representing the pinned taskbar state.
+                return source.calculateVisibleInsets(mRootBounds);
+            }
+        }
+
+        // Else, divider can calculate based on the full display.
+        return Insets.NONE;
+    }
+
     @Override
     public void insetsControlChanged(InsetsState insetsState,
             InsetsSourceControl[] activeControls) {
@@ -631,8 +678,8 @@
     }
 
     /**
-     * Same as {@link #snapToTarget(int, SnapTarget)}, with default animation duration and
-     * interpolator.
+     * Same as {@link #snapToTarget(int, SnapTarget, int, Interpolator)}, with default animation
+     * duration and interpolator.
      */
     public void snapToTarget(int currentPosition, SnapTarget snapTarget) {
         snapToTarget(currentPosition, snapTarget, FLING_RESIZE_DURATION,
@@ -683,6 +730,7 @@
                 mDividerSize,
                 mIsLeftRightSplit,
                 insets,
+                mPinnedTaskbarInsets.toRect(),
                 mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 6146ecd..886330f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -688,6 +688,12 @@
 
     private void launchUserAspectRatioSettings(
             @NonNull TaskInfo taskInfo, @NonNull ShellTaskOrganizer.TaskListener taskListener) {
+        launchUserAspectRatioSettings(mContext, taskInfo);
+    }
+
+    /** Launch the user aspect ratio settings for the package of the given task. */
+    public static void launchUserAspectRatioSettings(
+            @NonNull Context context, @NonNull TaskInfo taskInfo) {
         final Intent intent = new Intent(Settings.ACTION_MANAGE_USER_ASPECT_RATIO_SETTINGS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -697,7 +703,7 @@
             intent.setData(packageUri);
         }
         final UserHandle userHandle = UserHandle.of(taskInfo.userId);
-        mContext.startActivityAsUser(intent, userHandle);
+        context.startActivityAsUser(intent, userHandle);
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 615dad3..fec4c16 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -86,7 +86,10 @@
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationFilter;
+import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
+import com.android.wm.shell.desktopmode.education.AppToWebEducationFilter;
 import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
+import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository;
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializerImpl;
@@ -132,6 +135,7 @@
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController;
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController;
 import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
 
@@ -288,6 +292,7 @@
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             AppHandleEducationController appHandleEducationController,
+            AppToWebEducationController appToWebEducationController,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler,
             FocusTransitionObserver focusTransitionObserver,
@@ -317,6 +322,7 @@
                     multiInstanceHelper,
                     desktopTasksLimiter,
                     appHandleEducationController,
+                    appToWebEducationController,
                     windowDecorCaptionHandleRepository,
                     desktopActivityOrientationHandler,
                     focusTransitionObserver,
@@ -970,7 +976,10 @@
             CloseDesktopTaskTransitionHandler closeDesktopTaskTransitionHandler,
             Optional<DesktopImmersiveController> desktopImmersiveController,
             InteractionJankMonitor interactionJankMonitor,
-            @ShellMainThread Handler handler) {
+            @ShellMainThread Handler handler,
+            ShellInit shellInit,
+            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
+    ) {
         if (!DesktopModeStatus.canEnterDesktopMode(context)) {
             return Optional.empty();
         }
@@ -983,7 +992,9 @@
                         closeDesktopTaskTransitionHandler,
                         desktopImmersiveController.get(),
                         interactionJankMonitor,
-                        handler));
+                        handler,
+                        shellInit,
+                        rootTaskDisplayAreaOrganizer));
     }
 
     @WMSingleton
@@ -1035,6 +1046,20 @@
                 context, additionalSystemViewContainerFactory, displayController);
     }
 
+    @WMSingleton
+    @Provides
+    static DesktopWindowingEducationPromoController provideDesktopWindowingEducationPromoController(
+            Context context,
+            AdditionalSystemViewContainer.Factory additionalSystemViewContainerFactory,
+            DisplayController displayController
+    ) {
+        return new DesktopWindowingEducationPromoController(
+                context,
+                additionalSystemViewContainerFactory,
+                displayController
+        );
+    }
+
     @OptIn(markerClass = ExperimentalCoroutinesApi.class)
     @WMSingleton
     @Provides
@@ -1058,6 +1083,38 @@
 
     @WMSingleton
     @Provides
+    static AppToWebEducationDatastoreRepository provideAppToWebEducationDatastoreRepository(
+            Context context) {
+        return new AppToWebEducationDatastoreRepository(context);
+    }
+
+    @WMSingleton
+    @Provides
+    static AppToWebEducationFilter provideAppToWebEducationFilter(
+            Context context,
+            AppToWebEducationDatastoreRepository appToWebEducationDatastoreRepository) {
+        return new AppToWebEducationFilter(context, appToWebEducationDatastoreRepository);
+    }
+
+    @OptIn(markerClass = ExperimentalCoroutinesApi.class)
+    @WMSingleton
+    @Provides
+    static AppToWebEducationController provideAppToWebEducationController(
+            Context context,
+            AppToWebEducationFilter appToWebEducationFilter,
+            AppToWebEducationDatastoreRepository appToWebEducationDatastoreRepository,
+            WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
+            DesktopWindowingEducationPromoController desktopWindowingEducationPromoController,
+            @ShellMainThread CoroutineScope applicationScope,
+            @ShellBackgroundThread MainCoroutineDispatcher backgroundDispatcher) {
+        return new AppToWebEducationController(context, appToWebEducationFilter,
+                appToWebEducationDatastoreRepository, windowDecorCaptionHandleRepository,
+                desktopWindowingEducationPromoController, applicationScope,
+                backgroundDispatcher);
+    }
+
+    @WMSingleton
+    @Provides
     static DesktopPersistentRepository provideDesktopPersistentRepository(
             Context context, @ShellBackgroundThread CoroutineScope bgScope) {
         return new DesktopPersistentRepository(context, bgScope);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index e741892..f69aa6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -224,9 +224,9 @@
         finishTransaction: SurfaceControl.Transaction,
         finishCallback: Transitions.TransitionFinishCallback
     ): Boolean {
-        logD("startAnimation transition=%s", transition)
         val state = requireState()
         if (transition != state.transition) return false
+        logD("startAnimation transition=%s", transition)
         animateResize(
             targetTaskId = state.taskId,
             info = info,
@@ -334,7 +334,6 @@
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
     ) {
-        logD("onTransitionReady transition=%s", transition)
         // Check if this is a pending external exit transition.
         val pendingExit = pendingExternalExitTransitions
             .firstOrNull { pendingExit -> pendingExit.transition == transition }
@@ -402,7 +401,6 @@
     }
 
     override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
-        logD("onTransitionMerged merged=%s playing=%s", merged, playing)
         val pendingExit = pendingExternalExitTransitions
             .firstOrNull { pendingExit -> pendingExit.transition == merged }
         if (pendingExit != null) {
@@ -415,7 +413,6 @@
     }
 
     override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
-        logD("onTransitionFinished transition=%s aborted=%b", transition, aborted)
         val pendingExit = pendingExternalExitTransitions
             .firstOrNull { pendingExit -> pendingExit.transition == transition }
         if (pendingExit != null) {
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 0bc571f..48bb2a8 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
@@ -25,6 +25,7 @@
 import android.view.WindowManager
 import android.window.DesktopModeFlags
 import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import androidx.annotation.VisibleForTesting
@@ -32,10 +33,13 @@
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.protolog.ProtoLog
 import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.MixedTransitionHandler
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
@@ -50,8 +54,14 @@
     private val desktopImmersiveController: DesktopImmersiveController,
     private val interactionJankMonitor: InteractionJankMonitor,
     @ShellMainThread private val handler: Handler,
+    shellInit: ShellInit,
+    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
 ) : MixedTransitionHandler, FreeformTaskTransitionStarter {
 
+    init {
+        shellInit.addInitCallback ({ transitions.addHandler(this) }, this)
+    }
+
     @VisibleForTesting
     val pendingMixedTransitions = mutableListOf<PendingMixedTransition>()
 
@@ -85,9 +95,11 @@
         @WindowManager.TransitionType transitionType: Int,
         wct: WindowContainerTransaction,
         taskId: Int,
+        minimizingTaskId: Int? = null,
         exitingImmersiveTask: Int? = null,
     ): IBinder {
-        if (!Flags.enableFullyImmersiveInDesktop()) {
+        if (!Flags.enableFullyImmersiveInDesktop() &&
+            !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
             return transitions.startTransition(transitionType, wct, /* handler= */ null)
         }
         if (exitingImmersiveTask == null) {
@@ -103,11 +115,17 @@
                 pendingMixedTransitions.add(PendingMixedTransition.Launch(
                     transition = transition,
                     launchingTask = taskId,
-                    exitingImmersiveTask = exitingImmersiveTask
+                    minimizingTask = minimizingTaskId,
+                    exitingImmersiveTask = exitingImmersiveTask,
                 ))
             }
     }
 
+    /** Notifies this handler that there is a pending transition for it to handle. */
+    fun addPendingMixedTransition(pendingMixedTransition: PendingMixedTransition) {
+        pendingMixedTransitions.add(pendingMixedTransition)
+    }
+
     /** Returns null, as it only handles transitions started from Shell. */
     override fun handleRequest(
         transition: IBinder,
@@ -123,7 +141,7 @@
     ): Boolean {
         val pending = pendingMixedTransitions.find { pending -> pending.transition == transition }
             ?: return false.also {
-                logW("Should have pending desktop transition")
+                logV("No pending desktop transition")
             }
         pendingMixedTransitions.remove(pending)
         logV("Animating pending mixed transition: %s", pending)
@@ -191,6 +209,9 @@
         val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
             findDesktopTaskChange(info, exitingTask)
         }
+        val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
+            findDesktopTaskChange(info, minimizingTask)
+        }
         val launchChange = findDesktopTaskChange(info, pending.launchingTask)
             ?: error("Should have pending launching task change")
 
@@ -204,9 +225,17 @@
         }
 
         logV(
-            "Animating pending mixed launch transition task#%d immersiveExitTask#%s",
-            launchChange.taskInfo!!.taskId, immersiveExitChange?.taskInfo?.taskId
+            "Animating mixed launch transition task#%d, minimizingTask#%s immersiveExitTask#%s",
+            launchChange.taskInfo!!.taskId, minimizeChange?.taskInfo?.taskId,
+            immersiveExitChange?.taskInfo?.taskId
         )
+        if (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+            // Only apply minimize change reparenting here if we implement the new app launch
+            // transitions, otherwise this reparenting is handled in the default handler.
+            minimizeChange?.let {
+                applyMinimizeChangeReparenting(info, minimizeChange, startTransaction)
+            }
+        }
         if (immersiveExitChange != null) {
             subAnimationCount = 2
             // Animate the immersive exit change separately.
@@ -257,7 +286,7 @@
         change: TransitionInfo.Change,
         startTransaction: SurfaceControl.Transaction,
         finishTransaction: SurfaceControl.Transaction,
-        finishCallback: Transitions.TransitionFinishCallback,
+        finishCallback: TransitionFinishCallback,
     ): Boolean {
         // Starting the jank trace if closing the last window in desktop mode.
         interactionJankMonitor.begin(
@@ -280,6 +309,28 @@
         )
     }
 
+    /**
+     * Reparent the minimizing task back to its root display area.
+     *
+     * During the launch/minimize animation the all animated tasks will be reparented to a
+     * transition leash shown in front of other desktop tasks. Reparenting the minimizing task back
+     * to its root display area ensures that task stays behind other desktop tasks during the
+     * animation.
+     */
+    private fun applyMinimizeChangeReparenting(
+        info: TransitionInfo,
+        minimizeChange: Change,
+        startTransaction: SurfaceControl.Transaction,
+    ) {
+        require(TransitionUtil.isOpeningMode(info.type))
+        require(minimizeChange.taskInfo != null)
+        val taskInfo = minimizeChange.taskInfo!!
+        require(taskInfo.isFreeform)
+        logV("Reparenting minimizing task#%d", taskInfo.taskId)
+        rootTaskDisplayAreaOrganizer.reparentToDisplayArea(
+            taskInfo.displayId, minimizeChange.leash, startTransaction)
+    }
+
     private fun dispatchToLeftoverHandler(
         transition: IBinder,
         info: TransitionInfo,
@@ -341,6 +392,7 @@
         data class Launch(
             override val transition: IBinder,
             val launchingTask: Int,
+            val minimizingTask: Int?,
             val exitingImmersiveTask: Int?,
         ) : PendingMixedTransition()
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 5648feb..fda709a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -23,12 +23,12 @@
 import android.util.ArraySet
 import android.util.SparseArray
 import android.view.Display.INVALID_DISPLAY
+import android.window.DesktopModeFlags
 import android.window.WindowContainerToken
 import androidx.core.util.forEach
 import androidx.core.util.keyIterator
 import androidx.core.util.valueIterator
 import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags
 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
@@ -294,7 +294,7 @@
                 taskId, isVisible, displayId)
             logD("VisibleTaskCount has changed from %d to %d", prevCount, newCount)
             notifyVisibleTaskListeners(displayId, newCount)
-            if (Flags.enableDesktopWindowingPersistence()) {
+            if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
                 updatePersistentRepository(displayId)
             }
         }
@@ -344,7 +344,7 @@
         desktopTaskDataByDisplayId.getOrCreate(displayId).freeformTasksInZOrder.add(0, taskId)
         // Unminimize the task if it is minimized.
         unminimizeTask(displayId, taskId)
-        if (Flags.enableDesktopWindowingPersistence()) {
+        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
             updatePersistentRepository(displayId)
         }
     }
@@ -362,7 +362,7 @@
             desktopTaskDataByDisplayId.getOrCreate(displayId).minimizedTasks.add(taskId)
         }
         updateTask(displayId, taskId, isVisible = false)
-        if (Flags.enableDesktopWindowingPersistence()) {
+        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
             updatePersistentRepository(displayId)
         }
     }
@@ -410,7 +410,7 @@
         unminimizeTask(displayId, taskId)
         removeActiveTask(taskId)
         removeVisibleTask(taskId)
-        if (Flags.enableDesktopWindowingPersistence()) {
+        if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
             updatePersistentRepository(displayId)
         }
     }
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 133f069..75f8839 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
@@ -698,6 +698,7 @@
                 transitionType = transitionType,
                 wct = wct,
                 taskId = taskId,
+                minimizingTaskId = taskIdToMinimize,
                 exitingImmersiveTask = exitingImmersiveTask,
             )
             taskIdToMinimize?.let { addPendingMinimizeTransition(t, it) }
@@ -1161,7 +1162,7 @@
                 if (runningTaskInfo != null) {
                     // Task is already running, reorder it to the front
                     wct.reorder(runningTaskInfo.token, /* onTop= */ true)
-                } else if (Flags.enableDesktopWindowingPersistence()) {
+                } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) {
                     // Task is not running, start it
                     wct.startTask(
                         taskId,
@@ -1434,6 +1435,7 @@
                 )
             val transition = transitions.startTransition(TRANSIT_OPEN, wct, null)
             taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+            addPendingAppLaunchTransition(transition, requestedTaskId, taskIdToMinimize)
             exitResult.asExit()?.runOnTransitionStart?.invoke(transition)
         } else {
             val splitPosition = splitScreenController.determineNewInstancePosition(callingTask)
@@ -1574,6 +1576,7 @@
         desktopImmersiveController.exitImmersiveIfApplicable(transition, wct, task.displayId)
         // 2) minimize a Task if needed.
         val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
+        addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
         if (taskIdToMinimize != null) {
             addPendingMinimizeTransition(transition, taskIdToMinimize)
             return wct
@@ -1605,6 +1608,7 @@
                 // minimize another Task.
                 val taskIdToMinimize = addAndGetMinimizeChanges(task.displayId, wct, task.taskId)
                 taskIdToMinimize?.let { addPendingMinimizeTransition(transition, it) }
+                addPendingAppLaunchTransition(transition, task.taskId, taskIdToMinimize)
                 desktopImmersiveController.exitImmersiveIfApplicable(
                     transition, wct, task.displayId
                 )
@@ -1785,6 +1789,20 @@
         }
     }
 
+    private fun addPendingAppLaunchTransition(
+        transition: IBinder,
+        launchTaskId: Int,
+        minimizeTaskId: Int?,
+    ) {
+        if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue) {
+            return
+        }
+        // TODO b/359523924: pass immersive task here?
+        desktopMixedTransitionHandler.addPendingMixedTransition(
+            DesktopMixedTransitionHandler.PendingMixedTransition.Launch(
+                transition, launchTaskId, minimizeTaskId, /* exitingImmersiveTask= */ null))
+    }
+
     fun removeDesktop(displayId: Int) {
         if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index 7bcc5d1..f0e3a2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -133,7 +133,6 @@
         }
 
         override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
-            logV("transition %s finished", transition)
             if (activeTransitionTokensAndTasks.remove(transition) != null) {
                 if (aborted) {
                     interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)
@@ -252,10 +251,6 @@
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
     }
 
-    private fun logE(msg: String, vararg arguments: Any?) {
-        ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
-    }
-
     private companion object {
         const val TAG = "DesktopTasksLimiter"
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index dedd44f..d537da8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -60,7 +60,8 @@
  * entering and exiting freeform.
  */
 public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
-    private static final int FULLSCREEN_ANIMATION_DURATION = 336;
+    @VisibleForTesting
+    static final int FULLSCREEN_ANIMATION_DURATION = 336;
 
     private final Context mContext;
     private final Transitions mTransitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
index 7ae5370..8bfcca0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepository.kt
@@ -18,6 +18,10 @@
 
 import android.app.ActivityManager.RunningTaskInfo
 import android.graphics.Rect
+import com.android.wm.shell.desktopmode.CaptionState.AppHandle
+import com.android.wm.shell.desktopmode.CaptionState.AppHeader
+import com.android.wm.shell.desktopmode.CaptionState.NoCaption
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -26,11 +30,20 @@
   private val _captionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
   /** Observer for app handle state changes. */
   val captionStateFlow: StateFlow<CaptionState> = _captionStateFlow
+  private val _appToWebUsageFlow = MutableSharedFlow<Unit>()
+  /** Observer for App-to-Web usage. */
+  val appToWebUsageFlow = _appToWebUsageFlow
+
 
   /** Notifies [captionStateFlow] if there is a change to caption state. */
   fun notifyCaptionChanged(captionState: CaptionState) {
     _captionStateFlow.value = captionState
   }
+
+  /** Notifies [appToWebUsageFlow] if App-to-Web feature is used. */
+  fun onAppToWebUsage() {
+    _appToWebUsageFlow.tryEmit(Unit)
+  }
 }
 
 /**
@@ -41,17 +54,19 @@
  * * [AppHeader]: Indicating that there is at least one visible app chip on the screen.
  * * [NoCaption]: Signifying that no caption handle is currently visible on the device.
  */
-sealed class CaptionState {
+sealed class CaptionState{
   data class AppHandle(
-      val runningTaskInfo: RunningTaskInfo,
-      val isHandleMenuExpanded: Boolean,
-      val globalAppHandleBounds: Rect
+    val runningTaskInfo: RunningTaskInfo,
+    val isHandleMenuExpanded: Boolean,
+    val globalAppHandleBounds: Rect,
+    val isCapturedLinkAvailable: Boolean
   ) : CaptionState()
 
   data class AppHeader(
-      val runningTaskInfo: RunningTaskInfo,
-      val isHeaderMenuExpanded: Boolean,
-      val globalAppChipBounds: Rect
+    val runningTaskInfo: RunningTaskInfo,
+    val isHeaderMenuExpanded: Boolean,
+    val globalAppChipBounds: Rect,
+    val isCapturedLinkAvailable: Boolean
   ) : CaptionState()
 
   data object NoCaption : CaptionState()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
index f21a124..e01c448 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppHandleEducationController.kt
@@ -36,7 +36,7 @@
 import com.android.wm.shell.windowdecor.common.DecorThemeUtil
 import com.android.wm.shell.windowdecor.common.Theme
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController
-import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.EducationViewConfig
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipEducationViewConfig
 import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationTooltipController.TooltipColorScheme
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
@@ -137,7 +137,7 @@
     // TODO: b/370546801 - Differentiate between user dismissing the tooltip vs following the cue.
     // Populate information important to inflate app handle education tooltip.
     val appHandleTooltipConfig =
-        EducationViewConfig(
+        TooltipEducationViewConfig(
             tooltipViewLayout = R.layout.desktop_windowing_education_top_arrow_tooltip,
             tooltipColorScheme = tooltipColorScheme,
             tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
@@ -196,7 +196,7 @@
                       windowingOptionPillHeight / 2)
           // Populate information important to inflate windowing image button education tooltip.
           val windowingImageButtonTooltipConfig =
-              EducationViewConfig(
+              TooltipEducationViewConfig(
                   tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
                   tooltipColorScheme = tooltipColorScheme,
                   tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
@@ -249,7 +249,7 @@
                   globalAppChipBounds.top + globalAppChipBounds.height() / 2)
           // Populate information important to inflate exit desktop mode education tooltip.
           val exitWindowingTooltipConfig =
-              EducationViewConfig(
+              TooltipEducationViewConfig(
                   tooltipViewLayout = R.layout.desktop_windowing_education_left_arrow_tooltip,
                   tooltipColorScheme = tooltipColorScheme,
                   tooltipViewGlobalCoordinates = tooltipGlobalCoordinates,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
new file mode 100644
index 0000000..693da81
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationController.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.wm.shell.desktopmode.education
+
+import android.annotation.DimenRes
+import android.annotation.StringRes
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.os.SystemProperties
+import androidx.compose.ui.graphics.toArgb
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.CaptionState
+import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
+import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController.EducationColorScheme
+import com.android.wm.shell.windowdecor.education.DesktopWindowingEducationPromoController.EducationViewConfig
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.MainCoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.debounce
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/**
+ * Controls App-to-Web education end to end.
+ *
+ * Listen to usages of App-to-Web, calls an api to check if the education
+ * should be shown and controls education UI.
+ */
+@OptIn(kotlinx.coroutines.FlowPreview::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class AppToWebEducationController(
+    private val context: Context,
+    private val appToWebEducationFilter: AppToWebEducationFilter,
+    private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository,
+    private val windowDecorCaptionHandleRepository: WindowDecorCaptionHandleRepository,
+    private val windowingEducationViewController: DesktopWindowingEducationPromoController,
+    @ShellMainThread private val applicationCoroutineScope: CoroutineScope,
+    @ShellBackgroundThread private val backgroundDispatcher: MainCoroutineDispatcher,
+) {
+    private val decorThemeUtil = DecorThemeUtil(context)
+
+    init {
+        runIfEducationFeatureEnabled {
+            applicationCoroutineScope.launch {
+                // Central block handling the App-to-Web's educational flow end-to-end.
+                isEducationViewLimitReachedFlow()
+                    .flatMapLatest { countExceedsMaximum ->
+                        if (countExceedsMaximum) {
+                            // If the education has been viewed the maximum amount of times then
+                            // return emptyFlow() that completes immediately. This will help us to
+                            // not listen to [captionHandleStateFlow] after the education should
+                            // not be shown.
+                            emptyFlow()
+                        } else {
+                            // Listen for changes to window decor's caption.
+                            windowDecorCaptionHandleRepository.captionStateFlow
+                                // Wait for few seconds before emitting the latest state.
+                                .debounce(APP_TO_WEB_EDUCATION_DELAY_MILLIS)
+                                .filter { captionState ->
+                                    captionState !is CaptionState.NoCaption &&
+                                            appToWebEducationFilter
+                                                .shouldShowAppToWebEducation(captionState)
+                                }
+                        }
+                    }
+                    .flowOn(backgroundDispatcher)
+                    .collectLatest { captionState ->
+                        val educationColorScheme = educationColorScheme(captionState)
+                        showEducation(captionState, educationColorScheme!!)
+                        // After showing first tooltip, increase count of education views
+                        appToWebEducationDatastoreRepository.updateEducationShownCount()
+                    }
+            }
+
+            applicationCoroutineScope.launch {
+                if (isFeatureUsed()) return@launch
+                windowDecorCaptionHandleRepository.appToWebUsageFlow
+                    .collect {
+                        // If user utilizes App-to-Web, mark user has used the feature
+                        appToWebEducationDatastoreRepository
+                            .updateFeatureUsedTimestampMillis(isViewed = true)
+                    }
+            }
+        }
+    }
+
+    private inline fun runIfEducationFeatureEnabled(block: () -> Unit) {
+        if (canEnterDesktopMode(context) && Flags.enableDesktopWindowingAppToWebEducation()) block()
+    }
+
+    private fun showEducation(captionState: CaptionState, colorScheme: EducationColorScheme) {
+        val educationGlobalCoordinates: Point
+        val taskId: Int
+        when (captionState) {
+            is CaptionState.AppHandle -> {
+                val appHandleBounds = captionState.globalAppHandleBounds
+                val educationWidth =
+                    loadDimensionPixelSize(R.dimen.desktop_windowing_education_promo_width)
+                educationGlobalCoordinates = Point(
+                    appHandleBounds.centerX() - educationWidth / 2,
+                    appHandleBounds.bottom
+                )
+                taskId = captionState.runningTaskInfo.taskId
+            }
+
+            is CaptionState.AppHeader -> {
+                val taskBounds =
+                    captionState.runningTaskInfo.configuration.windowConfiguration.bounds
+                educationGlobalCoordinates =
+                    Point(taskBounds.left, captionState.globalAppChipBounds.bottom)
+                taskId = captionState.runningTaskInfo.taskId
+            }
+
+            else -> return
+        }
+
+        // Populate information important to inflate education promo.
+        val educationConfig =
+            EducationViewConfig(
+                viewLayout = R.layout.desktop_windowing_education_promo,
+                educationColorScheme = colorScheme,
+                viewGlobalCoordinates = educationGlobalCoordinates,
+                educationText = getString(R.string.desktop_windowing_app_to_web_education_text),
+                widthId = R.dimen.desktop_windowing_education_promo_width,
+                heightId = R.dimen.desktop_windowing_education_promo_height
+            )
+
+        windowingEducationViewController.showEducation(
+            viewConfig = educationConfig, taskId = taskId)
+    }
+
+    private fun educationColorScheme(captionState: CaptionState): EducationColorScheme? {
+        val taskInfo: RunningTaskInfo = when (captionState) {
+            is CaptionState.AppHandle -> captionState.runningTaskInfo
+            is CaptionState.AppHeader -> captionState.runningTaskInfo
+            else -> return null
+        }
+
+        val colorScheme = decorThemeUtil.getColorScheme(taskInfo)
+        val tooltipContainerColor = colorScheme.surfaceBright.toArgb()
+        val tooltipTextColor = colorScheme.onSurface.toArgb()
+        return EducationColorScheme(tooltipContainerColor, tooltipTextColor)
+    }
+
+    /**
+     * Listens to changes in the number of times the education has been viewed, mapping the count to
+     * true if the education has been viewed the maximum amount of times.
+     */
+    private fun isEducationViewLimitReachedFlow(): Flow<Boolean> =
+        appToWebEducationDatastoreRepository.dataStoreFlow
+            .map { preferences ->
+                appToWebEducationFilter.isEducationViewLimitReached(preferences)}
+            .distinctUntilChanged()
+
+    /**
+     * Listens to the changes to [WindowingEducationProto#hasFeatureUsedTimestampMillis()] in
+     * datastore proto object.
+     */
+    private suspend fun isFeatureUsed(): Boolean =
+        appToWebEducationDatastoreRepository.dataStoreFlow.first().hasFeatureUsedTimestampMillis()
+
+    private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
+        if (resourceId == Resources.ID_NULL) return 0
+        return context.resources.getDimensionPixelSize(resourceId)
+    }
+
+    private fun getString(@StringRes resId: Int): String = context.resources.getString(resId)
+
+    companion object {
+        const val TAG = "AppToWebEducationController"
+        val APP_TO_WEB_EDUCATION_DELAY_MILLIS: Long
+            get() = SystemProperties.getLong(
+                "persist.windowing_app_handle_education_delay",
+                3000L
+            )
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
new file mode 100644
index 0000000..feee6ed
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/AppToWebEducationFilter.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.wm.shell.desktopmode.education
+
+import android.annotation.IntegerRes
+import android.app.ActivityManager.RunningTaskInfo
+import android.content.Context
+import android.os.SystemClock
+import android.provider.Settings.Secure
+import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.CaptionState
+import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository
+import com.android.wm.shell.desktopmode.education.data.WindowingEducationProto
+import java.time.Duration
+
+/** Filters incoming App-to-Web education triggers based on set conditions. */
+class AppToWebEducationFilter(
+    private val context: Context,
+    private val appToWebEducationDatastoreRepository: AppToWebEducationDatastoreRepository
+) {
+
+    /** Returns true if conditions to show App-to-web education are met, returns false otherwise. */
+    suspend fun shouldShowAppToWebEducation(captionState: CaptionState): Boolean {
+        val (taskInfo: RunningTaskInfo, isCapturedLinkAvailable: Boolean) = when (captionState) {
+            is CaptionState.AppHandle ->
+                Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+            is CaptionState.AppHeader ->
+                Pair(captionState.runningTaskInfo, captionState.isCapturedLinkAvailable)
+            else -> return false
+        }
+
+        val focusAppPackageName = taskInfo.topActivityInfo?.packageName ?: return false
+        val windowingEducationProto = appToWebEducationDatastoreRepository.windowingEducationProto()
+
+        return !isOtherEducationShowing() &&
+                !isEducationViewLimitReached(windowingEducationProto) &&
+                hasSufficientTimeSinceSetup() &&
+                !isFeatureUsedBefore(windowingEducationProto) &&
+                isCapturedLinkAvailable &&
+                isFocusAppInAllowlist(focusAppPackageName)
+    }
+
+    private fun isFocusAppInAllowlist(focusAppPackageName: String): Boolean =
+        focusAppPackageName in
+                context.resources.getStringArray(
+                    R.array.desktop_windowing_app_to_web_education_allowlist_apps)
+
+    // TODO: b/350953004 - Add checks based on App compat
+    // TODO: b/350951797 - Add checks based on PKT tips education
+    private fun isOtherEducationShowing(): Boolean = isTaskbarEducationShowing() ||
+            isCompatUiEducationShowing()
+
+    private fun isTaskbarEducationShowing(): Boolean =
+        Secure.getInt(context.contentResolver, Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0) == 1
+
+    private fun isCompatUiEducationShowing(): Boolean =
+        Secure.getInt(context.contentResolver, Secure.COMPAT_UI_EDUCATION_SHOWING, 0) == 1
+
+    private fun hasSufficientTimeSinceSetup(): Boolean =
+        Duration.ofMillis(SystemClock.elapsedRealtime()) >
+                convertIntegerResourceToDuration(
+                    R.integer.desktop_windowing_education_required_time_since_setup_seconds)
+
+    /** Returns true if education is viewed maximum amount of times it should be shown. */
+    fun isEducationViewLimitReached(windowingEducationProto: WindowingEducationProto): Boolean =
+        windowingEducationProto.getAppToWebEducation().getEducationShownCount() >=
+                MAXIMUM_TIMES_EDUCATION_SHOWN
+
+    private fun isFeatureUsedBefore(windowingEducationProto: WindowingEducationProto): Boolean =
+        windowingEducationProto.hasFeatureUsedTimestampMillis()
+
+    private fun convertIntegerResourceToDuration(@IntegerRes resourceId: Int): Duration =
+        Duration.ofSeconds(context.resources.getInteger(resourceId).toLong())
+
+    companion object {
+        private const val MAXIMUM_TIMES_EDUCATION_SHOWN = 100
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
new file mode 100644
index 0000000..8be6e6d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/AppToWebEducationDatastoreRepository.kt
@@ -0,0 +1,117 @@
+/*
+ * 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.wm.shell.desktopmode.education.data
+
+import android.content.Context
+import android.util.Slog
+import androidx.datastore.core.CorruptionException
+import androidx.datastore.core.DataStore
+import androidx.datastore.core.DataStoreFactory
+import androidx.datastore.core.Serializer
+import androidx.datastore.dataStoreFile
+import com.android.framework.protobuf.InvalidProtocolBufferException
+import com.android.internal.annotations.VisibleForTesting
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
+import java.io.IOException
+import java.io.InputStream
+import java.io.OutputStream
+
+/** Updates data in App-to-Web's education datastore. */
+class AppToWebEducationDatastoreRepository
+@VisibleForTesting
+constructor(private val dataStore: DataStore<WindowingEducationProto>) {
+    constructor(
+        context: Context
+    ) : this(
+        DataStoreFactory.create(
+            serializer = WindowingEducationProtoSerializer,
+            produceFile = { context.dataStoreFile(APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH) }))
+
+    /** Provides dataStore.data flow and handles exceptions thrown during collection */
+    val dataStoreFlow: Flow<WindowingEducationProto> =
+        dataStore.data.catch { exception ->
+            // dataStore.data throws an IOException when an error is encountered when reading data
+            if (exception is IOException) {
+                Slog.e(
+                    TAG,
+                    "Error in reading App-to-Web education related data from datastore," +
+                            "data is stored in a file named" +
+                            "$APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH", exception)
+            } else {
+                throw exception
+            }
+        }
+
+    /**
+     * Reads and returns the [WindowingEducationProto] Proto object from the DataStore. If the
+     * DataStore is empty or there's an error reading, it returns the default value of Proto.
+     */
+    suspend fun windowingEducationProto(): WindowingEducationProto = dataStoreFlow.first()
+
+    /**
+     * Updates [WindowingEducationProto.featureUsedTimestampMillis_] field in datastore with current
+     * timestamp if [isViewed] is true, if not then clears the field.
+     */
+    suspend fun updateFeatureUsedTimestampMillis(isViewed: Boolean) {
+        dataStore.updateData { preferences ->
+            if (isViewed) {
+                preferences
+                    .toBuilder().setFeatureUsedTimestampMillis(System.currentTimeMillis()).build()
+            } else {
+                preferences.toBuilder().clearFeatureUsedTimestampMillis().build()
+            }
+        }
+    }
+
+    /**
+     * Increases [AppToWebEducation.educationShownCount] field by one.
+     */
+    suspend fun updateEducationShownCount() {
+        val currentAppHandleProto = windowingEducationProto().appToWebEducation.toBuilder()
+        currentAppHandleProto
+            .setEducationShownCount(currentAppHandleProto.getEducationShownCount() + 1)
+        dataStore.updateData { preferences ->
+            preferences.toBuilder().setAppToWebEducation(currentAppHandleProto).build()
+        }
+    }
+
+
+    companion object {
+        private const val TAG = "AppToWebEducationDatastoreRepository"
+        private const val APP_TO_WEB_EDUCATION_DATASTORE_FILEPATH = "app_to_web_education.pb"
+
+        object WindowingEducationProtoSerializer : Serializer<WindowingEducationProto> {
+
+            override val defaultValue: WindowingEducationProto =
+                WindowingEducationProto.getDefaultInstance()
+
+            override suspend fun readFrom(input: InputStream): WindowingEducationProto =
+                try {
+                    WindowingEducationProto.parseFrom(input)
+                } catch (exception: InvalidProtocolBufferException) {
+                    throw CorruptionException("Cannot read proto.", exception)
+                }
+
+            override suspend fun writeTo(
+                windowingProto: WindowingEducationProto,
+                output: OutputStream
+            ) = windowingProto.writeTo(output)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
index d29ec53..4cddd01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/education/data/proto/windowing_education.proto
@@ -28,6 +28,8 @@
   oneof education_data {
     // Fields specific to app handle education
     AppHandleEducation app_handle_education = 3;
+    // Fields specific to App-to-Web education
+    AppToWebEducation app_to_web_education = 4;
   }
 
   message AppHandleEducation {
@@ -36,4 +38,9 @@
     // Timestamp of when app_usage_stats was last cached
     optional int64 app_usage_stats_last_update_timestamp_millis = 2;
   }
+
+  message AppToWebEducation {
+    // Number of times education is shown
+    optional int64 education_shown_count = 1;
+  }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
index fc4ed15..d815656 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/persistence/DesktopRepositoryInitializerImpl.kt
@@ -17,7 +17,7 @@
 package com.android.wm.shell.desktopmode.persistence
 
 import android.content.Context
-import com.android.window.flags.Flags
+import android.window.DesktopModeFlags
 import com.android.wm.shell.desktopmode.DesktopRepository
 import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -36,7 +36,7 @@
     @ShellMainThread private val mainCoroutineScope: CoroutineScope,
 ) : DesktopRepositoryInitializer {
     override fun initialize(repository: DesktopRepository) {
-        if (!Flags.enableDesktopWindowingPersistence()) return
+        if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()) return
         //  TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
         mainCoroutineScope.launch {
             val desktop = persistentRepository.readDesktop() ?: return@launch
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index f060158..4aeecbe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -30,6 +30,7 @@
 import android.app.TaskInfo;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.view.Surface;
@@ -152,7 +153,6 @@
         return mCurrentAnimator;
     }
 
-    @SuppressWarnings("unchecked")
     /**
      * Construct and return an animator that animates from the {@param startBounds} to the
      * {@param endBounds} with the given {@param direction}. If {@param direction} is type
@@ -171,6 +171,7 @@
      * leaving PiP to fullscreen, and the {@param endBounds} is the fullscreen bounds before the
      * rotation change.
      */
+    @SuppressWarnings("unchecked")
     @VisibleForTesting
     public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash,
             Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect,
@@ -566,7 +567,7 @@
                     }
                     getSurfaceTransactionHelper()
                             .resetScale(tx, leash, getDestinationBounds())
-                            .crop(tx, leash, getDestinationBounds())
+                            .cropAndPosition(tx, leash, getDestinationBounds())
                             .round(tx, leash, true /* applyCornerRadius */)
                             .shadow(tx, leash, shouldApplyShadowRadius());
                     tx.show(leash);
@@ -590,18 +591,50 @@
             // Just for simplicity we'll interpolate between the source rect hint insets and empty
             // insets to calculate the window crop
             final Rect initialSourceValue;
+            final Rect mainWindowFrame = taskInfo.topActivityMainWindowFrame;
+            final boolean hasNonMatchFrame = mainWindowFrame != null;
+            final boolean changeOrientation =
+                    rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270;
+            final Rect baseBounds = new Rect(baseValue);
+            final Rect startBounds = new Rect(startValue);
+            final Rect endBounds = new Rect(endValue);
             if (isOutPipDirection) {
-                initialSourceValue = new Rect(endValue);
+                // TODO(b/356277166): handle rotation change with activity that provides main window
+                //  frame.
+                if (hasNonMatchFrame && !changeOrientation) {
+                    endBounds.set(mainWindowFrame);
+                }
+                initialSourceValue = new Rect(endBounds);
+            } else if (isInPipDirection) {
+                if (hasNonMatchFrame) {
+                    baseBounds.set(mainWindowFrame);
+                    if (startValue.equals(baseValue)) {
+                        // If the start value is at initial state as in PIP animation, also override
+                        // the start bounds with nonMatchParentBounds.
+                        startBounds.set(mainWindowFrame);
+                    }
+                }
+                initialSourceValue = new Rect(baseBounds);
             } else {
-                initialSourceValue = new Rect(baseValue);
+                // Note that we assume the window bounds always match task bounds in PIP mode.
+                initialSourceValue = new Rect(baseBounds);
+            }
+
+            final Point leashOffset;
+            if (isInPipDirection) {
+                leashOffset = new Point(baseValue.left, baseValue.top);
+            } else if (isOutPipDirection) {
+                leashOffset = new Point(endValue.left, endValue.top);
+            } else {
+                leashOffset = new Point(baseValue.left, baseValue.top);
             }
 
             final Rect rotatedEndRect;
             final Rect lastEndRect;
             final Rect initialContainerRect;
-            if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
-                lastEndRect = new Rect(endValue);
-                rotatedEndRect = new Rect(endValue);
+            if (changeOrientation) {
+                lastEndRect = new Rect(endBounds);
+                rotatedEndRect = new Rect(endBounds);
                 // Rotate the end bounds according to the rotation delta because the display will
                 // be rotated to the same orientation.
                 rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta);
@@ -617,9 +650,9 @@
                 // Crop a Rect matches the aspect ratio and pivots at the center point.
                 // This is done for entering case only.
                 if (isInPipDirection(direction)) {
-                    final float aspectRatio = endValue.width() / (float) endValue.height();
+                    final float aspectRatio = endBounds.width() / (float) endBounds.height();
                     adjustedSourceRectHint.set(PipUtils.getEnterPipWithOverlaySrcRectHint(
-                            startValue, aspectRatio));
+                            startBounds, aspectRatio));
                 }
             } else {
                 adjustedSourceRectHint.set(sourceRectHint);
@@ -644,7 +677,7 @@
 
             // construct new Rect instances in case they are recycled
             return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS,
-                    endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) {
+                    endBounds, new Rect(baseBounds), new Rect(startBounds), new Rect(endBounds)) {
                 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
                 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
 
@@ -668,11 +701,22 @@
                     setCurrentValue(bounds);
                     if (inScaleTransition() || adjustedSourceRectHint.isEmpty()) {
                         if (isOutPipDirection) {
-                            getSurfaceTransactionHelper().crop(tx, leash, end)
-                                    .scale(tx, leash, end, bounds);
+                            // Use the bounds relative to the task leash in case the leash does not
+                            // start from (0, 0).
+                            final Rect relativeEndBounds = new Rect(end);
+                            relativeEndBounds.offset(-leashOffset.x, -leashOffset.y);
+                            getSurfaceTransactionHelper()
+                                    .crop(tx, leash, relativeEndBounds)
+                                    .scale(tx, leash, relativeEndBounds, bounds,
+                                            false /* shouldOffset */);
                         } else {
-                            getSurfaceTransactionHelper().crop(tx, leash, base)
-                                    .scale(tx, leash, base, bounds, angle)
+                            // TODO(b/356277166): add support to specify sourceRectHint with
+                            //  non-match parent activity.
+                            // If there's a PIP resize animation, we should offset the bounds to
+                            // (0, 0) since the window bounds should match the leash bounds in PIP
+                            // mode.
+                            getSurfaceTransactionHelper().cropAndPosition(tx, leash, base)
+                                    .scale(tx, leash, base, bounds, angle, inScaleTransition())
                                     .round(tx, leash, base, bounds)
                                     .shadow(tx, leash, shouldApplyShadowRadius());
                         }
@@ -680,7 +724,7 @@
                         final Rect insets = computeInsets(fraction);
                         getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
                                 adjustedSourceRectHint, initialSourceValue, bounds, insets,
-                                isInPipDirection, fraction);
+                                isInPipDirection, fraction, leashOffset);
                         final Rect sourceBounds = new Rect(initialContainerRect);
                         sourceBounds.inset(insets);
                         getSurfaceTransactionHelper()
@@ -733,8 +777,7 @@
                     getSurfaceTransactionHelper()
                             .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds,
                                     insets, degree, x, y, isOutPipDirection,
-                                    rotationDelta == ROTATION_270 /* clockwise */);
-                    getSurfaceTransactionHelper()
+                                    rotationDelta == ROTATION_270 /* clockwise */)
                             .round(tx, leash, sourceBounds, bounds)
                             .shadow(tx, leash, shouldApplyShadowRadius());
                     if (!handlePipTransaction(leash, tx, bounds, 1f /* alpha */)) {
@@ -772,7 +815,7 @@
                         tx.setPosition(leash, 0, 0);
                         tx.setWindowCrop(leash, 0, 0);
                     } else {
-                        getSurfaceTransactionHelper().crop(tx, leash, destBounds);
+                        getSurfaceTransactionHelper().cropAndPosition(tx, leash, destBounds);
                     }
                     if (mContentOverlay != null) {
                         clearContentOverlay();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index 3d1994c..b02bd0f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -16,8 +16,10 @@
 
 package com.android.wm.shell.pip;
 
+import android.annotation.NonNull;
 import android.content.Context;
 import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.view.Choreographer;
@@ -68,52 +70,102 @@
      * Operates the crop (and position) on a given transaction and leash
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect destinationBounds) {
+    public PipSurfaceTransactionHelper cropAndPosition(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect destinationBounds) {
         tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
                 .setPosition(leash, destinationBounds.left, destinationBounds.top);
         return this;
     }
 
     /**
+     * Operates {@link SurfaceControl.Transaction#setCrop} on a given transaction and leash.
+     *
+     * @param tx the transaction to  apply
+     * @param leash the leash to crop
+     * @param relativeDestinationBounds the bounds to crop, which is relative to the leash
+     *                                  coordinate
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    public PipSurfaceTransactionHelper crop(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect relativeDestinationBounds) {
+        tx.setCrop(leash, relativeDestinationBounds);
+        return this;
+    }
+
+    /**
      * Operates the scale (setMatrix) on a given transaction and leash
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
     public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
             Rect sourceBounds, Rect destinationBounds) {
         mTmpDestinationRectF.set(destinationBounds);
-        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */);
+        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */,
+                true /* shouldOffset */);
     }
 
     /**
      * Operates the scale (setMatrix) on a given transaction and leash
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect sourceBounds, RectF destinationBounds) {
-        return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */);
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull RectF destinationBounds) {
+        return scale(tx, leash, sourceBounds, destinationBounds, 0 /* degrees */,
+                true /* shouldOffset */);
     }
 
     /**
-     * Operates the scale (setMatrix) on a given transaction and leash
+     * Operates the scale (setMatrix) on a given transaction and leash.
+     *
+     * @param shouldOffset {@code true} to offset the leash to (0, 0)
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect sourceBounds, Rect destinationBounds, float degrees) {
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull Rect destinationBounds, boolean shouldOffset) {
         mTmpDestinationRectF.set(destinationBounds);
-        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees);
+        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, 0 /* degrees */, shouldOffset);
+    }
+
+    /**
+     * Operates the scale (setMatrix) on a given transaction and leash.
+     *
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull Rect destinationBounds, float degrees) {
+        return scale(tx, leash, sourceBounds, destinationBounds, degrees, true /* shouldOffset */);
+    }
+
+    /**
+     * Operates the scale (setMatrix) on a given transaction and leash.
+     *
+     * @param shouldOffset {@code true} to offset the leash to (0, 0)
+     * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
+     */
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull Rect destinationBounds, float degrees, boolean shouldOffset) {
+        mTmpDestinationRectF.set(destinationBounds);
+        return scale(tx, leash, sourceBounds, mTmpDestinationRectF, degrees, shouldOffset);
     }
 
     /**
      * Operates the scale (setMatrix) on a given transaction and leash, along with a rotation.
+     *
+     * @param shouldOffset {@code true} to offset the leash to (0, 0)
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper scale(SurfaceControl.Transaction tx, SurfaceControl leash,
-            Rect sourceBounds, RectF destinationBounds, float degrees) {
+    public PipSurfaceTransactionHelper scale(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceBounds,
+            @NonNull RectF destinationBounds, float degrees, boolean shouldOffset) {
         mTmpSourceRectF.set(sourceBounds);
         // We want the matrix to position the surface relative to the screen coordinates so offset
-        // the source to 0,0
-        mTmpSourceRectF.offsetTo(0, 0);
+        // the source to (0, 0) if {@code shouldOffset} is true.
+        if (shouldOffset) {
+            mTmpSourceRectF.offsetTo(0, 0);
+        }
         mTmpDestinationRectF.set(destinationBounds);
         mTmpTransform.setRectToRect(mTmpSourceRectF, mTmpDestinationRectF, Matrix.ScaleToFit.FILL);
         mTmpTransform.postRotate(degrees,
@@ -123,17 +175,19 @@
     }
 
     /**
-     * Operates the scale (setMatrix) on a given transaction and leash
+     * Operates the scale (setMatrix) on a given transaction and leash.
+     *
+     * @param leashOffset the offset of the leash bounds relative to the screen coordinate
      * @return same {@link PipSurfaceTransactionHelper} instance for method chaining
      */
-    public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx,
-            SurfaceControl leash, Rect sourceRectHint,
-            Rect sourceBounds, Rect destinationBounds, Rect insets,
-            boolean isInPipDirection, float fraction) {
+    public PipSurfaceTransactionHelper scaleAndCrop(@NonNull SurfaceControl.Transaction tx,
+            @NonNull SurfaceControl leash, @NonNull Rect sourceRectHint, @NonNull Rect sourceBounds,
+            @NonNull Rect destinationBounds, @NonNull Rect insets, boolean isInPipDirection,
+            float fraction, @NonNull Point leashOffset) {
         mTmpDestinationRect.set(sourceBounds);
         // Similar to {@link #scale}, we want to position the surface relative to the screen
-        // coordinates so offset the bounds to 0,0
-        mTmpDestinationRect.offsetTo(0, 0);
+        // coordinates so offset the bounds relative to the leash.
+        mTmpDestinationRect.offset(-leashOffset.x, -leashOffset.y);
         mTmpDestinationRect.inset(insets);
         // Scale to the bounds no smaller than the destination and offset such that the top/left
         // of the scaled inset source rect aligns with the top/left of the destination bounds
@@ -152,13 +206,13 @@
             scale = Math.max((float) destinationBounds.width() / sourceBounds.width(),
                     (float) destinationBounds.height() / sourceBounds.height());
         }
-        float left = destinationBounds.left - insets.left * scale;
-        float top = destinationBounds.top - insets.top * scale;
+        float left = destinationBounds.left - mTmpDestinationRect.left * scale;
+        float top = destinationBounds.top - mTmpDestinationRect.top * scale;
         if (scale == 1) {
             // Work around the 1 pixel off error by rounding the position down at very beginning.
             // We noticed such error from flicker tests, not visually.
-            left = sourceBounds.left;
-            top = sourceBounds.top;
+            left = leashOffset.x;
+            top = leashOffset.y;
         }
         mTmpTransform.setScale(scale, scale);
         tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index b4e0329..c4e63df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -960,7 +960,7 @@
         final SurfaceControl.Transaction boundsChangeTx =
                 mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
-                .crop(boundsChangeTx, mLeash, destinationBounds)
+                .cropAndPosition(boundsChangeTx, mLeash, destinationBounds)
                 .round(boundsChangeTx, mLeash, true /* applyCornerRadius */);
 
         mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
@@ -988,7 +988,7 @@
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
                 .resetScale(tx, mLeash, destinationBounds)
-                .crop(tx, mLeash, destinationBounds)
+                .cropAndPosition(tx, mLeash, destinationBounds)
                 .round(tx, mLeash, isInPip());
         // The animation is finished in the Launcher and here we directly apply the final touch.
         applyEnterPipSyncTransaction(destinationBounds, () -> {
@@ -1525,7 +1525,7 @@
         mPipBoundsState.setBounds(toBounds);
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
-                .crop(tx, mLeash, toBounds)
+                .cropAndPosition(tx, mLeash, toBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
         if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
@@ -1628,7 +1628,7 @@
             Rect destinationBounds) {
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
-                .crop(tx, mLeash, destinationBounds)
+                .cropAndPosition(tx, mLeash, destinationBounds)
                 .resetScale(tx, mLeash, destinationBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
         return tx;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 6da3995..28b91c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -30,7 +30,6 @@
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
@@ -45,6 +44,7 @@
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
 
 import android.annotation.IntDef;
 import android.app.ActivityManager;
@@ -551,7 +551,7 @@
                 }
                 // Reset the scale with bounds change synchronously.
                 if (hasValidLeash) {
-                    mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
+                    mSurfaceTransactionHelper.cropAndPosition(tx, leash, destinationBounds)
                             .resetScale(tx, leash, destinationBounds)
                             .round(tx, leash, true /* applyCornerRadius */);
                     final Rect appBounds = mPipOrganizer.mAppBounds;
@@ -588,7 +588,8 @@
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                             "%s: Destination bounds were changed during animation", TAG);
                     rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
-                    mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
+                    mSurfaceTransactionHelper.cropAndPosition(mFinishTransaction, leash,
+                            finishBounds);
                 }
             }
             mFinishTransaction = null;
@@ -1068,6 +1069,11 @@
         mPipBoundsState.mayUseCachedLauncherShelfHeight();
         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
         final Rect currentBounds = pipChange.getStartAbsBounds();
+        // The app bounds should offset relative to the task leash to make the center calculation
+        // correctly.
+        final Rect relativeAppBounds = new Rect(taskInfo.topActivityMainWindowFrame != null
+                ? taskInfo.topActivityMainWindowFrame : currentBounds);
+        relativeAppBounds.offset(-currentBounds.left, -currentBounds.top);
 
         int rotationDelta = deltaRotation(startRotation, endRotation);
         Rect sourceHintRect = mPipOrganizer.takeSwipeSourceRectHint();
@@ -1089,6 +1095,8 @@
         if (taskInfo.pictureInPictureParams != null
                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
+            // TODO(b/356277166): add support to swipe PIP to home with
+            //  non-match parent activity.
             handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
                     sourceHintRect, destinationBounds, taskInfo);
             return;
@@ -1119,8 +1127,8 @@
                 // TODO(b/272819817): cleanup the null-check and extra logging.
                 final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
                 if (hasTopActivityInfo && mFixedRotationState != FIXED_ROTATION_TRANSITION) {
-                    animator.setAppIconContentOverlay(
-                            mContext, currentBounds, destinationBounds, taskInfo.topActivityInfo,
+                    animator.setAppIconContentOverlay(mContext, relativeAppBounds,
+                            destinationBounds, taskInfo.topActivityInfo,
                             mPipBoundsState.getLauncherState().getAppIconSizePx());
                 } else {
                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
@@ -1149,14 +1157,14 @@
                 animationDuration = 0;
             }
             mSurfaceTransactionHelper
-                    .crop(finishTransaction, leash, destinationBounds)
+                    .cropAndPosition(finishTransaction, leash, destinationBounds)
                     .round(finishTransaction, leash, true /* applyCornerRadius */);
             // Always reset to bounds animation type afterwards.
             setEnterAnimationType(ANIM_TYPE_BOUNDS);
         } else {
             throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
         }
-        mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds);
+        mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), relativeAppBounds);
         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(animationDuration);
@@ -1340,11 +1348,11 @@
                 "%s: Update pip for unhandled transition, change=%s, destBounds=%s, isInPip=%b",
                 TAG, pipChange, destBounds, isInPip);
         mSurfaceTransactionHelper
-                .crop(startTransaction, leash, destBounds)
+                .cropAndPosition(startTransaction, leash, destBounds)
                 .round(startTransaction, leash, isInPip)
                 .shadow(startTransaction, leash, isInPip);
         mSurfaceTransactionHelper
-                .crop(finishTransaction, leash, destBounds)
+                .cropAndPosition(finishTransaction, leash, destBounds)
                 .round(finishTransaction, leash, isInPip)
                 .shadow(finishTransaction, leash, isInPip);
         // Make sure the PiP keeps invisible if it was faded out. If it needs to fade in, that will
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
index 7a0e669..d3ae411 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java
@@ -23,7 +23,6 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.transitTypeToString;
 
 import static com.android.wm.shell.common.pip.PipMenuController.ALPHA_NO_CHANGE;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
@@ -35,6 +34,7 @@
 import static com.android.wm.shell.pip.PipTransitionState.UNDEFINED;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
 
 import android.animation.AnimationHandler;
 import android.animation.Animator;
@@ -338,7 +338,7 @@
         final Rect pipBounds = mPipBoundsState.getBounds();
         mSurfaceTransactionHelper
                 .resetScale(startTransaction, pipLeash, pipBounds)
-                .crop(startTransaction, pipLeash, pipBounds)
+                .cropAndPosition(startTransaction, pipLeash, pipBounds)
                 .shadow(startTransaction, pipLeash, false);
 
         final SurfaceControl.Transaction transaction = mTransactionFactory.getTransaction();
@@ -420,7 +420,7 @@
 
         mSurfaceTransactionHelper
                 .resetScale(finishTransaction, leash, pipBounds)
-                .crop(finishTransaction, leash, pipBounds)
+                .cropAndPosition(finishTransaction, leash, pipBounds)
                 .shadow(finishTransaction, leash, false);
 
         final Rect currentBounds = pipChange.getStartAbsBounds();
@@ -443,7 +443,7 @@
                 SurfaceControl.Transaction tx = mTransactionFactory.getTransaction();
                 mSurfaceTransactionHelper
                         .resetScale(tx, leash, pipBounds)
-                        .crop(tx, leash, pipBounds)
+                        .cropAndPosition(tx, leash, pipBounds)
                         .shadow(tx, leash, false);
                 mShellTaskOrganizer.applyTransaction(resizePipWct);
                 tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index d565776..012dabb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.pip2.animation;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -27,13 +28,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
 
 /**
  * Animator that handles any resize related animation for PIP.
  */
-public class PipResizeAnimator extends ValueAnimator
-        implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener{
+public class PipResizeAnimator extends ValueAnimator {
     @NonNull
     private final Context mContext;
     @NonNull
@@ -61,9 +62,47 @@
     private final Rect mAnimatedRect = new Rect();
     private final float mDelta;
 
-    private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+    private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
             mSurfaceControlTransactionFactory;
 
+    private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            super.onAnimationStart(animation);
+            if (mAnimationStartCallback != null) {
+                mAnimationStartCallback.run();
+            }
+            if (mStartTx != null) {
+                setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
+                mStartTx.apply();
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            super.onAnimationEnd(animation);
+            if (mFinishTx != null) {
+                setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
+            }
+            if (mAnimationEndCallback != null) {
+                mAnimationEndCallback.run();
+            }
+        }
+    };
+
+    private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener =
+            new AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+                    final SurfaceControl.Transaction tx =
+                            mSurfaceControlTransactionFactory.getTransaction();
+                    final float fraction = getAnimatedFraction();
+                    final float degrees = (1.0f - fraction) * mDelta;
+                    setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
+                    tx.apply();
+                }
+            };
+
     public PipResizeAnimator(@NonNull Context context,
             @NonNull SurfaceControl leash,
             @Nullable SurfaceControl.Transaction startTransaction,
@@ -89,8 +128,8 @@
         mRectEvaluator = new RectEvaluator(mAnimatedRect);
 
         setObjectValues(startBounds, endBounds);
-        addListener(this);
-        addUpdateListener(this);
+        addListener(mAnimatorListener);
+        addUpdateListener(mAnimatorUpdateListener);
         setEvaluator(mRectEvaluator);
         setDuration(duration);
     }
@@ -103,26 +142,6 @@
         mAnimationEndCallback = runnable;
     }
 
-    @Override
-    public void onAnimationStart(@NonNull Animator animation) {
-        if (mAnimationStartCallback != null) {
-            mAnimationStartCallback.run();
-        }
-        if (mStartTx != null) {
-            setBoundsAndRotation(mStartTx, mLeash, mBaseBounds, mStartBounds, mDelta);
-            mStartTx.apply();
-        }
-    }
-
-    @Override
-    public void onAnimationUpdate(@NonNull ValueAnimator animation) {
-        final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
-        final float fraction = getAnimatedFraction();
-        final float degrees = (1.0f - fraction) * mDelta;
-        setBoundsAndRotation(tx, mLeash, mBaseBounds, mAnimatedRect, degrees);
-        tx.apply();
-    }
-
     /**
      * Set a proper transform matrix for a leash to move it to given bounds with a certain rotation.
      *
@@ -130,7 +149,7 @@
      * @param targetBounds bounds to which we are scaling the leash.
      * @param degrees degrees of rotation - counter-clockwise is positive by convention.
      */
-    public static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
+    private static void setBoundsAndRotation(SurfaceControl.Transaction tx, SurfaceControl leash,
             Rect baseBounds, Rect targetBounds, float degrees) {
         Matrix transformTensor = new Matrix();
         final float[] mMatrixTmp = new float[9];
@@ -144,19 +163,9 @@
         tx.setMatrix(leash, transformTensor, mMatrixTmp);
     }
 
-    @Override
-    public void onAnimationEnd(@NonNull Animator animation) {
-        if (mFinishTx != null) {
-            setBoundsAndRotation(mFinishTx, mLeash, mBaseBounds, mEndBounds, 0f);
-        }
-        if (mAnimationEndCallback != null) {
-            mAnimationEndCallback.run();
-        }
+    @VisibleForTesting
+    void setSurfaceControlTransactionFactory(@NonNull
+            PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+        mSurfaceControlTransactionFactory = factory;
     }
-
-    @Override
-    public void onAnimationCancel(@NonNull Animator animation) {}
-
-    @Override
-    public void onAnimationRepeat(@NonNull Animator animation) {}
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 245829e..371bdd5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -45,4 +45,7 @@
 
     /** A task has moved to front. */
     oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo);
+
+    /** A task info has changed. */
+    oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 6086801..faa2015 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -47,7 +47,6 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.protolog.ProtoLog;
-import com.android.window.flags.Flags;
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
@@ -289,6 +288,11 @@
     }
 
     @Override
+    public void onTaskChangedThroughTransition(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+        notifyTaskInfoChanged(taskInfo);
+    }
+
+    @Override
     public void onTaskMovedToFrontThroughTransition(
             ActivityManager.RunningTaskInfo runningTaskInfo) {
         notifyTaskMovedToFront(runningTaskInfo);
@@ -355,6 +359,19 @@
         }
     }
 
+    private void notifyTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        if (mListener == null
+                || !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
+                || taskInfo.realActivity == null) {
+            return;
+        }
+        try {
+            mListener.onTaskInfoChanged(taskInfo);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed call onTaskInfoChanged", e);
+        }
+    }
+
     private void notifyTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
         if (mListener == null
                 || !DesktopModeFlags.ENABLE_TASK_STACK_OBSERVER_IN_SHELL.isTrue()
@@ -426,7 +443,7 @@
                 // If task has their app bounds set to null which happens after reboot, set the
                 // app bounds to persisted lastFullscreenBounds. Also set the position in parent
                 // to the top left of the bounds.
-                if (Flags.enableDesktopWindowingPersistence()
+                if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue()
                         && taskInfo.configuration.windowConfiguration.getAppBounds() == null) {
                     taskInfo.configuration.windowConfiguration.setAppBounds(
                             taskInfo.lastNonFullscreenBounds);
@@ -636,6 +653,11 @@
             public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
                 mListener.call(l -> l.onTaskMovedToFront(taskInfo));
             }
+
+            @Override
+            public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+                mListener.call(l -> l.onTaskInfoChanged(taskInfo));
+            }
         };
 
         public IRecentTasksImpl(RecentTasksController controller) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 1af99f9..d28a462 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -20,8 +20,9 @@
 import android.os.IBinder
 import android.util.ArrayMap
 import android.view.SurfaceControl
-import android.window.TransitionInfo
+import android.view.WindowManager.TRANSIT_CHANGE
 import android.window.DesktopModeFlags
+import android.window.TransitionInfo
 import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
@@ -69,8 +70,10 @@
                 // Find the first task that is opening, this should be the one at the front after
                 // the transition
                 if (TransitionUtil.isOpeningType(change.mode)) {
-                    notifyTaskStackTransitionObserverListeners(taskInfo)
+                    notifyOnTaskMovedToFront(taskInfo)
                     break
+                } else if (change.mode == TRANSIT_CHANGE) {
+                    notifyOnTaskChanged(taskInfo)
                 }
             }
         }
@@ -95,15 +98,23 @@
         taskStackTransitionObserverListeners.remove(taskStackTransitionObserverListener)
     }
 
-    private fun notifyTaskStackTransitionObserverListeners(taskInfo: RunningTaskInfo) {
+    private fun notifyOnTaskMovedToFront(taskInfo: RunningTaskInfo) {
         taskStackTransitionObserverListeners.forEach { (listener, executor) ->
             executor.execute { listener.onTaskMovedToFrontThroughTransition(taskInfo) }
         }
     }
 
+    private fun notifyOnTaskChanged(taskInfo: RunningTaskInfo) {
+        taskStackTransitionObserverListeners.forEach { (listener, executor) ->
+            executor.execute { listener.onTaskChangedThroughTransition(taskInfo) }
+        }
+    }
+
     /** Listener to use to get updates regarding task stack from this observer */
     interface TaskStackTransitionObserverListener {
         /** Called when a task is moved to front. */
         fun onTaskMovedToFrontThroughTransition(taskInfo: RunningTaskInfo) {}
+        /** Called when a task info has changed. */
+        fun onTaskChangedThroughTransition(taskInfo: RunningTaskInfo) {}
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 839bb4e..cc0e1df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -30,7 +30,6 @@
 import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 
@@ -73,6 +72,7 @@
 import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
index a1a9ca9..4ea4613 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java
@@ -136,6 +136,7 @@
         @NonNull final Animation mAnim;
         @Nullable final Point mPosition;
         @Nullable final Rect mClipRect;
+        @Nullable private final Rect mAnimClipRect;
         final float mCornerRadius;
         final boolean mIsActivity;
 
@@ -147,6 +148,7 @@
             mPosition = (position != null && (position.x != 0 || position.y != 0))
                     ? position : null;
             mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null;
+            mAnimClipRect = mClipRect != null ? new Rect() : null;
             mCornerRadius = cornerRadius;
             mIsActivity = isActivity;
         }
@@ -169,18 +171,26 @@
             t.setAlpha(leash, transformation.getAlpha());
 
             if (mClipRect != null) {
-                Rect clipRect = mClipRect;
+                boolean needCrop = false;
+                mAnimClipRect.set(mClipRect);
+                if (transformation.hasClipRect()
+                        && com.android.window.flags.Flags.respectAnimationClip()) {
+                    mAnimClipRect.intersectUnchecked(transformation.getClipRect());
+                    needCrop = true;
+                }
                 final Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
                 if (!extensionInsets.equals(Insets.NONE)) {
                     // Clip out any overflowing edge extension.
-                    clipRect = new Rect(mClipRect);
-                    clipRect.inset(extensionInsets);
-                    t.setCrop(leash, clipRect);
+                    mAnimClipRect.inset(extensionInsets);
+                    needCrop = true;
                 }
                 if (mCornerRadius > 0 && mAnim.hasRoundedCorners()) {
                     // Rounded corner can only be applied if a crop is set.
-                    t.setCrop(leash, clipRect);
                     t.setCornerRadius(leash, mCornerRadius);
+                    needCrop = true;
+                }
+                if (needCrop) {
+                    t.setCrop(leash, mAnimClipRect);
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
index a27c14bd..4feb475 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java
@@ -24,7 +24,6 @@
 import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -34,6 +33,7 @@
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
 import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_OPEN;
+import static com.android.wm.shell.transition.Transitions.transitTypeToString;
 
 import android.annotation.ColorInt;
 import android.annotation.NonNull;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 7c9cd08..1d456ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -29,7 +29,6 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManager.fixScale;
-import static android.view.WindowManager.transitTypeToString;
 import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW;
 import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -725,7 +724,7 @@
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
         info.setUnreleasedWarningCallSiteForAllSurfaces("Transitions.onTransitionReady");
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
-                info.getDebugId(), transitionToken, info);
+                info.getDebugId(), transitionToken, info.toString("    " /* prefix */));
         int activeIdx = findByToken(mPendingTransitions, transitionToken);
         if (activeIdx < 0) {
             final ActiveTransition existing = mKnownTransitions.get(transitionToken);
@@ -1847,6 +1846,40 @@
         }
     }
 
+    /**
+     * Like WindowManager#transitTypeToString(), but also covers known custom transition types as
+     * well.
+     */
+    public static String transitTypeToString(int transitType) {
+        if (transitType < TRANSIT_FIRST_CUSTOM) {
+            return WindowManager.transitTypeToString(transitType);
+        }
+
+        String typeStr = switch (transitType) {
+            case TRANSIT_EXIT_PIP -> "EXIT_PIP";
+            case TRANSIT_EXIT_PIP_TO_SPLIT -> "EXIT_PIP_TO_SPLIT";
+            case TRANSIT_REMOVE_PIP -> "REMOVE_PIP";
+            case TRANSIT_SPLIT_SCREEN_PAIR_OPEN -> "SPLIT_SCREEN_PAIR_OPEN";
+            case TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE -> "SPLIT_SCREEN_OPEN_TO_SIDE";
+            case TRANSIT_SPLIT_DISMISS_SNAP -> "SPLIT_DISMISS_SNAP";
+            case TRANSIT_SPLIT_DISMISS -> "SPLIT_DISMISS";
+            case TRANSIT_MAXIMIZE -> "MAXIMIZE";
+            case TRANSIT_RESTORE_FROM_MAXIMIZE -> "RESTORE_FROM_MAXIMIZE";
+            case TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP -> "DESKTOP_MODE_START_DRAG_TO_DESKTOP";
+            case TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP -> "DESKTOP_MODE_END_DRAG_TO_DESKTOP";
+            case TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP ->
+                    "DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP";
+            case TRANSIT_DESKTOP_MODE_TOGGLE_RESIZE -> "DESKTOP_MODE_TOGGLE_RESIZE";
+            case TRANSIT_RESIZE_PIP -> "RESIZE_PIP";
+            case TRANSIT_TASK_FRAGMENT_DRAG_RESIZE -> "TASK_FRAGMENT_DRAG_RESIZE";
+            case TRANSIT_SPLIT_PASSTHROUGH -> "SPLIT_PASSTHROUGH";
+            case TRANSIT_CLEANUP_PIP_EXIT -> "CLEANUP_PIP_EXIT";
+            case TRANSIT_MINIMIZE -> "MINIMIZE";
+            default -> "";
+        };
+        return typeStr + "(FIRST_CUSTOM+" + (transitType - TRANSIT_FIRST_CUSTOM) + ")";
+    }
+
     private static boolean getShellTransitEnabled() {
         try {
             if (AppGlobals.getPackageManager().hasSystemFeature(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 3946b61..c954673 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -180,12 +180,13 @@
         // all other cases, it is expected that the transition handler positions and crops the task
         // in order to allow the handler time to animate before the task before the final
         // position and crop are set.
-        final boolean shouldSetTaskPositionAndCrop = mTaskDragResizer.isResizingOrAnimating();
+        final boolean shouldSetTaskVisibilityPositionAndCrop =
+                mTaskDragResizer.isResizingOrAnimating();
         // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
         // synced with the buffer transaction (that draws the View). Both will be shown on screen
         // at the same, whereas applying them independently causes flickering. See b/270202228.
         relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
-                shouldSetTaskPositionAndCrop, hasGlobalFocus);
+                shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
     }
 
     @VisibleForTesting
@@ -193,7 +194,7 @@
             RelayoutParams relayoutParams,
             ActivityManager.RunningTaskInfo taskInfo,
             boolean applyStartTransactionOnDraw,
-            boolean setTaskCropAndPosition,
+            boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean isStatusBarVisible,
             boolean isKeyguardVisibleAndOccluded,
             InsetsState displayInsetsState,
@@ -206,7 +207,7 @@
                 ? R.dimen.freeform_decor_shadow_focused_thickness
                 : R.dimen.freeform_decor_shadow_unfocused_thickness;
         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
-        relayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
+        relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
         relayoutParams.mIsCaptionVisible = taskInfo.isFreeform()
                 || (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
 
@@ -234,7 +235,7 @@
     @SuppressLint("MissingPermission")
     void relayout(RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         final boolean isFreeform =
                 taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -246,7 +247,8 @@
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
         updateRelayoutParams(mRelayoutParams, taskInfo, applyStartTransactionOnDraw,
-                setTaskCropAndPosition, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded,
+                shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
+                mIsKeyguardVisibleAndOccluded,
                 mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index a3324cc6..e1683f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -103,6 +103,7 @@
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
@@ -113,6 +114,7 @@
 import com.android.wm.shell.desktopmode.DesktopWallpaperActivity;
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
+import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.shared.FocusTransitionListener;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
@@ -130,7 +132,6 @@
 import com.android.wm.shell.transition.FocusTransitionObserver;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
 import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -177,11 +178,11 @@
     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
     private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter;
     private final AppHandleEducationController mAppHandleEducationController;
+    private final AppToWebEducationController mAppToWebEducationController;
     private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
     private boolean mTransitionDragActive;
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
-    private DesktopStatusBarInputLayerSupplier mStatusBarInputLayerSupplier;
 
     private final ExclusionRegionListener mExclusionRegionListener =
             new ExclusionRegionListenerImpl();
@@ -251,6 +252,7 @@
             MultiInstanceHelper multiInstanceHelper,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             AppHandleEducationController appHandleEducationController,
+            AppToWebEducationController appToWebEducationController,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
             FocusTransitionObserver focusTransitionObserver,
@@ -284,6 +286,7 @@
                 interactionJankMonitor,
                 desktopTasksLimiter,
                 appHandleEducationController,
+                appToWebEducationController,
                 windowDecorCaptionHandleRepository,
                 activityOrientationChangeHandler,
                 new TaskPositionerFactory(),
@@ -321,6 +324,7 @@
             InteractionJankMonitor interactionJankMonitor,
             Optional<DesktopTasksLimiter> desktopTasksLimiter,
             AppHandleEducationController appHandleEducationController,
+            AppToWebEducationController appToWebEducationController,
             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
             Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
             TaskPositionerFactory taskPositionerFactory,
@@ -356,6 +360,7 @@
         mInteractionJankMonitor = interactionJankMonitor;
         mDesktopTasksLimiter = desktopTasksLimiter;
         mAppHandleEducationController = appHandleEducationController;
+        mAppToWebEducationController = appToWebEducationController;
         mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
         mActivityOrientationChangeHandler = activityOrientationChangeHandler;
         mAssistContentRequester = assistContentRequester;
@@ -420,11 +425,7 @@
                         return Unit.INSTANCE;
                     });
         }
-        if (Flags.enableHandleInputFix()) {
-            mStatusBarInputLayerSupplier =
-                    new DesktopStatusBarInputLayerSupplier(mContext, mMainHandler);
-            mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
-        }
+        mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
     }
 
     @Override
@@ -480,7 +481,6 @@
             removeTaskFromEventReceiver(oldTaskInfo.displayId);
             incrementEventReceiverTasks(taskInfo.displayId);
         }
-        decoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
         decoration.relayout(taskInfo, decoration.mHasGlobalFocus);
         mActivityOrientationChangeHandler.ifPresent(handler ->
                 handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
@@ -519,7 +519,6 @@
         if (decoration == null) {
             createWindowDecoration(taskInfo, taskSurface, startT, finishT);
         } else {
-            decoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
             decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
                     false /* shouldSetTaskPositionAndCrop */,
                     mFocusTransitionObserver.hasGlobalFocus(taskInfo));
@@ -673,7 +672,7 @@
         decoration.closeHandleMenu();
         // When the app enters split-select, the handle will no longer be visible, meaning
         // we shouldn't receive input for it any longer.
-        decoration.detachStatusBarInputLayer();
+        decoration.disposeStatusBarInputLayer();
         mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
     }
 
@@ -1314,8 +1313,8 @@
                         // should not be receiving any input.
                         if (resultType == TO_SPLIT_LEFT_INDICATOR
                                 || resultType == TO_SPLIT_RIGHT_INDICATOR) {
-                            relevantDecor.detachStatusBarInputLayer();
-                            // We should also detach the other split task's input layer if
+                            relevantDecor.disposeStatusBarInputLayer();
+                            // We should also dispose the other split task's input layer if
                             // applicable.
                             final int splitPosition = mSplitScreenController
                                     .getSplitPosition(relevantDecor.mTaskInfo.taskId);
@@ -1328,7 +1327,7 @@
                                         mSplitScreenController.getTaskInfo(oppositePosition);
                                 if (oppositeTaskInfo != null) {
                                     mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
-                                            .detachStatusBarInputLayer();
+                                            .disposeStatusBarInputLayer();
                                 }
                             }
                         }
@@ -1574,11 +1573,14 @@
             onManageWindows(windowDecoration);
             return Unit.INSTANCE;
         });
+        windowDecoration.setOnChangeAspectRatioClickListener(() -> {
+            CompatUIController.launchUserAspectRatioSettings(mContext, taskInfo);
+            return Unit.INSTANCE;
+        });
         windowDecoration.setCaptionListeners(
                 touchEventListener, touchEventListener, touchEventListener, touchEventListener);
         windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
         windowDecoration.setDragPositioningCallback(taskPositioner);
-        windowDecoration.setStatusBarInputLayer(getStatusBarInputLayer(taskInfo));
         windowDecoration.relayout(taskInfo, startT, finishT,
                 false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
                 mFocusTransitionObserver.hasGlobalFocus(taskInfo));
@@ -1587,18 +1589,6 @@
         }
     }
 
-    /** Decide which cached status bar input layer should be used for a decoration. */
-    private AdditionalSystemViewContainer getStatusBarInputLayer(
-            RunningTaskInfo taskInfo
-    ) {
-        if (mStatusBarInputLayerSupplier == null) return null;
-        return mStatusBarInputLayerSupplier.getStatusBarInputLayer(
-                taskInfo,
-                mSplitScreenController.getSplitPosition(taskInfo.taskId),
-                mSplitScreenController.isLeftRightSplit()
-        );
-    }
-
     private RunningTaskInfo getOtherSplitTask(int taskId) {
         @SplitPosition int remainingTaskPosition = mSplitScreenController
                 .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 9f37358..d97632a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -106,7 +106,6 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 import com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer;
 import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer;
 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
 import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -155,6 +154,7 @@
     private Function0<Unit> mOnToSplitscreenClickListener;
     private Function0<Unit> mOnNewWindowClickListener;
     private Function0<Unit> mOnManageWindowsClickListener;
+    private Function0<Unit> mOnChangeAspectRatioClickListener;
     private DragPositioningCallback mDragPositioningCallback;
     private DragResizeInputListener mDragResizeListener;
     private Runnable mCurrentViewHostRunnable = null;
@@ -206,7 +206,6 @@
     private final MultiInstanceHelper mMultiInstanceHelper;
     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
     private final DesktopRepository mDesktopRepository;
-    private AdditionalSystemViewContainer mStatusBarInputLayer;
 
     DesktopModeWindowDecoration(
             Context context,
@@ -366,6 +365,11 @@
         mOnManageWindowsClickListener = listener;
     }
 
+    /** Registers a listener to be called when the aspect ratio action is triggered. */
+    void setOnChangeAspectRatioClickListener(Function0<Unit> listener) {
+        mOnChangeAspectRatioClickListener = listener;
+    }
+
     void setCaptionListeners(
             View.OnClickListener onCaptionButtonClickListener,
             View.OnTouchListener onCaptionTouchListener,
@@ -392,18 +396,25 @@
     @Override
     void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus) {
         final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
-        // The crop and position of the task should only be set when a task is fluid resizing. In
-        // all other cases, it is expected that the transition handler positions and crops the task
-        // in order to allow the handler time to animate before the task before the final
-        // position and crop are set.
-        final boolean shouldSetTaskPositionAndCrop = !DesktopModeStatus.isVeiledResizeEnabled()
-                && mTaskDragResizer.isResizingOrAnimating();
+        // The visibility, crop and position of the task should only be set when a task is
+        // fluid resizing. In all other cases, it is expected that the transition handler sets
+        // those task properties to allow the handler time to animate with full control of the task
+        // leash. In general, allowing the window decoration to set any of these is likely to cause
+        // incorrect frames and flickering because relayouts from TaskListener#onTaskInfoChanged
+        // aren't synchronized with shell transition callbacks, so if they come too early it
+        // might show/hide or crop the task at a bad time.
+        // Fluid resizing is exempt from this because it intentionally doesn't use shell
+        // transitions to resize the task, so onTaskInfoChanged relayouts is the only way to make
+        // sure the crop is set correctly.
+        final boolean shouldSetTaskVisibilityPositionAndCrop =
+                !DesktopModeStatus.isVeiledResizeEnabled()
+                        && mTaskDragResizer.isResizingOrAnimating();
         // For headers only (i.e. in freeform): use |applyStartTransactionOnDraw| so that the
         // transaction (that applies task crop) is synced with the buffer transaction (that draws
         // the View). Both will be shown on screen at the same, whereas applying them independently
         // causes flickering. See b/270202228.
         final boolean applyTransactionOnDraw = taskInfo.isFreeform();
-        relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskPositionAndCrop,
+        relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
                 hasGlobalFocus);
         if (!applyTransactionOnDraw) {
             t.apply();
@@ -430,19 +441,19 @@
 
     void relayout(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         Trace.beginSection("DesktopModeWindowDecoration#relayout");
         if (taskInfo.isFreeform()) {
             // The Task is in Freeform mode -> show its header in sync since it's an integral part
             // of the window itself - a delayed header might cause bad UX.
             relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw,
-                    shouldSetTaskPositionAndCrop, hasGlobalFocus);
+                    shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
         } else {
             // The Task is outside Freeform mode -> allow the handle view to be delayed since the
             // handle is just a small addition to the window.
             relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw,
-                    shouldSetTaskPositionAndCrop, hasGlobalFocus);
+                    shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
         }
         Trace.endSection();
     }
@@ -450,12 +461,12 @@
     /** Run the whole relayout phase immediately without delay. */
     private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         // Clear the current ViewHost runnable as we will update the ViewHost here
         clearCurrentViewHostRunnable();
         updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw,
-                shouldSetTaskPositionAndCrop, hasGlobalFocus);
+                shouldSetTaskVisibilityPositionAndCrop, hasGlobalFocus);
         if (mResult.mRootView != null) {
             updateViewHost(mRelayoutParams, startT, mResult);
         }
@@ -477,7 +488,7 @@
      */
     private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         if (applyStartTransactionOnDraw) {
             throw new IllegalArgumentException(
@@ -486,7 +497,7 @@
         // Clear the current ViewHost runnable as we will update the ViewHost here
         clearCurrentViewHostRunnable();
         updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT,
-                false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop,
+                false /* applyStartTransactionOnDraw */, shouldSetTaskVisibilityPositionAndCrop,
                 hasGlobalFocus);
         if (mResult.mRootView == null) {
             // This means something blocks the window decor from showing, e.g. the task is hidden.
@@ -501,10 +512,9 @@
     @SuppressLint("MissingPermission")
     private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop,
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean hasGlobalFocus) {
         Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces");
-
         if (Flags.enableDesktopWindowingAppToWeb()) {
             setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
         }
@@ -526,9 +536,9 @@
         final boolean inFullImmersive = mDesktopRepository
                 .isTaskInFullImmersiveState(taskInfo.taskId);
         updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
-                shouldSetTaskPositionAndCrop, mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded,
-                inFullImmersive, mDisplayController.getInsetsState(taskInfo.displayId),
-                hasGlobalFocus);
+                shouldSetTaskVisibilityPositionAndCrop, mIsStatusBarVisible,
+                mIsKeyguardVisibleAndOccluded, inFullImmersive,
+                mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus);
 
         final WindowDecorLinearLayout oldRootView = mResult.mRootView;
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
@@ -546,17 +556,17 @@
         if (mResult.mRootView == null) {
             // This means something blocks the window decor from showing, e.g. the task is hidden.
             // Nothing is set up in this case including the decoration surface.
-            if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+            if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
                 notifyNoCaptionHandle();
             }
             mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
-            detachStatusBarInputLayer();
+            disposeStatusBarInputLayer();
             Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces
             return;
         }
 
         if (oldRootView != mResult.mRootView) {
-            detachStatusBarInputLayer();
+            disposeStatusBarInputLayer();
             mWindowDecorViewHolder = createViewHolder();
         }
         Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
@@ -565,7 +575,7 @@
         if (isAppHandle(mWindowDecorViewHolder)) {
             position.set(determineHandlePosition());
         }
-        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
             notifyCaptionStateChanged();
         }
 
@@ -574,9 +584,6 @@
                     mTaskInfo, position, mResult.mCaptionWidth, mResult.mCaptionHeight,
                     isCaptionVisible()
             ));
-            if (mStatusBarInputLayer != null) {
-                asAppHandle(mWindowDecorViewHolder).bindStatusBarInputLayer(mStatusBarInputLayer);
-            }
         } else {
             mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData(
                     mTaskInfo,
@@ -708,7 +715,7 @@
 
     private void notifyCaptionStateChanged() {
         // TODO: b/366159408 - Ensure bounds sent with notification account for RTL mode.
-        if (!canEnterDesktopMode(mContext) || !Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (!canEnterDesktopMode(mContext) || !isEducationEnabled()) {
             return;
         }
         if (!isCaptionVisible()) {
@@ -717,7 +724,7 @@
             // App handle is visible since `mWindowDecorViewHolder` is of type
             // [AppHandleViewHolder].
             final CaptionState captionState = new CaptionState.AppHandle(mTaskInfo,
-                    isHandleMenuActive(), getCurrentAppHandleBounds());
+                    isHandleMenuActive(), getCurrentAppHandleBounds(), isCapturedLinkAvailable());
             mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
         } else {
             // App header is visible since `mWindowDecorViewHolder` is of type
@@ -730,8 +737,12 @@
         }
     }
 
+    private boolean isCapturedLinkAvailable() {
+        return mCapturedLink != null && !mCapturedLink.mExpired;
+    }
+
     private void notifyNoCaptionHandle() {
-        if (!canEnterDesktopMode(mContext) || !Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (!canEnterDesktopMode(mContext) || !isEducationEnabled()) {
             return;
         }
         mWindowDecorCaptionHandleRepository.notifyCaptionChanged(
@@ -758,7 +769,8 @@
         final CaptionState captionState = new CaptionState.AppHeader(
                 mTaskInfo,
                 isHandleMenuActive(),
-                appChipGlobalPosition);
+                appChipGlobalPosition,
+                isCapturedLinkAvailable());
 
         mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
     }
@@ -790,15 +802,15 @@
     }
 
     /**
-     * Detach the status bar input layer from this decoration. Intended to be
+     * Dispose of the view used to forward inputs in status bar region. Intended to be
      * used any time handle is no longer visible.
      */
-    void detachStatusBarInputLayer() {
+    void disposeStatusBarInputLayer() {
         if (!isAppHandle(mWindowDecorViewHolder)
                 || !Flags.enableHandleInputFix()) {
             return;
         }
-        asAppHandle(mWindowDecorViewHolder).detachStatusBarInputLayer();
+        asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
     }
 
     private WindowDecorationViewHolder createViewHolder() {
@@ -857,7 +869,7 @@
             Context context,
             ActivityManager.RunningTaskInfo taskInfo,
             boolean applyStartTransactionOnDraw,
-            boolean shouldSetTaskPositionAndCrop,
+            boolean shouldSetTaskVisibilityPositionAndCrop,
             boolean isStatusBarVisible,
             boolean isKeyguardVisibleAndOccluded,
             boolean inFullImmersiveMode,
@@ -953,7 +965,7 @@
                     : R.dimen.freeform_decor_shadow_unfocused_thickness;
         }
         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
-        relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop;
+        relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
 
         // The configuration used to layout the window decoration. A copy is made instead of using
         // the original reference so that the configuration isn't mutated on config changes and
@@ -1094,13 +1106,13 @@
             if (mAppIconBitmap != null && mAppName != null) {
                 return;
             }
-            final ComponentName baseActivity = mTaskInfo.baseActivity;
-            if (baseActivity == null) {
-                Slog.e(TAG, "Base activity component not found in task");
+            if (mTaskInfo.baseIntent == null) {
+                Slog.e(TAG, "Base intent not found in task");
                 return;
             }
             final PackageManager pm = mUserContext.getPackageManager();
-            final ActivityInfo activityInfo = pm.getActivityInfo(baseActivity, 0 /* flags */);
+            final ActivityInfo activityInfo =
+                    pm.getActivityInfo(mTaskInfo.baseIntent.getComponent(), 0 /* flags */);
             final IconProvider provider = new IconProvider(mContext);
             final Drawable appIconDrawable = provider.getIcon(activityInfo);
             final Drawable badgedAppIconDrawable = pm.getUserBadgedIcon(appIconDrawable,
@@ -1352,6 +1364,8 @@
                 && Flags.enableDesktopWindowingMultiInstanceFeatures();
         final boolean shouldShowManageWindowsButton = supportsMultiInstance
                 && mMinimumInstancesFound;
+        final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
+                .shouldShowChangeAspectRatioButton(mTaskInfo);
         final boolean inDesktopImmersive = mDesktopRepository
                 .isTaskInFullImmersiveState(mTaskInfo.taskId);
         mHandleMenu = mHandleMenuFactory.create(
@@ -1364,6 +1378,7 @@
                 canEnterDesktopMode(mContext),
                 supportsMultiInstance,
                 shouldShowManageWindowsButton,
+                shouldShowChangeAspectRatioButton,
                 getBrowserLink(),
                 mResult.mCaptionWidth,
                 mResult.mCaptionHeight,
@@ -1384,9 +1399,13 @@
                 /* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
                 /* onNewWindowClickListener= */ mOnNewWindowClickListener,
                 /* onManageWindowsClickListener= */ mOnManageWindowsClickListener,
+                /* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener,
                 /* openInBrowserClickListener= */ (intent) -> {
                     mOpenInBrowserClickListener.accept(intent);
                     onCapturedLinkExpired();
+                    if (Flags.enableDesktopWindowingAppToWebEducation()) {
+                        mWindowDecorCaptionHandleRepository.onAppToWebUsage();
+                    }
                     return Unit.INSTANCE;
                 },
                 /* onOpenByDefaultClickListener= */ () -> {
@@ -1405,7 +1424,7 @@
                 },
                 /* forceShowSystemBars= */ inDesktopImmersive
         );
-        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
             notifyCaptionStateChanged();
         }
         mMinimumInstancesFound = false;
@@ -1476,7 +1495,7 @@
         mWindowDecorViewHolder.onHandleMenuClosed();
         mHandleMenu.close();
         mHandleMenu = null;
-        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
             notifyCaptionStateChanged();
         }
     }
@@ -1631,6 +1650,12 @@
                 && v.getTop() <= y && v.getBottom() >= y;
     }
 
+    /** Returns true if at least one education flag is enabled. */
+    private boolean isEducationEnabled() {
+        return Flags.enableDesktopWindowingAppHandleEducation()
+                || Flags.enableDesktopWindowingAppToWebEducation();
+    }
+
     @Override
     public void close() {
         closeDragResizeListener();
@@ -1638,9 +1663,9 @@
         closeManageWindowsMenu();
         mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
         disposeResizeVeil();
-        detachStatusBarInputLayer();
+        disposeStatusBarInputLayer();
         clearCurrentViewHostRunnable();
-        if (canEnterDesktopMode(mContext) && Flags.enableDesktopWindowingAppHandleEducation()) {
+        if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
             notifyNoCaptionHandle();
         }
         super.close();
@@ -1755,16 +1780,6 @@
                 + "}";
     }
 
-    /**
-     * Set the view container to be used to forward input through status bar. Null in cases
-     * where input forwarding isn't needed.
-     */
-    public void setStatusBarInputLayer(
-            @Nullable AdditionalSystemViewContainer additionalSystemViewContainer
-    ) {
-        mStatusBarInputLayer = additionalSystemViewContainer;
-    }
-
     static class Factory {
 
         DesktopModeWindowDecoration create(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt
deleted file mode 100644
index 9c5215d..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopStatusBarInputLayerSupplier.kt
+++ /dev/null
@@ -1,117 +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.wm.shell.windowdecor
-
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
-import android.content.Context
-import android.graphics.PixelFormat
-import android.os.Handler
-import android.view.Gravity
-import android.view.View
-import android.view.WindowManager
-import com.android.wm.shell.shared.annotations.ShellMainThread
-import com.android.wm.shell.shared.split.SplitScreenConstants
-import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
-
-/**
- * Supplier for [AdditionalSystemViewContainer] objects to be used for forwarding input
- * events through status bar to an app handle. Currently supports two simultaneous input layers.
- *
- * The supplier will pick one of two input layer view containers to use: one for tasks in
- * fullscreen or top/left split stage, and one for tasks in right split stage.
- */
-class DesktopStatusBarInputLayerSupplier(
-    private val context: Context,
-    @ShellMainThread handler: Handler
-) {
-    private val inputLayers: MutableList<AdditionalSystemViewContainer> = mutableListOf()
-
-    init {
-        // Post this as creation of the input layer views is a relatively expensive operation.
-        handler.post {
-            repeat(TOTAL_INPUT_LAYERS) {
-                inputLayers.add(createInputLayer())
-            }
-        }
-    }
-
-    private fun createInputLayer(): AdditionalSystemViewContainer {
-        val lp = WindowManager.LayoutParams(
-            WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
-            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-            PixelFormat.TRANSPARENT
-        )
-        lp.title = "Desktop status bar input layer"
-        lp.gravity = Gravity.LEFT or Gravity.TOP
-        lp.setTrustedOverlay()
-
-        // Make this window a spy window to enable it to pilfer pointers from the system-wide
-        // gesture listener that receives events before window. This is to prevent notification
-        // shade gesture when we swipe down to enter desktop.
-        lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
-        lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-        val view = View(context)
-        view.visibility = View.GONE
-        return AdditionalSystemViewContainer(
-            WindowManagerWrapper(
-                context.getSystemService<WindowManager>(WindowManager::class.java)
-            ),
-            view,
-            lp
-        )
-    }
-
-    /**
-     * Decide which cached status bar input layer should be used for a decoration, if any.
-     *
-     * [splitPosition] and [isLeftRightSplit] are used to determine which input layer we use.
-     * The first one is reserved for fullscreen tasks or tasks in top/left split,
-     * while the second one is exclusively used for tasks in right split stage. Note we care about
-     * left-right vs top-bottom split as the bottom stage should not use an input layer.
-     */
-    fun getStatusBarInputLayer(
-        taskInfo: RunningTaskInfo,
-        @SplitScreenConstants.SplitPosition splitPosition: Int,
-        isLeftRightSplit: Boolean
-    ): AdditionalSystemViewContainer? {
-        if (!taskInfo.isVisibleRequested) return null
-        // Fullscreen and top/left split tasks will use the first input layer.
-        if (taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-            || splitPosition == SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
-        ) {
-            return inputLayers[LEFT_TOP_INPUT_LAYER]
-        }
-        // Right split tasks will use the second one.
-        if (isLeftRightSplit && splitPosition == SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-        ) {
-            return inputLayers[RIGHT_SPLIT_INPUT_LAYER]
-        }
-        // Which leaves bottom split and freeform tasks, which do not need an input layer
-        // as the status bar is not blocking them.
-        return null
-    }
-
-    companion object {
-        private const val TOTAL_INPUT_LAYERS = 2
-        // Input layer index for fullscreen tasks and tasks in top-left split
-        private const val LEFT_TOP_INPUT_LAYER = 0
-        // Input layer index for tasks in right split stage. Does not include bottom split as that
-        // stage is not blocked by status bar.
-        private const val RIGHT_SPLIT_INPUT_LAYER = 1
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 93bd929..2edc380 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -19,6 +19,7 @@
 import android.annotation.DimenRes
 import android.annotation.SuppressLint
 import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
 import android.content.Context
 import android.content.Intent
 import android.content.res.ColorStateList
@@ -71,6 +72,7 @@
     private val shouldShowWindowingPill: Boolean,
     private val shouldShowNewWindowButton: Boolean,
     private val shouldShowManageWindowsButton: Boolean,
+    private val shouldShowChangeAspectRatioButton: Boolean,
     private val openInBrowserIntent: Intent?,
     private val captionWidth: Int,
     private val captionHeight: Int,
@@ -111,6 +113,10 @@
     private val shouldShowBrowserPill: Boolean
         get() = openInBrowserIntent != null
 
+    private val shouldShowMoreActionsPill: Boolean
+        get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton ||
+            shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton
+
     init {
         updateHandleMenuPillPositions(captionX, captionY)
     }
@@ -121,6 +127,7 @@
         onToSplitScreenClickListener: () -> Unit,
         onNewWindowClickListener: () -> Unit,
         onManageWindowsClickListener: () -> Unit,
+        onChangeAspectRatioClickListener: () -> Unit,
         openInBrowserClickListener: (Intent) -> Unit,
         onOpenByDefaultClickListener: () -> Unit,
         onCloseMenuClickListener: () -> Unit,
@@ -138,6 +145,7 @@
             onToSplitScreenClickListener = onToSplitScreenClickListener,
             onNewWindowClickListener = onNewWindowClickListener,
             onManageWindowsClickListener = onManageWindowsClickListener,
+            onChangeAspectRatioClickListener = onChangeAspectRatioClickListener,
             openInBrowserClickListener = openInBrowserClickListener,
             onOpenByDefaultClickListener = onOpenByDefaultClickListener,
             onCloseMenuClickListener = onCloseMenuClickListener,
@@ -158,6 +166,7 @@
         onToSplitScreenClickListener: () -> Unit,
         onNewWindowClickListener: () -> Unit,
         onManageWindowsClickListener: () -> Unit,
+        onChangeAspectRatioClickListener: () -> Unit,
         openInBrowserClickListener: (Intent) -> Unit,
         onOpenByDefaultClickListener: () -> Unit,
         onCloseMenuClickListener: () -> Unit,
@@ -171,14 +180,16 @@
             shouldShowWindowingPill = shouldShowWindowingPill,
             shouldShowBrowserPill = shouldShowBrowserPill,
             shouldShowNewWindowButton = shouldShowNewWindowButton,
-            shouldShowManageWindowsButton = shouldShowManageWindowsButton
+            shouldShowManageWindowsButton = shouldShowManageWindowsButton,
+            shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton
         ).apply {
-            bind(taskInfo, appIconBitmap, appName)
+            bind(taskInfo, appIconBitmap, appName, shouldShowMoreActionsPill)
             this.onToDesktopClickListener = onToDesktopClickListener
             this.onToFullscreenClickListener = onToFullscreenClickListener
             this.onToSplitScreenClickListener = onToSplitScreenClickListener
             this.onNewWindowClickListener = onNewWindowClickListener
             this.onManageWindowsClickListener = onManageWindowsClickListener
+            this.onChangeAspectRatioClickListener = onChangeAspectRatioClickListener
             this.onOpenInBrowserClickListener = {
                 openInBrowserClickListener.invoke(openInBrowserIntent!!)
             }
@@ -392,8 +403,11 @@
                 R.dimen.desktop_mode_handle_menu_manage_windows_height
             )
         }
-        if (!SHOULD_SHOW_SCREENSHOT_BUTTON && !shouldShowNewWindowButton
-            && !shouldShowManageWindowsButton) {
+        if (!shouldShowChangeAspectRatioButton) {
+            menuHeight -= loadDimensionPixelSize(
+                R.dimen.desktop_mode_handle_menu_change_aspect_ratio_height)
+        }
+        if (!shouldShowMoreActionsPill) {
             menuHeight -= pillTopMargin
         }
         if (!shouldShowBrowserPill) {
@@ -427,7 +441,8 @@
         private val shouldShowWindowingPill: Boolean,
         private val shouldShowBrowserPill: Boolean,
         private val shouldShowNewWindowButton: Boolean,
-        private val shouldShowManageWindowsButton: Boolean
+        private val shouldShowManageWindowsButton: Boolean,
+        private val shouldShowChangeAspectRatioButton: Boolean
     ) {
         val rootView = LayoutInflater.from(context)
             .inflate(R.layout.desktop_mode_window_decor_handle_menu, null /* root */) as View
@@ -454,6 +469,8 @@
         private val newWindowBtn = moreActionsPill.requireViewById<Button>(R.id.new_window_button)
         private val manageWindowBtn = moreActionsPill
             .requireViewById<Button>(R.id.manage_windows_button)
+        private val changeAspectRatioBtn = moreActionsPill
+            .requireViewById<Button>(R.id.change_aspect_ratio_button)
 
         // Open in Browser Pill.
         private val openInBrowserPill = rootView.requireViewById<View>(R.id.open_in_browser_pill)
@@ -472,6 +489,7 @@
         var onToSplitScreenClickListener: (() -> Unit)? = null
         var onNewWindowClickListener: (() -> Unit)? = null
         var onManageWindowsClickListener: (() -> Unit)? = null
+        var onChangeAspectRatioClickListener: (() -> Unit)? = null
         var onOpenInBrowserClickListener: (() -> Unit)? = null
         var onOpenByDefaultClickListener: (() -> Unit)? = null
         var onCloseMenuClickListener: (() -> Unit)? = null
@@ -488,6 +506,7 @@
             collapseMenuButton.setOnClickListener { onCloseMenuClickListener?.invoke() }
             newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
             manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() }
+            changeAspectRatioBtn.setOnClickListener { onChangeAspectRatioClickListener?.invoke() }
 
             rootView.setOnTouchListener { _, event ->
                 if (event.actionMasked == ACTION_OUTSIDE) {
@@ -499,7 +518,12 @@
         }
 
         /** Binds the menu views to the new data. */
-        fun bind(taskInfo: RunningTaskInfo, appIconBitmap: Bitmap?, appName: CharSequence?) {
+        fun bind(
+            taskInfo: RunningTaskInfo,
+            appIconBitmap: Bitmap?,
+            appName: CharSequence?,
+            shouldShowMoreActionsPill: Boolean
+        ) {
             this.taskInfo = taskInfo
             this.style = calculateMenuStyle(taskInfo)
 
@@ -507,7 +531,10 @@
             if (shouldShowWindowingPill) {
                 bindWindowingPill(style)
             }
-            bindMoreActionsPill(style)
+            moreActionsPill.isGone = !shouldShowMoreActionsPill
+            if (shouldShowMoreActionsPill) {
+                bindMoreActionsPill(style)
+            }
             bindOpenInBrowserPill(style)
         }
 
@@ -616,27 +643,20 @@
         }
 
         private fun bindMoreActionsPill(style: MenuStyle) {
-            moreActionsPill.apply {
-                isGone = !shouldShowNewWindowButton && !SHOULD_SHOW_SCREENSHOT_BUTTON
-                        && !shouldShowManageWindowsButton
-            }
-            screenshotBtn.apply {
-                isGone = !SHOULD_SHOW_SCREENSHOT_BUTTON
-                background.setTint(style.backgroundColor)
-                setTextColor(style.textColor)
-                compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
-            }
-            newWindowBtn.apply {
-                isGone = !shouldShowNewWindowButton
-                background.setTint(style.backgroundColor)
-                setTextColor(style.textColor)
-                compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
-            }
-            manageWindowBtn.apply {
-                isGone = !shouldShowManageWindowsButton
-                background.setTint(style.backgroundColor)
-                setTextColor(style.textColor)
-                compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+            arrayOf(
+                screenshotBtn to SHOULD_SHOW_SCREENSHOT_BUTTON,
+                newWindowBtn to shouldShowNewWindowButton,
+                manageWindowBtn to shouldShowManageWindowsButton,
+                changeAspectRatioBtn to shouldShowChangeAspectRatioButton,
+            ).forEach {
+                val button = it.first
+                val shouldShow = it.second
+                button.apply {
+                    isGone = !shouldShow
+                    background.setTint(style.backgroundColor)
+                    setTextColor(style.textColor)
+                    compoundDrawableTintList = ColorStateList.valueOf(style.textColor)
+                }
             }
         }
 
@@ -664,6 +684,14 @@
     companion object {
         private const val TAG = "HandleMenu"
         private const val SHOULD_SHOW_SCREENSHOT_BUTTON = false
+
+        /**
+         * Returns whether the aspect ratio button should be shown for the task. It usually means
+         * that the task is on a large screen with ignore-orientation-request.
+         */
+        fun shouldShowChangeAspectRatioButton(taskInfo: RunningTaskInfo): Boolean =
+            taskInfo.appCompatTaskInfo.eligibleForUserAspectRatioButton() &&
+                taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
     }
 }
 
@@ -679,6 +707,7 @@
         shouldShowWindowingPill: Boolean,
         shouldShowNewWindowButton: Boolean,
         shouldShowManageWindowsButton: Boolean,
+        shouldShowChangeAspectRatioButton: Boolean,
         openInBrowserIntent: Intent?,
         captionWidth: Int,
         captionHeight: Int,
@@ -699,6 +728,7 @@
         shouldShowWindowingPill: Boolean,
         shouldShowNewWindowButton: Boolean,
         shouldShowManageWindowsButton: Boolean,
+        shouldShowChangeAspectRatioButton: Boolean,
         openInBrowserIntent: Intent?,
         captionWidth: Int,
         captionHeight: Int,
@@ -715,6 +745,7 @@
             shouldShowWindowingPill,
             shouldShowNewWindowButton,
             shouldShowManageWindowsButton,
+            shouldShowChangeAspectRatioButton,
             openInBrowserIntent,
             captionWidth,
             captionHeight,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index f97dfb89..b016c75 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -249,7 +249,9 @@
 
         if (!mTaskInfo.isVisible) {
             releaseViews(wct);
-            finishT.hide(mTaskSurface);
+            if (params.mSetTaskVisibilityPositionAndCrop) {
+                finishT.hide(mTaskSurface);
+            }
             return;
         }
 
@@ -422,7 +424,7 @@
 
     private void updateTaskSurface(RelayoutParams params, SurfaceControl.Transaction startT,
             SurfaceControl.Transaction finishT, RelayoutResult<T> outResult) {
-        if (params.mSetTaskPositionAndCrop) {
+        if (params.mSetTaskVisibilityPositionAndCrop) {
             final Point taskPosition = mTaskInfo.positionInParent;
             startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
             finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
@@ -437,9 +439,13 @@
             shadowRadius =
                     loadDimension(mDecorWindowContext.getResources(), params.mShadowRadiusId);
         }
-        startT.setShadowRadius(mTaskSurface, shadowRadius).show(mTaskSurface);
+        startT.setShadowRadius(mTaskSurface, shadowRadius);
         finishT.setShadowRadius(mTaskSurface, shadowRadius);
 
+        if (params.mSetTaskVisibilityPositionAndCrop) {
+            startT.show(mTaskSurface);
+        }
+
         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             if (!DesktopModeStatus.isVeiledResizeEnabled()) {
                 // When fluid resize is enabled, add a background to freeform tasks
@@ -758,7 +764,7 @@
         Configuration mWindowDecorConfig;
 
         boolean mApplyStartTransactionOnDraw;
-        boolean mSetTaskPositionAndCrop;
+        boolean mSetTaskVisibilityPositionAndCrop;
         boolean mHasGlobalFocus;
 
         void reset() {
@@ -777,7 +783,7 @@
             mIsCaptionVisible = false;
 
             mApplyStartTransactionOnDraw = false;
-            mSetTaskPositionAndCrop = false;
+            mSetTaskVisibilityPositionAndCrop = false;
             mWindowDecorConfig = null;
             mHasGlobalFocus = false;
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
index 1451f36..8b6aaaf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/additionalviewcontainer/AdditionalSystemViewContainer.kt
@@ -23,8 +23,8 @@
 import android.view.LayoutInflater
 import android.view.SurfaceControl
 import android.view.View
+import android.view.WindowInsets
 import android.view.WindowManager
-import android.view.WindowManager.LayoutParams
 import com.android.wm.shell.windowdecor.WindowManagerWrapper
 
 /**
@@ -33,11 +33,27 @@
  */
 class AdditionalSystemViewContainer(
     private val windowManagerWrapper: WindowManagerWrapper,
-    override val view: View,
-    val lp: LayoutParams
+    taskId: Int,
+    x: Int,
+    y: Int,
+    width: Int,
+    height: Int,
+    flags: Int,
+    @WindowInsets.Type.InsetsType forciblyShownTypes: Int = 0,
+    override val view: View
 ) : AdditionalViewContainer() {
+    val lp: WindowManager.LayoutParams = WindowManager.LayoutParams(
+        width, height, x, y,
+        WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
+        flags,
+        PixelFormat.TRANSPARENT
+    ).apply {
+        title = "Additional view container of Task=$taskId"
+        gravity = Gravity.LEFT or Gravity.TOP
+        setTrustedOverlay()
+        this.forciblyShownTypes = forciblyShownTypes
+    }
 
-    /** Provide a layout id of a view to inflate for this view container. */
     constructor(
         context: Context,
         windowManagerWrapper: WindowManagerWrapper,
@@ -50,30 +66,15 @@
         @LayoutRes layoutId: Int
     ) : this(
         windowManagerWrapper = windowManagerWrapper,
-        view = LayoutInflater.from(context).inflate(layoutId, null /* parent */),
-        lp = createLayoutParams(x, y, width, height, flags, taskId)
+        taskId = taskId,
+        x = x,
+        y = y,
+        width = width,
+        height = height,
+        flags = flags,
+        view = LayoutInflater.from(context).inflate(layoutId, null /* parent */)
     )
 
-    /** Provide a view directly for this view container */
-    constructor(
-        windowManagerWrapper: WindowManagerWrapper,
-        taskId: Int,
-        x: Int,
-        y: Int,
-        width: Int,
-        height: Int,
-        flags: Int,
-        view: View,
-        forciblyShownTypes: Int = 0
-    ) : this(
-        windowManagerWrapper = windowManagerWrapper,
-        view = view,
-        lp = createLayoutParams(x, y, width, height, flags, taskId).apply {
-            this.forciblyShownTypes = forciblyShownTypes
-        }
-    )
-
-    /** Do not supply a view at all, instead creating the view container with a basic view. */
     constructor(
         context: Context,
         windowManagerWrapper: WindowManagerWrapper,
@@ -85,7 +86,12 @@
         flags: Int
     ) : this(
         windowManagerWrapper = windowManagerWrapper,
-        lp = createLayoutParams(x, y, width, height, flags, taskId),
+        taskId = taskId,
+        x = x,
+        y = y,
+        width = width,
+        height = height,
+        flags = flags,
         view = View(context)
     )
 
@@ -98,7 +104,7 @@
     }
 
     override fun setPosition(t: SurfaceControl.Transaction, x: Float, y: Float) {
-        lp.apply {
+        val lp = (view.layoutParams as WindowManager.LayoutParams).apply {
             this.x = x.toInt()
             this.y = y.toInt()
         }
@@ -118,29 +124,13 @@
         ): AdditionalSystemViewContainer =
             AdditionalSystemViewContainer(
                 windowManagerWrapper = windowManagerWrapper,
-                view = view,
-                lp = createLayoutParams(x, y, width, height, flags, taskId)
+                taskId = taskId,
+                x = x,
+                y = y,
+                width = width,
+                height = height,
+                flags = flags,
+                view = view
             )
     }
-    companion object {
-        fun createLayoutParams(
-            x: Int,
-            y: Int,
-            width: Int,
-            height: Int,
-            flags: Int,
-            taskId: Int
-        ): LayoutParams {
-            return LayoutParams(
-                width, height, x, y,
-                LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
-                flags,
-                PixelFormat.TRANSPARENT
-            ).apply {
-                title = "Additional view container of Task=$taskId"
-                gravity = Gravity.LEFT or Gravity.TOP
-                setTrustedOverlay()
-            }
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationPromoController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationPromoController.kt
new file mode 100644
index 0000000..b3489a4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationPromoController.kt
@@ -0,0 +1,231 @@
+/*
+ * 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.wm.shell.windowdecor.education
+
+import android.annotation.ColorInt
+import android.annotation.DimenRes
+import android.annotation.LayoutRes
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import android.widget.LinearLayout
+import android.widget.TextView
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.shared.animation.PhysicsAnimator
+import com.android.wm.shell.windowdecor.WindowManagerWrapper
+import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+
+/**
+ * Controls the lifecycle of an education promo, including showing and hiding it.
+ */
+class DesktopWindowingEducationPromoController(
+    private val context: Context,
+    private val additionalSystemViewContainerFactory: AdditionalSystemViewContainer.Factory,
+    private val displayController: DisplayController,
+) : OnDisplayChangingListener {
+    private var educationView: View? = null
+    private var animator: PhysicsAnimator<View>? = null
+    private val springConfig by lazy {
+        PhysicsAnimator.SpringConfig(
+            SpringForce.STIFFNESS_MEDIUM,
+            SpringForce.DAMPING_RATIO_LOW_BOUNCY
+        )
+    }
+    private var popupWindow: AdditionalSystemViewContainer? = null
+
+    override fun onDisplayChange(
+        displayId: Int,
+        fromRotation: Int,
+        toRotation: Int,
+        newDisplayAreaInfo: DisplayAreaInfo?,
+        t: WindowContainerTransaction?
+    ) {
+        // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and
+        // [toRotation] can be one of the [@Surface.Rotation] values.
+        if ((fromRotation % 2 == toRotation % 2)) return
+        hideEducation()
+    }
+
+    /**
+     * Shows education promo.
+     *
+     * @param viewConfig features of the education.
+     * @param taskId is used in the title of popup window created for the education view.
+     */
+    fun showEducation(
+        viewConfig: EducationViewConfig,
+        taskId: Int
+    ) {
+        hideEducation()
+        educationView = createEducationView(viewConfig, taskId)
+        animator = createAnimator()
+        animateShowEducationTransition()
+        displayController.addDisplayChangingController(this)
+    }
+
+    /** Hide the current education view if visible */
+    private fun hideEducation() = animateHideEducationTransition { cleanUp() }
+
+    /** Create education view by inflating layout provided. */
+    private fun createEducationView(
+        viewConfig: EducationViewConfig,
+        taskId: Int
+    ): View {
+        val educationView =
+            LayoutInflater.from(context)
+                .inflate(
+                    viewConfig.viewLayout, /* root= */ null, /* attachToRoot= */ false)
+                .apply {
+                    alpha = 0f
+                    scaleX = 0f
+                    scaleY = 0f
+
+                    requireViewById<TextView>(R.id.education_text).apply {
+                        text = viewConfig.educationText
+                    }
+                    setOnTouchListener { _, motionEvent ->
+                        if (motionEvent.action == MotionEvent.ACTION_OUTSIDE) {
+                            hideEducation()
+                            true
+                        } else {
+                            false
+                        }
+                    }
+                    setOnClickListener {
+                        hideEducation()
+                    }
+                    setEducationColorScheme(viewConfig.educationColorScheme)
+                }
+
+        createEducationPopupWindow(
+            taskId,
+            viewConfig.viewGlobalCoordinates,
+            loadDimensionPixelSize(viewConfig.widthId),
+            loadDimensionPixelSize(viewConfig.heightId),
+            educationView = educationView)
+
+        return educationView
+    }
+
+    /** Create animator for education transitions */
+    private fun createAnimator(): PhysicsAnimator<View>? =
+        educationView?.let {
+            PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) }
+        }
+
+    /** Animate show transition for the education view */
+    private fun animateShowEducationTransition() {
+        animator
+            ?.spring(DynamicAnimation.ALPHA, 1f)
+            ?.spring(DynamicAnimation.SCALE_X, 1f)
+            ?.spring(DynamicAnimation.SCALE_Y, 1f)
+            ?.start()
+    }
+
+    /** Animate hide transition for the education view */
+    private fun animateHideEducationTransition(endActions: () -> Unit) {
+        animator
+            ?.spring(DynamicAnimation.ALPHA, 0f)
+            ?.spring(DynamicAnimation.SCALE_X, 0f)
+            ?.spring(DynamicAnimation.SCALE_Y, 0f)
+            ?.start()
+        endActions()
+    }
+
+    /** Remove education promo and clean up all relative properties */
+    private fun cleanUp() {
+        educationView = null
+        animator = null
+        popupWindow?.releaseView()
+        popupWindow = null
+        displayController.removeDisplayChangingController(this)
+    }
+
+    private fun createEducationPopupWindow(
+        taskId: Int,
+        educationViewGlobalCoordinates: Point,
+        width: Int,
+        height: Int,
+        educationView: View,
+    ) {
+        popupWindow =
+            additionalSystemViewContainerFactory.create(
+                windowManagerWrapper =
+                WindowManagerWrapper(context.getSystemService(WindowManager::class.java)),
+                taskId = taskId,
+                x = educationViewGlobalCoordinates.x,
+                y = educationViewGlobalCoordinates.y,
+                width = width,
+                height = height,
+                flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+                        WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+                view = educationView)
+    }
+
+    private fun View.setEducationColorScheme(educationColorScheme: EducationColorScheme) {
+        requireViewById<LinearLayout>(R.id.education_container).apply {
+            background.setTint(educationColorScheme.container)
+        }
+        requireViewById<TextView>(R.id.education_text).apply {
+            setTextColor(educationColorScheme.text)
+        }
+    }
+
+    private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
+        if (resourceId == Resources.ID_NULL) return 0
+        return context.resources.getDimensionPixelSize(resourceId)
+    }
+
+    /**
+     * The configuration for education view features:
+     *
+     * @property viewLayout Layout resource ID of the view to be used for education promo.
+     * @property viewGlobalCoordinates Global (screen) coordinates of the education.
+     * @property educationText Text to be added to the TextView of the promo.
+     * @property widthId res Id for education width
+     * @property heightId res Id for education height
+     */
+    data class EducationViewConfig(
+        @LayoutRes val viewLayout: Int,
+        val educationColorScheme: EducationColorScheme,
+        val viewGlobalCoordinates: Point,
+        val educationText: String,
+        @DimenRes val widthId: Int,
+        @DimenRes val heightId: Int
+    )
+
+    /**
+     * Color scheme of education view:
+     *
+     * @property container Color of the container of the education.
+     * @property text Text color of the [TextView] of education promo.
+     */
+    data class EducationColorScheme(
+        @ColorInt val container: Int,
+        @ColorInt val text: Int,
+    )
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
index c61b31e..4fa2744 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipController.kt
@@ -80,7 +80,7 @@
    * @param tooltipViewConfig features of tooltip.
    * @param taskId is used in the title of popup window created for the tooltip view.
    */
-  fun showEducationTooltip(tooltipViewConfig: EducationViewConfig, taskId: Int) {
+  fun showEducationTooltip(tooltipViewConfig: TooltipEducationViewConfig, taskId: Int) {
     hideEducationTooltip()
     tooltipView = createEducationTooltipView(tooltipViewConfig, taskId)
     animator = createAnimator()
@@ -93,7 +93,7 @@
 
   /** Create education view by inflating layout provided. */
   private fun createEducationTooltipView(
-      tooltipViewConfig: EducationViewConfig,
+      tooltipViewConfig: TooltipEducationViewConfig,
       taskId: Int,
   ): View {
     val tooltipView =
@@ -271,7 +271,7 @@
    * @property onEducationClickAction Lambda to be executed when the tooltip is clicked.
    * @property onDismissAction Lambda to be executed when the tooltip is dismissed.
    */
-  data class EducationViewConfig(
+  data class TooltipEducationViewConfig(
       @LayoutRes val tooltipViewLayout: Int,
       val tooltipColorScheme: TooltipColorScheme,
       val tooltipViewGlobalCoordinates: Point,
@@ -299,4 +299,4 @@
     UP,
     LEFT,
   }
-}
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index ff418c6..e43c3a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -21,10 +21,13 @@
 import android.content.Context
 import android.graphics.Rect
 import android.util.SparseArray
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
 import androidx.core.util.valueIterator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayChangeController
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.desktopmode.DesktopRepository
@@ -45,10 +48,16 @@
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val returnToDragStartAnimator: ReturnToDragStartAnimator,
     private val taskRepository: DesktopRepository,
-) {
+) : DisplayChangeController.OnDisplayChangingListener {
     @VisibleForTesting
     var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>()
 
+    init {
+        // TODO(b/374309287): Move this interface implementation to
+        // [DesktopModeWindowDecorViewModel] when the migration is done.
+        displayController.addDisplayChangingController(this)
+    }
+
     fun snapToHalfScreen(
         taskInfo: ActivityManager.RunningTaskInfo,
         desktopModeWindowDecoration: DesktopModeWindowDecoration,
@@ -102,7 +111,20 @@
 
     fun onUserChange() {
         for (tilingHandler in tilingTransitionHandlerByDisplayId.valueIterator()) {
-            tilingHandler.onUserChange()
+            tilingHandler.resetTilingSession()
         }
     }
+
+    override fun onDisplayChange(
+        displayId: Int,
+        fromRotation: Int,
+        toRotation: Int,
+        newDisplayAreaInfo: DisplayAreaInfo?,
+        t: WindowContainerTransaction?,
+    ) {
+        // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and
+        // [toRotation] can be one of the [@Surface.Rotation] values.
+        if ((fromRotation % 2 == toRotation % 2)) return
+        tilingTransitionHandlerByDisplayId.get(displayId)?.resetTilingSession()
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 9bf1304..209eb5e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -23,6 +23,7 @@
 import android.graphics.Region
 import android.os.Binder
 import android.view.LayoutInflater
+import android.view.RoundedCorner
 import android.view.SurfaceControl
 import android.view.SurfaceControlViewHost
 import android.view.View
@@ -53,12 +54,14 @@
     private val transitionHandler: DesktopTilingWindowDecoration,
     private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
     private var dividerBounds: Rect,
+    private val displayContext: Context,
 ) : WindowlessWindowManager(config, leash, null), DividerMoveCallback, View.OnLayoutChangeListener {
     private lateinit var viewHost: SurfaceControlViewHost
     private var tilingDividerView: TilingDividerView? = null
     private var dividerShown = false
     private var handleRegionWidth: Int = -1
     private var setTouchRegion = true
+    private val maxRoundedCornerRadius = getMaxRoundedCornerRadius()
 
     /**
      * Gets bounds of divider window with screen based coordinate on the param Rect.
@@ -93,7 +96,11 @@
         getDividerBounds(tmpDividerBounds)
         dividerView.setup(this, tmpDividerBounds)
         t.setRelativeLayer(leash, relativeLeash, 1)
-            .setPosition(leash, dividerBounds.left.toFloat(), dividerBounds.top.toFloat())
+            .setPosition(
+                leash,
+                dividerBounds.left.toFloat() - maxRoundedCornerRadius,
+                dividerBounds.top.toFloat(),
+            )
             .show(leash)
         syncQueue.runInSync { transaction ->
             transaction.merge(t)
@@ -144,7 +151,7 @@
      */
     override fun onDividerMove(pos: Int): Boolean {
         val t = transactionSupplier.get()
-        t.setPosition(leash, pos.toFloat(), dividerBounds.top.toFloat())
+        t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat())
         val dividerWidth = dividerBounds.width()
         dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
         return transitionHandler.onDividerHandleMoved(dividerBounds, t)
@@ -157,7 +164,7 @@
     override fun onDividerMovedEnd(pos: Int) {
         setSlippery(true)
         val t = transactionSupplier.get()
-        t.setPosition(leash, pos.toFloat(), dividerBounds.top.toFloat())
+        t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat())
         val dividerWidth = dividerBounds.width()
         dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
         transitionHandler.onDividerHandleDragEnd(dividerBounds, t)
@@ -166,7 +173,7 @@
     private fun getWindowManagerParams(): WindowManager.LayoutParams {
         val lp =
             WindowManager.LayoutParams(
-                dividerBounds.width(),
+                dividerBounds.width() + 2 * maxRoundedCornerRadius,
                 dividerBounds.height(),
                 TYPE_DOCK_DIVIDER,
                 FLAG_NOT_FOCUSABLE or
@@ -225,4 +232,15 @@
         }
         viewHost.relayout(lp)
     }
+
+    private fun getMaxRoundedCornerRadius(): Int {
+        val display = displayContext.display
+        return listOf(
+                RoundedCorner.POSITION_TOP_LEFT,
+                RoundedCorner.POSITION_TOP_RIGHT,
+                RoundedCorner.POSITION_BOTTOM_RIGHT,
+                RoundedCorner.POSITION_BOTTOM_LEFT,
+            )
+            .maxOf { position -> display.getRoundedCorner(position)?.getRadius() ?: 0 }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index a7087ae..c46767c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -195,6 +195,7 @@
         val builder = SurfaceControl.Builder()
         rootTdaOrganizer.attachToDisplayArea(displayId, builder)
         val leash = builder.setName(TILING_DIVIDER_TAG).setContainerLayer().build()
+        val displayContext = displayController.getDisplayContext(displayId) ?: return null
         val tilingManager =
             displayLayout?.let {
                 dividerBounds = inflateDividerBounds(it)
@@ -207,6 +208,7 @@
                     this,
                     transactionSupplier,
                     dividerBounds,
+                    displayContext,
                 )
             }
         // a leash to present the divider on top of, without re-parenting.
@@ -483,7 +485,7 @@
         }
     }
 
-    fun onUserChange() {
+    fun resetTilingSession() {
         if (leftTaskResizingHelper != null) {
             removeTask(leftTaskResizingHelper, taskVanished = false, shouldDelayUpdate = true)
             leftTaskResizingHelper = null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
index f8113c219..8922905 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
@@ -118,7 +118,7 @@
             val dividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width)
             val backgroundLeft = (width - dividerSize) / 2
             val backgroundTop = 0
-            val backgroundRight = left + dividerSize
+            val backgroundRight = backgroundLeft + dividerSize
             val backgroundBottom = height
             backgroundRect.set(backgroundLeft, backgroundTop, backgroundRight, backgroundBottom)
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index c4e4946..b5700ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -36,10 +36,13 @@
 import android.widget.ImageButton
 import androidx.core.view.ViewCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
+import com.android.internal.policy.SystemBarUtils
+import com.android.window.flags.Flags
 import com.android.wm.shell.R
 import com.android.wm.shell.shared.animation.Interpolators
 import com.android.wm.shell.windowdecor.WindowManagerWrapper
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
+import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder.Data
 
 /**
  * A desktop mode window decoration used when the window is in full "focus" (i.e. fullscreen/split).
@@ -66,12 +69,10 @@
     ) : Data()
 
     private lateinit var taskInfo: RunningTaskInfo
-    private val position: Point = Point()
-    private var width: Int = 0
-    private var height: Int = 0
     private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
     private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
     private val inputManager = context.getSystemService(InputManager::class.java)
+    private var statusBarInputLayerExists = false
 
     // An invisible View that takes up the same coordinates as captionHandle but is layered
     // above the status bar. The purpose of this View is to receive input intended for
@@ -111,54 +112,21 @@
     ) {
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
         this.taskInfo = taskInfo
-        this.position.set(position)
-        this.width = width
-        this.height = height
-        if (!isCaptionVisible && statusBarInputLayer != null) {
-            detachStatusBarInputLayer()
+        // If handle is not in status bar region(i.e., bottom stage in vertical split),
+        // do not create an input layer
+        if (position.y >= SystemBarUtils.getStatusBarHeight(context)) return
+        if (!isCaptionVisible && statusBarInputLayerExists) {
+            disposeStatusBarInputLayer()
             return
         }
-    }
-
-    fun bindStatusBarInputLayer(
-        statusBarLayer: AdditionalSystemViewContainer
-    ) {
-        // Input layer view modification takes a significant amount of time;
+        // Input layer view creation / modification takes a significant amount of time;
         // post them so we don't hold up DesktopModeWindowDecoration#relayout.
-        if (statusBarLayer == statusBarInputLayer) {
+        if (statusBarInputLayerExists) {
             handler.post { updateStatusBarInputLayer(position) }
-            return
-        }
-        // Remove the old input layer when changing to a new one.
-        if (statusBarInputLayer != null) detachStatusBarInputLayer()
-        if (statusBarLayer.view.visibility == View.GONE) {
-            statusBarLayer.view.visibility = View.VISIBLE
-        }
-        statusBarInputLayer = statusBarLayer
-        statusBarInputLayer?.let {
-            inputLayer -> setupAppHandleA11y(inputLayer.view)
-        }
-        handler.post {
-            val view = statusBarInputLayer?.view
-                ?: error("Unable to find statusBarInputLayer View")
-            // Caption handle is located within the status bar region, meaning the
-            // DisplayPolicy will attempt to transfer this input to status bar if it's
-            // a swipe down. Pilfer here to keep the gesture in handle alone.
-            view.setOnTouchListener { v, event ->
-                if (event.actionMasked == ACTION_DOWN) {
-                    inputManager.pilferPointers(v.viewRootImpl.inputToken)
-                }
-                captionHandle.dispatchTouchEvent(event)
-                return@setOnTouchListener true
-            }
-            view.setOnHoverListener { _, event ->
-                captionHandle.onHoverEvent(event)
-            }
-            val lp = statusBarInputLayer?.view?.layoutParams as WindowManager.LayoutParams
-            lp.x = position.x
-            lp.y = position.y
-            lp.width = width
-            lp.height = height
+        } else {
+            // Input layer is created on a delay; prevent multiple from being created.
+            statusBarInputLayerExists = true
+            handler.post { createStatusBarInputLayer(position, width, height) }
         }
     }
 
@@ -170,6 +138,40 @@
         animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
     }
 
+    private fun createStatusBarInputLayer(handlePosition: Point,
+                                          handleWidth: Int,
+                                          handleHeight: Int) {
+        if (!Flags.enableHandleInputFix()) return
+        statusBarInputLayer = AdditionalSystemViewContainer(context, windowManagerWrapper,
+            taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
+            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+        )
+        val view = statusBarInputLayer?.view ?: error("Unable to find statusBarInputLayer View")
+        val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer " +
+                "LayoutParams")
+        lp.title = "Handle Input Layer of task " + taskInfo.taskId
+        lp.setTrustedOverlay()
+        // Make this window a spy window to enable it to pilfer pointers from the system-wide
+        // gesture listener that receives events before window. This is to prevent notification
+        // shade gesture when we swipe down to enter desktop.
+        lp.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+        view.setOnHoverListener { _, event ->
+            captionHandle.onHoverEvent(event)
+        }
+        // Caption handle is located within the status bar region, meaning the
+        // DisplayPolicy will attempt to transfer this input to status bar if it's
+        // a swipe down. Pilfer here to keep the gesture in handle alone.
+        view.setOnTouchListener { v, event ->
+            if (event.actionMasked == ACTION_DOWN) {
+                inputManager.pilferPointers(v.viewRootImpl.inputToken)
+            }
+            captionHandle.dispatchTouchEvent(event)
+            return@setOnTouchListener true
+        }
+        setupAppHandleA11y(view)
+        windowManagerWrapper.updateViewLayout(view, lp)
+    }
+
     private fun setupAppHandleA11y(view: View) {
         view.accessibilityDelegate = object : View.AccessibilityDelegate() {
             override fun onInitializeAccessibilityNodeInfo(
@@ -222,12 +224,15 @@
     }
 
     /**
-     * Remove the input listeners from the input layer and remove it from this view holder.
+     * Remove the input layer from [WindowManager]. Should be used when caption handle
+     * is not visible.
      */
-    fun detachStatusBarInputLayer() {
-        statusBarInputLayer?.view?.setOnTouchListener(null)
-        statusBarInputLayer?.view?.setOnHoverListener(null)
-        statusBarInputLayer = null
+    fun disposeStatusBarInputLayer() {
+        statusBarInputLayerExists = false
+        handler.post {
+            statusBarInputLayer?.releaseView()
+            statusBarInputLayer = null
+        }
     }
 
     private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 72d4dc6..13a8518 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -700,7 +700,7 @@
                 eq(tInfo), eq(st), eq(ft), eq(callback));
 
         mBackTransitionHandler.onAnimationFinished();
-        final TransitionInfo.Change openToClose = createAppChange(openTaskId, TRANSIT_CLOSE,
+        final TransitionInfo.Change openToClose = createAppChangeFromChange(open, TRANSIT_CLOSE,
                 FLAG_BACK_GESTURE_ANIMATED);
         tInfo2 = createTransitionInfo(TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, openToClose);
         mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class);
@@ -830,6 +830,16 @@
         return change;
     }
 
+    private TransitionInfo.Change createAppChangeFromChange(
+            TransitionInfo.Change originalChange, @TransitionInfo.TransitionMode int mode,
+            @TransitionInfo.ChangeFlags int flags) {
+        final TransitionInfo.Change change = new TransitionInfo.Change(
+                originalChange.getTaskInfo().token, originalChange.getLeash());
+        change.setMode(mode);
+        change.setFlags(flags);
+        return change;
+    }
+
     private static TransitionInfo createTransitionInfo(
             @WindowManager.TransitionType int type, TransitionInfo.Change ... changes) {
         final TransitionInfo info = new TransitionInfo(type, 0);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index 868883d..df061e3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -39,9 +39,12 @@
 import com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
+import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler.PendingMixedTransition
 import com.android.wm.shell.freeform.FreeformTaskTransitionHandler
+import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertFalse
@@ -51,7 +54,9 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
@@ -80,6 +85,8 @@
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock lateinit var mockHandler: Handler
     @Mock lateinit var closingTaskLeash: SurfaceControl
+    @Mock lateinit var shellInit: ShellInit
+    @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
 
     private lateinit var mixedHandler: DesktopMixedTransitionHandler
 
@@ -94,7 +101,9 @@
                 closeDesktopTaskTransitionHandler,
                 desktopImmersiveController,
                 interactionJankMonitor,
-                mockHandler
+                mockHandler,
+                shellInit,
+                rootTaskDisplayAreaOrganizer,
             )
     }
 
@@ -238,8 +247,10 @@
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
-    fun startLaunchTransition_immersiveMixDisabled_doesNotUseMixedHandler() {
+    @DisableFlags(
+        Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP,
+        Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun startLaunchTransition_immersiveAndAppLaunchFlagsDisabled_doesNotUseMixedHandler() {
         val wct = WindowContainerTransaction()
         val task = createTask(WINDOWING_MODE_FREEFORM)
         whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
@@ -274,6 +285,24 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun startLaunchTransition_desktopAppLaunchEnabled_usesMixedHandler() {
+        val wct = WindowContainerTransaction()
+        val task = createTask(WINDOWING_MODE_FREEFORM)
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(Binder())
+
+        mixedHandler.startLaunchTransition(
+            transitionType = TRANSIT_OPEN,
+            wct = wct,
+            taskId = task.taskId,
+            exitingImmersiveTask = null
+        )
+
+        verify(transitions).startTransition(TRANSIT_OPEN, wct, mixedHandler)
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
     fun startAndAnimateLaunchTransition_withoutImmersiveChange_dispatchesAllChangesToLeftOver() {
         val wct = WindowContainerTransaction()
@@ -355,6 +384,134 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun startAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
+        val wct = WindowContainerTransaction()
+        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val launchTaskChange = createChange(launchingTask)
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(transition)
+
+        mixedHandler.startLaunchTransition(
+            transitionType = TRANSIT_OPEN,
+            wct = wct,
+            taskId = launchingTask.taskId,
+            minimizingTaskId = null,
+        )
+        mixedHandler.startAnimation(
+            transition,
+            createTransitionInfo(
+                TRANSIT_OPEN,
+                listOf(launchTaskChange)
+            ),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) { }
+
+        verify(rootTaskDisplayAreaOrganizer, times(0))
+            .reparentToDisplayArea(anyInt(), any(), any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun startAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
+        val wct = WindowContainerTransaction()
+        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val launchTaskChange = createChange(launchingTask)
+        val minimizeChange = createChange(minimizingTask)
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(transition)
+
+        mixedHandler.startLaunchTransition(
+            transitionType = TRANSIT_OPEN,
+            wct = wct,
+            taskId = launchingTask.taskId,
+            minimizingTaskId = minimizingTask.taskId,
+        )
+        mixedHandler.startAnimation(
+            transition,
+            createTransitionInfo(
+                TRANSIT_OPEN,
+                listOf(launchTaskChange, minimizeChange)
+            ),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) { }
+
+        verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
+            anyInt(), eq(minimizeChange.leash), any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
+        val wct = WindowContainerTransaction()
+        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val launchTaskChange = createChange(launchingTask)
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(transition)
+
+        mixedHandler.addPendingMixedTransition(
+            PendingMixedTransition.Launch(
+                transition = transition,
+                launchingTask = launchingTask.taskId,
+                minimizingTask = null,
+                exitingImmersiveTask = null,
+            )
+        )
+        mixedHandler.startAnimation(
+            transition,
+            createTransitionInfo(
+                TRANSIT_OPEN,
+                listOf(launchTaskChange)
+            ),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) { }
+
+        verify(rootTaskDisplayAreaOrganizer, times(0))
+            .reparentToDisplayArea(anyInt(), any(), any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+    fun addPendingAndAnimateLaunchTransition_withMinimizeChange_reparentsMinimizeChange() {
+        val wct = WindowContainerTransaction()
+        val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val minimizingTask = createTask(WINDOWING_MODE_FREEFORM)
+        val launchTaskChange = createChange(launchingTask)
+        val minimizeChange = createChange(minimizingTask)
+        val transition = Binder()
+        whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+            .thenReturn(transition)
+
+        mixedHandler.addPendingMixedTransition(
+            PendingMixedTransition.Launch(
+                transition = transition,
+                launchingTask = launchingTask.taskId,
+                minimizingTask = minimizingTask.taskId,
+                exitingImmersiveTask = null,
+            )
+        )
+        mixedHandler.startAnimation(
+            transition,
+            createTransitionInfo(
+                TRANSIT_OPEN,
+                listOf(launchTaskChange, minimizeChange)
+            ),
+            SurfaceControl.Transaction(),
+            SurfaceControl.Transaction(),
+        ) { }
+
+        verify(rootTaskDisplayAreaOrganizer).reparentToDisplayArea(
+            anyInt(), eq(minimizeChange.leash), any())
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
     fun startAndAnimateLaunchTransition_removesPendingMixedTransition() {
         val wct = WindowContainerTransaction()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index fc89b0e..b157d55 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -1413,7 +1413,8 @@
       eq(TRANSIT_TO_FRONT),
       any(),
       eq(freeformTasks[0].taskId),
-      anyOrNull()
+      anyOrNull(),
+      anyOrNull(),
     )).thenReturn(Binder())
 
     controller.moveTaskToFront(freeformTasks[0], remoteTransition = null)
@@ -1471,7 +1472,7 @@
     val task = createTaskInfo(1001)
     whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(null)
     whenever(desktopMixedTransitionHandler
-      .startLaunchTransition(eq(TRANSIT_OPEN), any(), eq(task.taskId), anyOrNull()))
+      .startLaunchTransition(eq(TRANSIT_OPEN), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
       .thenReturn(Binder())
 
     controller.moveTaskToFront(task.taskId, remoteTransition = null)
@@ -3692,7 +3693,8 @@
         runOnTransitionStart = runOnStartTransit,
       ))
     whenever(desktopMixedTransitionHandler
-      .startLaunchTransition(any(), any(), anyInt(), anyOrNull())).thenReturn(transition)
+      .startLaunchTransition(any(), any(), anyInt(), anyOrNull(), anyOrNull()))
+      .thenReturn(transition)
 
     controller.moveTaskToFront(task.taskId, remoteTransition = null)
 
@@ -3713,7 +3715,7 @@
         runOnTransitionStart = runOnStartTransit,
       ))
     whenever(desktopMixedTransitionHandler
-      .startLaunchTransition(any(), any(), eq(task.taskId), anyOrNull()))
+      .startLaunchTransition(any(), any(), eq(task.taskId), anyOrNull(), anyOrNull()))
       .thenReturn(transition)
 
     controller.moveTaskToFront(task.taskId, remoteTransition = null)
@@ -4102,7 +4104,7 @@
     val arg: ArgumentCaptor<WindowContainerTransaction> =
       ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
     verify(desktopMixedTransitionHandler)
-      .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull())
+      .startLaunchTransition(eq(type), capture(arg), anyInt(), anyOrNull(), anyOrNull())
     return arg.value
   }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
index fefa933..a82e5e8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java
@@ -19,8 +19,6 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
-import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
-
 import static com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN;
 
 import static org.junit.Assert.assertTrue;
@@ -28,6 +26,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.animation.AnimatorTestRule;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.WindowConfiguration;
@@ -36,6 +35,8 @@
 import android.graphics.Point;
 import android.os.Handler;
 import android.os.IBinder;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
 import android.util.DisplayMetrics;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -53,17 +54,23 @@
 import com.android.wm.shell.transition.Transitions;
 
 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.ArrayList;
 import java.util.function.Supplier;
 
 /** Tests of {@link com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler} */
 @SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
 public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
 
+    @Rule
+    public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this);
+
     @Mock
     private Transitions mTransitions;
     @Mock
@@ -105,7 +112,7 @@
     }
 
     @Test
-    public void testTransitExitDesktopModeAnimation() throws Throwable {
+    public void testTransitExitDesktopModeAnimation() {
         final int transitionType = TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN;
         final int taskId = 1;
         WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -118,21 +125,16 @@
         TransitionInfo.Change change =
                 createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
         TransitionInfo info = createTransitionInfo(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN, change);
-        ArrayList<Exception> exceptions = new ArrayList<>();
-        runOnUiThread(() -> {
-            try {
-                assertTrue(mExitDesktopTaskTransitionHandler
-                        .startAnimation(mToken, info,
-                                new SurfaceControl.Transaction(),
-                                new SurfaceControl.Transaction(),
-                                mTransitionFinishCallback));
-            } catch (Exception e) {
-                exceptions.add(e);
-            }
-        });
-        if (!exceptions.isEmpty()) {
-            throw exceptions.get(0);
-        }
+
+        final boolean animated = mExitDesktopTaskTransitionHandler
+                .startAnimation(mToken, info,
+                        new SurfaceControl.Transaction(),
+                        new SurfaceControl.Transaction(),
+                        mTransitionFinishCallback);
+        mAnimatorTestRule.advanceTimeBy(
+                ExitDesktopTaskTransitionHandler.FULLSCREEN_ANIMATION_DURATION);
+
+        assertTrue(animated);
     }
 
     private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
index e3caf2e..38c6ed9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/WindowDecorCaptionHandleRepositoryTest.kt
@@ -49,7 +49,10 @@
     val taskInfo = createTaskInfo(WINDOWING_MODE_FULLSCREEN, GMAIL_PACKAGE_NAME)
     val appHandleCaptionState =
         CaptionState.AppHandle(
-            taskInfo, false, Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3))
+          runningTaskInfo = taskInfo,
+          isHandleMenuExpanded = false,
+          globalAppHandleBounds = Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3),
+          isCapturedLinkAvailable = false)
 
     captionHandleRepository.notifyCaptionChanged(appHandleCaptionState)
 
@@ -61,7 +64,10 @@
     val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, GMAIL_PACKAGE_NAME)
     val appHeaderCaptionState =
         CaptionState.AppHeader(
-            taskInfo, true, Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3))
+          runningTaskInfo = taskInfo,
+          isHeaderMenuExpanded = true,
+          globalAppChipBounds = Rect(/* left= */ 0, /* top= */ 1, /* right= */ 2, /* bottom= */ 3),
+          isCapturedLinkAvailable = false)
 
     captionHandleRepository.notifyCaptionChanged(appHeaderCaptionState)
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
index 5419887..d94186c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/education/AppHandleEducationControllerTest.kt
@@ -68,7 +68,6 @@
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @OptIn(ExperimentalCoroutinesApi::class)
-@Ignore("b/374328725")
 class AppHandleEducationControllerTest : ShellTestCase() {
   @JvmField
   @Rule
@@ -85,7 +84,7 @@
   private val testDataStoreFlow = MutableStateFlow(createWindowingEducationProto())
   private val testCaptionStateFlow = MutableStateFlow<CaptionState>(CaptionState.NoCaption)
   private val educationConfigCaptor =
-      argumentCaptor<DesktopWindowingEducationTooltipController.EducationViewConfig>()
+      argumentCaptor<DesktopWindowingEducationTooltipController.TooltipEducationViewConfig>()
   @Mock private lateinit var mockEducationFilter: AppHandleEducationFilter
   @Mock private lateinit var mockDataStoreRepository: AppHandleEducationDatastoreRepository
   @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index e74c804..bcb7461 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -280,7 +280,7 @@
 
     private void preparePipSurfaceTransactionHelper() {
         doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
-                .crop(any(), any(), any());
+                .cropAndPosition(any(), any(), any());
         doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
                 .resetScale(any(), any(), any());
         doReturn(mMockPipSurfaceTransactionHelper).when(mMockPipSurfaceTransactionHelper)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
new file mode 100644
index 0000000..0adb50b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipResizeAnimatorTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.wm.shell.pip2.animation;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.MatchersKt.eq;
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipResizeAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipResizeAnimatorTest {
+
+    private static final float FLOAT_COMPARISON_DELTA = 0.001f;
+
+    @Mock private Context mMockContext;
+
+    @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+    @Mock private SurfaceControl.Transaction mMockTransaction;
+
+    @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+    @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+    @Mock private Runnable mMockStartCallback;
+
+    @Mock private Runnable mMockEndCallback;
+
+    private PipResizeAnimator mPipResizeAnimator;
+    private Rect mBaseBounds;
+    private Rect mStartBounds;
+    private Rect mEndBounds;
+    private SurfaceControl mTestLeash;
+    private ArgumentCaptor<Matrix> mArgumentCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
+        when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockTransaction);
+        when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockStartTransaction);
+        when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+                .thenReturn(mMockFinishTransaction);
+
+        mArgumentCaptor = ArgumentCaptor.forClass(Matrix.class);
+        mTestLeash = new SurfaceControl.Builder()
+                .setContainerLayer()
+                .setName("PipResizeAnimatorTest")
+                .setCallsite("PipResizeAnimatorTest")
+                .build();
+    }
+
+    @Test
+    public void setAnimationStartCallback_resize_callbackStartCallback() {
+        mBaseBounds = new Rect(100, 100, 500, 500);
+        mStartBounds = new Rect(200, 200, 1_000, 1_000);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 0;
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            mPipResizeAnimator.pause();
+        });
+
+        verify(mMockStartCallback).run();
+        verifyZeroInteractions(mMockEndCallback);
+    }
+
+    @Test
+    public void setAnimationEndCallback_resize_callbackStartAndEndCallback() {
+        mBaseBounds = new Rect(100, 100, 500, 500);
+        mStartBounds = new Rect(200, 200, 1_000, 1_000);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 0;
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            mPipResizeAnimator.end();
+        });
+
+        verify(mMockStartCallback).run();
+        verify(mMockEndCallback).run();
+    }
+
+    @Test
+    public void onAnimationEnd_resizeDown_sizeChanged() {
+        // Resize from 800x800 to 400x400, eg. resize down
+        mBaseBounds = new Rect(100, 100, 500, 500);
+        mStartBounds = new Rect(200, 200, 1_000, 1_000);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 0;
+        final float[] matrix = new float[9];
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+        mPipResizeAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            clearInvocations(mMockTransaction);
+            mPipResizeAnimator.end();
+        });
+
+        // Start transaction scales down from its final state
+        verify(mMockStartTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X],
+                mStartBounds.width() / (float) mEndBounds.width(), FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y],
+                mStartBounds.height() / (float) mEndBounds.height(), FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mStartBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mStartBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Final animation transaction scales to 1 and puts the leash at final position
+        verify(mMockTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Finish transaction resets scale and puts the leash at final position
+        verify(mMockFinishTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+    }
+
+    @Test
+    public void onAnimationEnd_resizeUp_sizeChanged() {
+        // Resize from 400x400 to 800x800, eg. resize up
+        mBaseBounds = new Rect(200, 200, 1_000, 1_000);
+        mStartBounds = new Rect(100, 100, 500, 500);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 0;
+        final float[] matrix = new float[9];
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+        mPipResizeAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            clearInvocations(mMockTransaction);
+            mPipResizeAnimator.end();
+        });
+
+        // Start transaction scales up from its final state
+        verify(mMockStartTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X],
+                mStartBounds.width() / (float) mEndBounds.width(), FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y],
+                mStartBounds.height() / (float) mEndBounds.height(), FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mStartBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mStartBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Final animation transaction scales to 1 and puts the leash at final position
+        verify(mMockTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+
+        // Finish transaction resets scale and puts the leash at final position
+        verify(mMockFinishTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSCALE_X], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSCALE_Y], 1f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_X], mEndBounds.left, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MTRANS_Y], mEndBounds.top, FLOAT_COMPARISON_DELTA);
+    }
+
+    @Test
+    public void onAnimationEnd_withInitialDelta_rotateToZeroDegree() {
+        mBaseBounds = new Rect(200, 200, 1_000, 1_000);
+        mStartBounds = new Rect(100, 100, 500, 500);
+        mEndBounds = new Rect(mBaseBounds);
+        final int duration = 10;
+        final float delta = 45;
+        final float[] matrix = new float[9];
+        mPipResizeAnimator = new PipResizeAnimator(mMockContext, mTestLeash,
+                mMockStartTransaction, mMockFinishTransaction,
+                mBaseBounds, mStartBounds, mEndBounds,
+                duration, delta);
+        mPipResizeAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+        mPipResizeAnimator.setAnimationStartCallback(mMockStartCallback);
+        mPipResizeAnimator.setAnimationEndCallback(mMockEndCallback);
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            mPipResizeAnimator.start();
+            clearInvocations(mMockTransaction);
+            mPipResizeAnimator.end();
+        });
+
+        // Final animation transaction sets skew to zero
+        verify(mMockTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA);
+
+        // Finish transaction sets skew to zero
+        verify(mMockFinishTransaction).setMatrix(eq(mTestLeash), mArgumentCaptor.capture(), any());
+        mArgumentCaptor.getValue().getValues(matrix);
+        assertEquals(matrix[Matrix.MSKEW_X], 0f, FLOAT_COMPARISON_DELTA);
+        assertEquals(matrix[Matrix.MSKEW_Y], 0f, FLOAT_COMPARISON_DELTA);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index afdb687..efe4fb1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -34,6 +34,7 @@
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.TransitionInfoBuilder
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.windowdecor.extension.isFullscreen
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
 import org.junit.Before
@@ -107,8 +108,8 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(change.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
             .isEqualTo(change.taskInfo?.windowingMode)
     }
 
@@ -130,8 +131,8 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(1)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(1)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
             .isEqualTo(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
     }
 
@@ -161,9 +162,9 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
             .isEqualTo(freeformOpenChange.taskInfo?.taskId)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
             .isEqualTo(freeformOpenChange.taskInfo?.windowingMode)
     }
 
@@ -199,9 +200,15 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId).isEqualTo(change.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
             .isEqualTo(change.taskInfo?.windowingMode)
+
+        assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(1)
+        with(listener.taskInfoOnTaskChanged.last()) {
+            assertThat(taskId).isEqualTo(mergedChange.taskInfo?.taskId)
+            assertThat(windowingMode).isEqualTo(mergedChange.taskInfo?.windowingMode)
+        }
     }
 
     @Test
@@ -236,18 +243,151 @@
         callOnTransitionFinished()
         executor.flushAll()
 
-        assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(mergedChange.taskInfo?.taskId)
-        assertThat(listener.taskInfoToBeNotified.windowingMode)
-                .isEqualTo(mergedChange.taskInfo?.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+            .isEqualTo(mergedChange.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
+            .isEqualTo(mergedChange.taskInfo?.windowingMode)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    fun taskChange_freeformWindowToFullscreenWindow_listenerNotified() {
+        val listener = TestListener()
+        val executor = TestShellExecutor()
+        transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+        val freeformState =
+            createChange(
+                WindowManager.TRANSIT_OPEN,
+                createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+            )
+        val transitionInfoOpen =
+            TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(freeformState).build()
+        callOnTransitionReady(transitionInfoOpen)
+        callOnTransitionFinished()
+        executor.flushAll()
+
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+            .isEqualTo(freeformState.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.windowingMode)
+            .isEqualTo(freeformState.taskInfo?.windowingMode)
+        assertThat(listener.taskInfoOnTaskMovedToFront.isFullscreen).isEqualTo(false)
+
+        // create change transition to update the windowing mode to full screen.
+        val fullscreenState =
+            createChange(
+                WindowManager.TRANSIT_CHANGE,
+                createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+            )
+        val transitionInfoChange =
+            TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
+                .addChange(fullscreenState)
+                .build()
+
+        callOnTransitionReady(transitionInfoChange)
+        callOnTransitionFinished()
+        executor.flushAll()
+
+        // Asserting whether freeformState remains the same as before the change
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+            .isEqualTo(freeformState.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.isFullscreen).isEqualTo(false)
+
+        // Asserting changes
+        assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(1)
+        with(listener.taskInfoOnTaskChanged.last()) {
+            assertThat(taskId).isEqualTo(fullscreenState.taskInfo?.taskId)
+            assertThat(isFullscreen).isEqualTo(true)
+        }
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    fun singleTransition_withOpenAndChange_onlyOpenIsNotified() {
+        val listener = TestListener()
+        val executor = TestShellExecutor()
+        transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+        // Creating multiple changes to be fired in a single transition
+        val freeformState =
+            createChange(
+                mode = WindowManager.TRANSIT_OPEN,
+                taskInfo = createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+            )
+        val fullscreenState =
+            createChange(
+                mode = WindowManager.TRANSIT_CHANGE,
+                taskInfo = createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+            )
+
+        val transitionInfoWithChanges =
+            TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
+                .addChange(freeformState)
+                .addChange(fullscreenState)
+                .build()
+
+        callOnTransitionReady(transitionInfoWithChanges)
+        callOnTransitionFinished()
+        executor.flushAll()
+
+        assertThat(listener.taskInfoOnTaskMovedToFront.taskId)
+            .isEqualTo(freeformState.taskInfo?.taskId)
+        assertThat(listener.taskInfoOnTaskMovedToFront.isFullscreen).isEqualTo(false)
+        assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(0)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+    fun singleTransition_withMultipleChanges_listenerNotified_forEachChange() {
+        val listener = TestListener()
+        val executor = TestShellExecutor()
+        transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+        val taskId = 1
+
+        // Creating multiple changes to be fired in a single transition
+        val changes =
+            listOf(
+                    WindowConfiguration.WINDOWING_MODE_FREEFORM,
+                    WindowConfiguration.WINDOW_CONFIG_DISPLAY_ROTATION,
+                    WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+                )
+                .map { change ->
+                    createChange(
+                        mode = WindowManager.TRANSIT_CHANGE,
+                        taskInfo = createTaskInfo(taskId, change)
+                    )
+                }
+
+        val transitionInfoWithChanges =
+            TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0)
+                .apply { changes.forEach { c -> this@apply.addChange(c) } }
+                .build()
+
+        callOnTransitionReady(transitionInfoWithChanges)
+        callOnTransitionFinished()
+        executor.flushAll()
+
+        assertThat(listener.taskInfoOnTaskChanged.size).isEqualTo(changes.size)
+        changes.forEachIndexed { index, change ->
+            assertThat(listener.taskInfoOnTaskChanged[index].taskId)
+                .isEqualTo(change.taskInfo?.taskId)
+            assertThat(listener.taskInfoOnTaskChanged[index].windowingMode)
+                .isEqualTo(change.taskInfo?.windowingMode)
+        }
     }
 
     class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener {
-        var taskInfoToBeNotified = ActivityManager.RunningTaskInfo()
+        var taskInfoOnTaskMovedToFront = ActivityManager.RunningTaskInfo()
+        var taskInfoOnTaskChanged = mutableListOf<ActivityManager.RunningTaskInfo>()
 
         override fun onTaskMovedToFrontThroughTransition(
             taskInfo: ActivityManager.RunningTaskInfo
         ) {
-            taskInfoToBeNotified = taskInfo
+            taskInfoOnTaskMovedToFront = taskInfo
+        }
+
+        override fun onTaskChangedThroughTransition(taskInfo: ActivityManager.RunningTaskInfo) {
+            taskInfoOnTaskChanged += taskInfo
         }
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
index 708fadb..99e8295 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/util/WindowingEducationTestUtils.kt
@@ -32,11 +32,13 @@
     runningTaskInfo: RunningTaskInfo = createTaskInfo(),
     isHandleMenuExpanded: Boolean = false,
     globalAppHandleBounds: Rect = Rect(),
+    isCapturedLinkAvailable: Boolean = false
 ): CaptionState.AppHandle =
     CaptionState.AppHandle(
         runningTaskInfo = runningTaskInfo,
         isHandleMenuExpanded = isHandleMenuExpanded,
-        globalAppHandleBounds = globalAppHandleBounds)
+        globalAppHandleBounds = globalAppHandleBounds,
+        isCapturedLinkAvailable = isCapturedLinkAvailable)
 
 /**
  * Create an instance of [CaptionState.AppHeader] with parameters as properties.
@@ -47,11 +49,13 @@
     runningTaskInfo: RunningTaskInfo = createTaskInfo(),
     isHeaderMenuExpanded: Boolean = false,
     globalAppChipBounds: Rect = Rect(),
+    isCapturedLinkAvailable: Boolean = false
 ): CaptionState.AppHeader =
     CaptionState.AppHeader(
         runningTaskInfo = runningTaskInfo,
         isHeaderMenuExpanded = isHeaderMenuExpanded,
-        globalAppChipBounds = globalAppChipBounds)
+        globalAppChipBounds = globalAppChipBounds,
+        isCapturedLinkAvailable = isCapturedLinkAvailable)
 
 /**
  * Create an instance of [RunningTaskInfo] with parameters as properties.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index c42be7f..03aab18 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -95,6 +95,7 @@
 import com.android.wm.shell.desktopmode.DesktopTasksLimiter
 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository
 import com.android.wm.shell.desktopmode.education.AppHandleEducationController
+import com.android.wm.shell.desktopmode.education.AppToWebEducationController
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
@@ -195,6 +196,7 @@
             DesktopModeWindowDecorViewModel.TaskPositionerFactory
     @Mock private lateinit var mockTaskPositioner: TaskPositioner
     @Mock private lateinit var mockAppHandleEducationController: AppHandleEducationController
+    @Mock private lateinit var mockAppToWebEducationController: AppToWebEducationController
     @Mock private lateinit var mockFocusTransitionObserver: FocusTransitionObserver
     @Mock private lateinit var mockCaptionHandleRepository: WindowDecorCaptionHandleRepository
     @Mock private lateinit var motionEvent: MotionEvent
@@ -261,6 +263,7 @@
                 mockInteractionJankMonitor,
                 Optional.of(mockTasksLimiter),
                 mockAppHandleEducationController,
+                mockAppToWebEducationController,
                 mockCaptionHandleRepository,
                 Optional.of(mockActivityOrientationChangeHandler),
                 mockTaskPositionerFactory,
@@ -946,7 +949,7 @@
     }
 
     @Test
-    fun testDecor_onClickToSplitScreen_detachesStatusBarInputLayer() {
+    fun testDecor_onClickToSplitScreen_disposesStatusBarInputLayer() {
         val toSplitScreenListenerCaptor = forClass(Function0::class.java)
                 as ArgumentCaptor<Function0<Unit>>
         val decor = createOpenTaskDecoration(
@@ -956,7 +959,7 @@
 
         toSplitScreenListenerCaptor.value.invoke()
 
-        verify(decor).detachStatusBarInputLayer()
+        verify(decor).disposeStatusBarInputLayer()
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 8a2c778..41f57ae 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -49,6 +49,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.kotlin.VerificationKt.times;
 
 import android.app.ActivityManager;
 import android.app.assist.AssistContent;
@@ -261,8 +262,8 @@
         doReturn(defaultDisplay).when(mMockDisplayController).getDisplay(Display.DEFAULT_DISPLAY);
         doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
         when(mMockHandleMenuFactory.create(any(), any(), anyInt(), any(), any(), any(),
-                anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt(),
-                anyInt()))
+                anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(),
+                anyInt(), anyInt()))
                 .thenReturn(mMockHandleMenu);
         when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false);
         when(mMockAppHeaderViewHolderFactory.create(any(), any(), any(), any(), any(), any(), any(),
@@ -849,7 +850,8 @@
         ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
         spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
-        verify(mMockHandler).post(runnableArgument.capture());
+        // Once for view host, the other for the AppHandle input layer.
+        verify(mMockHandler, times(2)).post(runnableArgument.capture());
         runnableArgument.getValue().run();
         verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any());
     }
@@ -876,7 +878,8 @@
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
         spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
-        verify(mMockHandler).post(runnableArgument.capture());
+        // Once for view host, the other for the AppHandle input layer.
+        verify(mMockHandler, times(2)).post(runnableArgument.capture());
 
         spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
@@ -890,7 +893,8 @@
         taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class);
         spyWindowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
-        verify(mMockHandler).post(runnableArgument.capture());
+        // Once for view host, the other for the AppHandle input layer.
+        verify(mMockHandler, times(2)).post(runnableArgument.capture());
 
         spyWindowDecor.close();
 
@@ -1174,6 +1178,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 openInBrowserCaptor.capture(),
                 any(),
                 any(),
@@ -1204,6 +1209,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 openInBrowserCaptor.capture(),
                 any(),
                 any(),
@@ -1259,6 +1265,7 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 closeClickListener.capture(),
                 any(),
                 anyBoolean()
@@ -1290,12 +1297,14 @@
                 any(),
                 any(),
                 any(),
+                any(),
                 /* forceShowSystemBars= */ eq(true)
         );
     }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION)
+    @DisableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_HANDLE_EDUCATION,
+            Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION})
     public void notifyCaptionStateChanged_flagDisabled_doNoNotify() {
         when(DesktopModeStatus.canEnterDesktopMode(mContext)).thenReturn(true);
         final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
@@ -1433,7 +1442,7 @@
 
     private void verifyHandleMenuCreated(@Nullable Uri uri) {
         verify(mMockHandleMenuFactory).create(any(), any(), anyInt(), any(), any(),
-                any(), anyBoolean(), anyBoolean(), anyBoolean(),
+                any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
                 argThat(intent -> (uri == null && intent == null) || intent.getData().equals(uri)),
                 anyInt(), anyInt(), anyInt(), anyInt());
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 9544fa8..ade17c6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -266,6 +266,7 @@
             WindowManagerWrapper(mockWindowManager),
             layoutId, appIcon, appName, splitScreenController, shouldShowWindowingPill = true,
             shouldShowNewWindowButton = true, shouldShowManageWindowsButton = false,
+            shouldShowChangeAspectRatioButton = false,
             null /* openInBrowserLink */, captionWidth = HANDLE_WIDTH, captionHeight = 50,
             captionX = captionX,
             captionY = 0,
@@ -276,6 +277,7 @@
             onToSplitScreenClickListener = mock(),
             onNewWindowClickListener = mock(),
             onManageWindowsClickListener = mock(),
+            onChangeAspectRatioClickListener = mock(),
             openInBrowserClickListener = mock(),
             onOpenByDefaultClickListener = mock(),
             onCloseMenuClickListener = mock(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index cb7fade..8e0434c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -218,8 +218,6 @@
         verify(captionContainerSurfaceBuilder, never()).build();
         verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any());
 
-        verify(mMockSurfaceControlFinishT).hide(mMockTaskSurface);
-
         assertNull(mRelayoutResult.mRootView);
     }
 
@@ -281,8 +279,6 @@
 
         verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
         verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
-        verify(mMockSurfaceControlStartT)
-                .show(mMockTaskSurface);
         verify(mMockSurfaceControlStartT).setShadowRadius(mMockTaskSurface, 10);
 
         assertEquals(300, mRelayoutResult.mWidth);
@@ -863,7 +859,7 @@
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
 
-        mRelayoutParams.mSetTaskPositionAndCrop = false;
+        mRelayoutParams.mSetTaskVisibilityPositionAndCrop = false;
         windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
         verify(mMockSurfaceControlStartT, never()).setWindowCrop(
@@ -891,7 +887,7 @@
         taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
         final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
 
-        mRelayoutParams.mSetTaskPositionAndCrop = true;
+        mRelayoutParams.mSetTaskVisibilityPositionAndCrop = true;
         windowDecor.relayout(taskInfo, true /* hasGlobalFocus */);
 
         verify(mMockSurfaceControlStartT).setWindowCrop(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
index 741dfb8..15f2c7b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/education/DesktopWindowingEducationTooltipControllerTest.kt
@@ -285,7 +285,7 @@
       onEducationClickAction: () -> Unit = {},
       onDismissAction: () -> Unit = {}
   ) =
-      DesktopWindowingEducationTooltipController.EducationViewConfig(
+      DesktopWindowingEducationTooltipController.TooltipEducationViewConfig(
           tooltipViewLayout = tooltipViewLayout,
           tooltipColorScheme = tooltipColorScheme,
           tooltipViewGlobalCoordinates = tooltipViewGlobalCoordinates,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
index 52e93bb..80ad1df 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
@@ -36,6 +36,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
@@ -145,7 +146,7 @@
     }
 
     @Test
-    fun userChange_starting_allTilingSessionsShouldBeDestroyed() {
+    fun onUserChange_allTilingSessionsShouldBeDestroyed() {
         desktopTilingDecorViewModel.tilingTransitionHandlerByDisplayId.put(
             1,
             desktopTilingDecoration,
@@ -157,7 +158,29 @@
 
         desktopTilingDecorViewModel.onUserChange()
 
-        verify(desktopTilingDecoration, times(2)).onUserChange()
+        verify(desktopTilingDecoration, times(2)).resetTilingSession()
+    }
+
+    @Test
+    fun displayOrientationChange_tilingForDisplayShouldBeDestroyed() {
+        desktopTilingDecorViewModel.tilingTransitionHandlerByDisplayId.put(
+            1,
+            desktopTilingDecoration,
+        )
+        desktopTilingDecorViewModel.tilingTransitionHandlerByDisplayId.put(
+            2,
+            desktopTilingDecoration,
+        )
+
+        desktopTilingDecorViewModel.onDisplayChange(1, 1, 2, null, null)
+
+        verify(desktopTilingDecoration, times(1)).resetTilingSession()
+        verify(displayControllerMock, times(1))
+            .addDisplayChangingController(eq(desktopTilingDecorViewModel))
+
+        desktopTilingDecorViewModel.onDisplayChange(1, 1, 3, null, null)
+        // No extra calls after 180 degree change.
+        verify(desktopTilingDecoration, times(1)).resetTilingSession()
     }
 
     companion object {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
index 0ee3f46..3143946 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManagerTest.kt
@@ -16,9 +16,12 @@
 
 package com.android.wm.shell.windowdecor.tiling
 
+import android.content.Context
 import android.content.res.Configuration
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.RoundedCorner
 import android.view.SurfaceControl
 import androidx.test.annotation.UiThreadTest
 import androidx.test.filters.SmallTest
@@ -29,6 +32,7 @@
 import org.junit.Before
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
@@ -55,10 +59,17 @@
 
     private lateinit var desktopTilingWindowManager: DesktopTilingDividerWindowManager
 
+    private val context = mock<Context>()
+    private val display = mock<Display>()
+    private val roundedCorner = mock<RoundedCorner>()
+
     @Before
     fun setup() {
         config = Configuration()
         config.setToDefaults()
+        whenever(context.display).thenReturn(display)
+        whenever(display.getRoundedCorner(any())).thenReturn(roundedCorner)
+        whenever(roundedCorner.radius).thenReturn(CORNER_RADIUS)
         desktopTilingWindowManager =
             DesktopTilingDividerWindowManager(
                 config,
@@ -69,6 +80,7 @@
                 transitionHandlerMock,
                 transactionSupplierMock,
                 BOUNDS,
+                context,
             )
     }
 
@@ -85,7 +97,6 @@
 
         // Ensure a surfaceControl transaction runs to show the divider.
         verify(transactionSupplierMock, times(1)).get()
-        verify(syncQueueMock, times(1)).runInSync(any())
 
         desktopTilingWindowManager.release()
         verify(transaction, times(1)).hide(any())
@@ -93,7 +104,24 @@
         verify(transaction, times(1)).apply()
     }
 
+    @Test
+    @UiThreadTest
+    fun testWindowManager_accountsForRoundedCornerDimensions() {
+        whenever(transactionSupplierMock.get()).thenReturn(transaction)
+        whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
+        whenever(transaction.setRelativeLayer(any(), any(), any())).thenReturn(transaction)
+        whenever(transaction.setPosition(any(), any(), any())).thenReturn(transaction)
+        whenever(transaction.show(any())).thenReturn(transaction)
+
+        desktopTilingWindowManager.generateViewHost(surfaceControl)
+
+        // Ensure a surfaceControl transaction runs to show the divider.
+        verify(transaction, times(1))
+            .setPosition(any(), eq(BOUNDS.left.toFloat() - CORNER_RADIUS), any())
+    }
+
     companion object {
         private val BOUNDS = Rect(1, 2, 3, 4)
+        private val CORNER_RADIUS = 28
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index 2ae2461..f371f52 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -475,7 +475,7 @@
         tilingDecoration.rightTaskResizingHelper = tiledTaskHelper
         tilingDecoration.desktopTilingDividerWindowManager = desktopTilingDividerWindowManager
 
-        tilingDecoration.onUserChange()
+        tilingDecoration.resetTilingSession()
 
         assertThat(tilingDecoration.leftTaskResizingHelper).isNull()
         assertThat(tilingDecoration.rightTaskResizingHelper).isNull()
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index 27817e9..faf84a8 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -3,8 +3,9 @@
 
   public final class AppFunctionManager {
     ctor public AppFunctionManager(android.content.Context);
-    method 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 public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>);
+    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 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>);
     field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0
     field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2
@@ -34,6 +35,7 @@
   }
 
   public final class ExecuteAppFunctionResponse {
+    method public int getErrorCategory();
     method @Nullable public String getErrorMessage();
     method @NonNull public android.os.Bundle getExtras();
     method public int getResultCode();
@@ -41,14 +43,19 @@
     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 = 2; // 0x2
-    field public static final int RESULT_CANCELLED = 6; // 0x6
-    field public static final int RESULT_DENIED = 1; // 0x1
-    field public static final int RESULT_DISABLED = 5; // 0x5
-    field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
-    field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+    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
   }
 
 }
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
index 6870446..2075104 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java
@@ -16,9 +16,11 @@
 
 package com.google.android.appfunctions.sidecar;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.UserHandleAware;
 import android.content.Context;
@@ -103,6 +105,12 @@
      * <p>See {@link android.app.appfunctions.AppFunctionManager#executeAppFunction} for the
      * documented behaviour of this method.
      */
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+                Manifest.permission.EXECUTE_APP_FUNCTIONS
+            },
+            conditional = true)
     public void executeAppFunction(
             @NonNull ExecuteAppFunctionRequest sidecarRequest,
             @NonNull @CallbackExecutor Executor executor,
@@ -131,6 +139,12 @@
      * <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
      * documented behaviour of this method.
      */
+    @RequiresPermission(
+            anyOf = {
+                Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
+                Manifest.permission.EXECUTE_APP_FUNCTIONS
+            },
+            conditional = true)
     public void isAppFunctionEnabled(
             @NonNull String functionIdentifier,
             @NonNull String targetPackage,
@@ -140,6 +154,19 @@
     }
 
     /**
+     * Returns a boolean through a callback, indicating whether the app function is enabled.
+     *
+     * <p>See {@link android.app.appfunctions.AppFunctionManager#isAppFunctionEnabled} for the
+     * documented behaviour of this method.
+     */
+    public void isAppFunctionEnabled(
+            @NonNull String functionIdentifier,
+            @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Boolean, Exception> callback) {
+        mManager.isAppFunctionEnabled(functionIdentifier, executor, callback);
+    }
+
+    /**
      * Sets the enabled state of the app function owned by the calling package.
      *
      * <p>See {@link android.app.appfunctions.AppFunctionManager#setAppFunctionEnabled} for the
diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
index d5dfaeb..4e88fb0 100644
--- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java
@@ -50,38 +50,102 @@
      */
     public static final String PROPERTY_RETURN_VALUE = "returnValue";
 
-    /** The call was successful. */
+    /**
+     * 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. */
-    public static final int RESULT_DENIED = 1;
+    /**
+     * 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.
-     */
-    public static final int RESULT_APP_UNKNOWN_ERROR = 2;
-
-    /** An internal unexpected error coming from the system. */
-    public static final int RESULT_INTERNAL_ERROR = 3;
-
-    /**
-     * The caller supplied invalid arguments to the call.
      *
-     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     * <p>This error is in the {@link #ERROR_CATEGORY_APP} category.
      */
-    public static final int RESULT_INVALID_ARGUMENT = 4;
-
-    /** The caller tried to execute a disabled app function. */
-    public static final int RESULT_DISABLED = 5;
+    public static final int RESULT_APP_UNKNOWN_ERROR = 3000;
 
     /**
-     * The operation was cancelled. Use this error code to report that a cancellation is done after
-     * receiving a cancellation signal.
+     * The error category is unknown.
+     *
+     * <p>This is the default value for {@link #getErrorCategory}.
      */
-    public static final int RESULT_CANCELLED = 6;
+    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;
@@ -171,6 +235,36 @@
     }
 
     /**
+     * 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.
@@ -238,11 +332,28 @@
                 RESULT_OK,
                 RESULT_DENIED,
                 RESULT_APP_UNKNOWN_ERROR,
-                RESULT_INTERNAL_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/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
index 1f9fddd..264f842 100644
--- a/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
+++ b/libs/appfunctions/tests/src/com/google/android/appfunctions/sidecar/tests/SidecarConverterTest.kt
@@ -105,7 +105,7 @@
         val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
         val platformResponse =
             ExecuteAppFunctionResponse.newFailure(
-                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                 null,
                 null
             )
@@ -119,7 +119,7 @@
         assertThat(sidecarResponse.resultDocument.id).isEqualTo(emptyGd.id)
         assertThat(sidecarResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
         assertThat(sidecarResponse.resultCode)
-            .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR)
+            .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
         assertThat(sidecarResponse.errorMessage).isNull()
     }
 
@@ -152,7 +152,7 @@
         val emptyGd = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "").build()
         val sidecarResponse =
             com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse.newFailure(
-                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                 null,
                 null
             )
@@ -166,7 +166,7 @@
         assertThat(platformResponse.resultDocument.id).isEqualTo(emptyGd.id)
         assertThat(platformResponse.resultDocument.schemaType).isEqualTo(emptyGd.schemaType)
         assertThat(platformResponse.resultCode)
-            .isEqualTo(ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR)
+            .isEqualTo(ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR)
         assertThat(platformResponse.errorMessage).isNull()
     }
 }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d93ad4e..fcb7efc 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -384,6 +384,7 @@
         "jni/ScopedParcel.cpp",
         "jni/Shader.cpp",
         "jni/RenderEffect.cpp",
+        "jni/RuntimeEffectUtils.cpp",
         "jni/Typeface.cpp",
         "jni/Utils.cpp",
         "jni/YuvToJpegEncoder.cpp",
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
index 3a3bfb47..f6b6be0 100644
--- a/libs/hwui/ColorFilter.h
+++ b/libs/hwui/ColorFilter.h
@@ -22,6 +22,7 @@
 #include <memory>
 
 #include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
 #include "SkColorFilter.h"
 
 namespace android {
@@ -113,6 +114,36 @@
     std::vector<float> mMatrix;
 };
 
+class RuntimeColorFilter : public ColorFilter {
+public:
+    RuntimeColorFilter(SkRuntimeEffectBuilder* builder) : mBuilder(builder) {}
+
+    void updateUniforms(JNIEnv* env, const char* name, const float vals[], int count,
+                        bool isColor) {
+        UpdateFloatUniforms(env, mBuilder, name, vals, count, isColor);
+        discardInstance();
+    }
+
+    void updateUniforms(JNIEnv* env, const char* name, const int vals[], int count) {
+        UpdateIntUniforms(env, mBuilder, name, vals, count);
+        discardInstance();
+    }
+
+    void updateChild(JNIEnv* env, const char* name, SkFlattenable* childEffect) {
+        UpdateChild(env, mBuilder, name, childEffect);
+        discardInstance();
+    }
+
+private:
+    sk_sp<SkColorFilter> createInstance() override {
+        // TODO: throw error if null
+        return mBuilder->makeColorFilter();
+    }
+
+private:
+    SkRuntimeEffectBuilder* mBuilder;
+};
+
 }  // namespace uirenderer
 }  // namespace android
 
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index f255967..5ad788c 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -146,3 +146,11 @@
   description: "Whether to have more information in ashmem filenames for bitmaps"
   bug: "369619160"
 }
+
+flag {
+  name: "animated_image_drawable_filter_bitmap"
+  is_exported: true
+  namespace: "core_graphics"
+  description: "API's that enable animated image drawables to use nearest sampling when scaling."
+  bug: "370523334"
+}
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 69613c7..5e379aa 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -347,4 +347,26 @@
     return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration());
 }
 
+bool AnimatedImageDrawable::getFilterBitmap() const {
+    const SkFilterMode kFilterBitmap = mSkAnimatedImage->getFilterMode();
+    if (kFilterBitmap == SkFilterMode::kLinear) {
+        return true;
+    }
+    return false;
+}
+
+bool AnimatedImageDrawable::setFilterBitmap(bool filterBitmap) {
+    if (filterBitmap) {
+        if (mSkAnimatedImage->getFilterMode() == SkFilterMode::kLinear) {
+            return false;
+        }
+        mSkAnimatedImage->setFilterMode(SkFilterMode::kLinear);
+    } else {
+        if (mSkAnimatedImage->getFilterMode() == SkFilterMode::kNearest) {
+            return false;
+        }
+        mSkAnimatedImage->setFilterMode(SkFilterMode::kNearest);
+    }
+    return true;
+}
 }  // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 1e965ab..2212324 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -87,6 +87,11 @@
     bool isRunning();
     int getRepetitionCount() const { return mSkAnimatedImage->getRepetitionCount(); }
     void setRepetitionCount(int count) { mSkAnimatedImage->setRepetitionCount(count); }
+    // Returns true if the filter mode is set to linear sampling; false if it is
+    // set to nearest neighbor sampling.
+    bool getFilterBitmap() const;
+    // Returns true if the filter mode was changed; false otherwise.
+    bool setFilterBitmap(bool filterBitmap);
 
     void setOnAnimationEndListener(std::unique_ptr<OnAnimationEndListener> listener) {
         mEndListener = std::move(listener);
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index b01e38d..2c8530d 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -276,6 +276,18 @@
     drawable->setStagingBounds(rect);
 }
 
+static jboolean AnimatedImageDrawable_nSetFilterBitmap(JNIEnv* env, jobject /*clazz*/,
+                                                       jlong nativePtr, jboolean filterBitmap) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    return drawable->setFilterBitmap(filterBitmap);
+}
+
+static jboolean AnimatedImageDrawable_nGetFilterBitmap(JNIEnv* env, jobject /*clazz*/,
+                                                       jlong nativePtr) {
+    auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
+    return drawable->getFilterBitmap();
+}
+
 static const JNINativeMethod gAnimatedImageDrawableMethods[] = {
         {"nCreate", "(JLandroid/graphics/ImageDecoder;IIJZLandroid/graphics/Rect;)J",
          (void*)AnimatedImageDrawable_nCreate},
@@ -294,6 +306,8 @@
         {"nNativeByteSize", "(J)J", (void*)AnimatedImageDrawable_nNativeByteSize},
         {"nSetMirrored", "(JZ)V", (void*)AnimatedImageDrawable_nSetMirrored},
         {"nSetBounds", "(JLandroid/graphics/Rect;)V", (void*)AnimatedImageDrawable_nSetBounds},
+        {"nSetFilterBitmap", "(JZ)Z", (void*)AnimatedImageDrawable_nSetFilterBitmap},
+        {"nGetFilterBitmap", "(J)Z", (void*)AnimatedImageDrawable_nGetFilterBitmap},
 };
 
 int register_android_graphics_drawable_AnimatedImageDrawable(JNIEnv* env) {
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 0b95148..20301d2 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -18,7 +18,9 @@
 #include "ColorFilter.h"
 
 #include "GraphicsJNI.h"
+#include "RuntimeEffectUtils.h"
 #include "SkBlendMode.h"
+#include "include/effects/SkRuntimeEffect.h"
 
 namespace android {
 
@@ -89,6 +91,78 @@
             filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
         }
     }
+
+    static jlong RuntimeColorFilter_createColorFilter(JNIEnv* env, jobject, jstring agsl) {
+        ScopedUtfChars strSksl(env, agsl);
+        auto result = SkRuntimeEffect::MakeForColorFilter(SkString(strSksl.c_str()),
+                                                          SkRuntimeEffect::Options{});
+        if (result.effect.get() == nullptr) {
+            doThrowIAE(env, result.errorText.c_str());
+            return 0;
+        }
+        auto builder = new SkRuntimeEffectBuilder(std::move(result.effect));
+        auto* runtimeColorFilter = new RuntimeColorFilter(builder);
+        runtimeColorFilter->incStrong(nullptr);
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(runtimeColorFilter));
+    }
+
+    static void RuntimeColorFilter_updateUniformsFloatArray(JNIEnv* env, jobject,
+                                                            jlong colorFilterPtr,
+                                                            jstring uniformName,
+                                                            jfloatArray uniforms,
+                                                            jboolean isColor) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        AutoJavaFloatArray autoValues(env, uniforms, 0, kRO_JNIAccess);
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length(),
+                                   isColor);
+        }
+    }
+
+    static void RuntimeColorFilter_updateUniformsFloats(JNIEnv* env, jobject, jlong colorFilterPtr,
+                                                        jstring uniformName, jfloat value1,
+                                                        jfloat value2, jfloat value3, jfloat value4,
+                                                        jint count) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        const float values[4] = {value1, value2, value3, value4};
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), values, count, false);
+        }
+    }
+
+    static void RuntimeColorFilter_updateUniformsIntArray(JNIEnv* env, jobject,
+                                                          jlong colorFilterPtr, jstring uniformName,
+                                                          jintArray uniforms) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        AutoJavaIntArray autoValues(env, uniforms, 0);
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), autoValues.ptr(), autoValues.length());
+        }
+    }
+
+    static void RuntimeColorFilter_updateUniformsInts(JNIEnv* env, jobject, jlong colorFilterPtr,
+                                                      jstring uniformName, jint value1, jint value2,
+                                                      jint value3, jint value4, jint count) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, uniformName);
+        const int values[4] = {value1, value2, value3, value4};
+        if (filter) {
+            filter->updateUniforms(env, name.c_str(), values, count);
+        }
+    }
+
+    static void RuntimeColorFilter_updateChild(JNIEnv* env, jobject, jlong colorFilterPtr,
+                                               jstring childName, jlong childPtr) {
+        auto* filter = reinterpret_cast<RuntimeColorFilter*>(colorFilterPtr);
+        ScopedUtfChars name(env, childName);
+        auto* child = reinterpret_cast<SkFlattenable*>(childPtr);
+        if (filter && child) {
+            filter->updateChild(env, name.c_str(), child);
+        }
+    }
 };
 
 static const JNINativeMethod colorfilter_methods[] = {
@@ -107,6 +181,20 @@
         {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
         {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};
 
+static const JNINativeMethod runtime_color_filter_methods[] = {
+        {"nativeCreateRuntimeColorFilter", "(Ljava/lang/String;)J",
+         (void*)ColorFilterGlue::RuntimeColorFilter_createColorFilter},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[FZ)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloatArray},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;FFFFI)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsFloats},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;[I)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsIntArray},
+        {"nativeUpdateUniforms", "(JLjava/lang/String;IIIII)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateUniformsInts},
+        {"nativeUpdateChild", "(JLjava/lang/String;J)V",
+         (void*)ColorFilterGlue::RuntimeColorFilter_updateChild}};
+
 int register_android_graphics_ColorFilter(JNIEnv* env) {
     android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
                                   NELEM(colorfilter_methods));
@@ -118,7 +206,10 @@
                                   NELEM(lighting_methods));
     android::RegisterMethodsOrDie(env, "android/graphics/ColorMatrixColorFilter",
                                   colormatrix_methods, NELEM(colormatrix_methods));
-    
+    android::RegisterMethodsOrDie(env, "android/graphics/RuntimeColorFilter",
+                                  runtime_color_filter_methods,
+                                  NELEM(runtime_color_filter_methods));
+
     return 0;
 }
 
diff --git a/libs/hwui/jni/RuntimeEffectUtils.cpp b/libs/hwui/jni/RuntimeEffectUtils.cpp
new file mode 100644
index 0000000..46db863
--- /dev/null
+++ b/libs/hwui/jni/RuntimeEffectUtils.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+#include "RuntimeEffectUtils.h"
+
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android {
+namespace uirenderer {
+
+static inline int ThrowIAEFmt(JNIEnv* env, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    int ret = jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", fmt, args);
+    va_end(args);
+    return ret;
+}
+
+bool isIntUniformType(const SkRuntimeEffect::Uniform::Type& type) {
+    switch (type) {
+        case SkRuntimeEffect::Uniform::Type::kFloat:
+        case SkRuntimeEffect::Uniform::Type::kFloat2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4:
+        case SkRuntimeEffect::Uniform::Type::kFloat2x2:
+        case SkRuntimeEffect::Uniform::Type::kFloat3x3:
+        case SkRuntimeEffect::Uniform::Type::kFloat4x4:
+            return false;
+        case SkRuntimeEffect::Uniform::Type::kInt:
+        case SkRuntimeEffect::Uniform::Type::kInt2:
+        case SkRuntimeEffect::Uniform::Type::kInt3:
+        case SkRuntimeEffect::Uniform::Type::kInt4:
+            return true;
+    }
+}
+
+void UpdateFloatUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                         const float values[], int count, bool isColor) {
+    SkRuntimeEffectBuilder::BuilderUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (isColor != ((uniform.fVar->flags & SkRuntimeEffect::Uniform::kColor_Flag) != 0)) {
+        if (isColor) {
+            jniThrowExceptionFmt(
+                    env, "java/lang/IllegalArgumentException",
+                    "attempting to set a color uniform using the non-color specific APIs: %s %x",
+                    uniformName, uniform.fVar->flags);
+        } else {
+            ThrowIAEFmt(env,
+                        "attempting to set a non-color uniform using the setColorUniform APIs: %s",
+                        uniformName);
+        }
+    } else if (isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a int uniform using the setUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<float>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+void UpdateIntUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                       const int values[], int count) {
+    SkRuntimeEffectBuilder::BuilderUniform uniform = builder->uniform(uniformName);
+    if (uniform.fVar == nullptr) {
+        ThrowIAEFmt(env, "unable to find uniform named %s", uniformName);
+    } else if (!isIntUniformType(uniform.fVar->type)) {
+        ThrowIAEFmt(env, "attempting to set a non-int uniform using the setIntUniform APIs: %s",
+                    uniformName);
+    } else if (!uniform.set<int>(values, count)) {
+        ThrowIAEFmt(env, "mismatch in byte size for uniform [expected: %zu actual: %zu]",
+                    uniform.fVar->sizeInBytes(), sizeof(float) * count);
+    }
+}
+
+void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* childName,
+                 SkFlattenable* childEffect) {
+    SkRuntimeShaderBuilder::BuilderChild builderChild = builder->child(childName);
+    if (builderChild.fChild == nullptr) {
+        ThrowIAEFmt(env, "unable to find shader named %s", childName);
+        return;
+    }
+
+    builderChild = sk_ref_sp(childEffect);
+}
+
+}  // namespace uirenderer
+}  // namespace android
\ No newline at end of file
diff --git a/libs/hwui/jni/RuntimeEffectUtils.h b/libs/hwui/jni/RuntimeEffectUtils.h
new file mode 100644
index 0000000..75623c0
--- /dev/null
+++ b/libs/hwui/jni/RuntimeEffectUtils.h
@@ -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.
+ */
+
+#ifndef RUNTIMEEFFECTUTILS_H
+#define RUNTIMEEFFECTUTILS_H
+
+#include "GraphicsJNI.h"
+#include "include/effects/SkRuntimeEffect.h"
+
+namespace android {
+namespace uirenderer {
+
+void UpdateFloatUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                         const float values[], int count, bool isColor);
+
+void UpdateIntUniforms(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* uniformName,
+                       const int values[], int count);
+
+void UpdateChild(JNIEnv* env, SkRuntimeEffectBuilder* builder, const char* childName,
+                 SkFlattenable* childEffect);
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // MAIN_RUNTIMEEFFECTUTILS_H
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 6e03bbd..d9dc8eb 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -593,9 +593,9 @@
 
             Matrix4 transform;
             SkIRect clipBounds;
+            uirenderer::Rect initialClipBounds;
+            const auto clipFlags = props.getClippingFlags();
             if (enableClip) {
-                uirenderer::Rect initialClipBounds;
-                const auto clipFlags = props.getClippingFlags();
                 if (clipFlags) {
                     props.getClippingRectForFlags(clipFlags, &initialClipBounds);
                 } else {
@@ -659,8 +659,8 @@
                         static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
                         static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom),
                         static_cast<jint>(clipBounds.fLeft), static_cast<jint>(clipBounds.fTop),
-                        static_cast<jint>(clipBounds.fRight),
-                        static_cast<jint>(clipBounds.fBottom));
+                        static_cast<jint>(clipBounds.fRight), static_cast<jint>(clipBounds.fBottom),
+                        static_cast<jint>(props.getWidth()), static_cast<jint>(props.getHeight()));
             }
             if (!keepListening) {
                 env->DeleteGlobalRef(mListener);
@@ -891,7 +891,7 @@
     gPositionListener.callPositionChanged = GetStaticMethodIDOrDie(
             env, clazz, "callPositionChanged", "(Ljava/lang/ref/WeakReference;JIIII)Z");
     gPositionListener.callPositionChanged2 = GetStaticMethodIDOrDie(
-            env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIII)Z");
+            env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIIIII)Z");
     gPositionListener.callApplyStretch = GetStaticMethodIDOrDie(
             env, clazz, "callApplyStretch", "(Ljava/lang/ref/WeakReference;JFFFFFFFFFF)Z");
     gPositionListener.callPositionLost = GetStaticMethodIDOrDie(
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 5a8eb3a..5b90547 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -1216,7 +1216,6 @@
                     break;
                 case AudioSystem.STREAM_BLUETOOTH_SCO:
                     mContentType = CONTENT_TYPE_SPEECH;
-                    mFlags |= FLAG_SCO;
                     break;
                 case AudioSystem.STREAM_DTMF:
                     mContentType = CONTENT_TYPE_SONIFICATION;
@@ -1763,8 +1762,7 @@
                     AudioSystem.STREAM_SYSTEM : AudioSystem.STREAM_SYSTEM_ENFORCED;
         }
         if ((aa.getAllFlags() & FLAG_SCO) == FLAG_SCO) {
-            return fromGetVolumeControlStream ?
-                    AudioSystem.STREAM_VOICE_CALL : AudioSystem.STREAM_BLUETOOTH_SCO;
+            return AudioSystem.STREAM_VOICE_CALL;
         }
         if ((aa.getAllFlags() & FLAG_BEACON) == FLAG_BEACON) {
             return fromGetVolumeControlStream ?
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 39b29d0..2da8eec 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -16,11 +16,15 @@
 
 package android.media;
 
+import static android.media.audio.Flags.FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE;
+
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
+import android.media.audio.Flags;
 import android.util.SparseIntArray;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -192,6 +196,15 @@
      */
     public static final int TYPE_DOCK_ANALOG = 31;
 
+    /**
+     * A device type describing a speaker group that supports multichannel contents. The speakers in
+     * the group are connected together using local network based protocols. The speaker group
+     * requires additional input of the physical positions of each individual speaker to provide a
+     * better experience on multichannel contents.
+     */
+    @FlaggedApi(FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE)
+    public static final int TYPE_MULTICHANNEL_GROUP = 32;
+
     /** @hide */
     @IntDef(flag = false, prefix = "TYPE", value = {
             TYPE_BUILTIN_EARPIECE,
@@ -224,7 +237,8 @@
             TYPE_BLE_SPEAKER,
             TYPE_ECHO_REFERENCE,
             TYPE_BLE_BROADCAST,
-            TYPE_DOCK_ANALOG}
+            TYPE_DOCK_ANALOG,
+            TYPE_MULTICHANNEL_GROUP}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceType {}
@@ -285,7 +299,8 @@
             TYPE_BLE_HEADSET,
             TYPE_BLE_SPEAKER,
             TYPE_BLE_BROADCAST,
-            TYPE_DOCK_ANALOG}
+            TYPE_DOCK_ANALOG,
+            TYPE_MULTICHANNEL_GROUP}
     )
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDeviceTypeOut {}
@@ -321,7 +336,13 @@
             case TYPE_BLE_BROADCAST:
             case TYPE_DOCK_ANALOG:
                 return true;
+
             default:
+                if (Flags.enableMultichannelGroupDevice()) {
+                    if (type == TYPE_MULTICHANNEL_GROUP) {
+                        return true;
+                    }
+                }
                 return false;
         }
     }
@@ -665,6 +686,10 @@
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_HEADSET, TYPE_BLE_HEADSET);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_SPEAKER, TYPE_BLE_SPEAKER);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BLE_BROADCAST, TYPE_BLE_BROADCAST);
+        if (Flags.enableMultichannelGroupDevice()) {
+            INT_TO_EXT_DEVICE_MAPPING.put(
+                    AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP, TYPE_MULTICHANNEL_GROUP);
+        }
 
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
         INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -721,6 +746,10 @@
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_HEADSET);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_SPEAKER, AudioSystem.DEVICE_OUT_BLE_SPEAKER);
         EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BLE_BROADCAST, AudioSystem.DEVICE_OUT_BLE_BROADCAST);
+        if (Flags.enableMultichannelGroupDevice()) {
+            EXT_TO_INT_DEVICE_MAPPING.put(
+                    TYPE_MULTICHANNEL_GROUP, AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP);
+        }
 
         // privileges mapping to input device
         EXT_TO_INT_INPUT_DEVICE_MAPPING = new SparseIntArray();
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 8250a53..9beeef4 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -21,6 +21,7 @@
 import static android.content.Context.DEVICE_ID_DEFAULT;
 import static android.media.audio.Flags.autoPublicVolumeApiHardening;
 import static android.media.audio.Flags.automaticBtDeviceType;
+import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO;
 import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
 import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
 import static android.media.audio.Flags.FLAG_SUPPORTED_DEVICE_TYPES_API;
@@ -406,8 +407,10 @@
     /** Used to identify the volume of audio streams for notifications */
     public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
     /** @hide Used to identify the volume of audio streams for phone calls when connected
-     *        to bluetooth */
+     *        to bluetooth
+     *  @deprecated use {@link #STREAM_VOICE_CALL} instead */
     @SystemApi
+    @FlaggedApi(FLAG_DEPRECATE_STREAM_BT_SCO)
     public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO;
     /** @hide Used to identify the volume of audio streams for enforced system sounds
      *        in certain countries (e.g camera in Japan) */
@@ -6156,6 +6159,11 @@
      */
     public static final int DEVICE_OUT_BLE_BROADCAST = AudioSystem.DEVICE_OUT_BLE_BROADCAST;
     /** @hide
+     * The audio output device code for a wireless speaker group supporting multichannel content.
+     */
+    public static final int DEVICE_OUT_MULTICHANNEL_GROUP =
+            AudioSystem.DEVICE_OUT_MULTICHANNEL_GROUP;
+    /** @hide
      * This is not used as a returned value from {@link #getDevicesForStream}, but could be
      *  used in the future in a set method to select whatever default device is chosen by the
      *  platform-specific implementation.
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index bf09cb0..d0d91ba 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -93,7 +93,8 @@
     /** @hide Used to identify the volume of audio streams for notifications */
     public static final int STREAM_NOTIFICATION = 5;
     /** @hide
-     *  Used to identify the volume of audio streams for phone calls when connected on bluetooth */
+     *  Used to identify the volume of audio streams for phone calls when connected on bluetooth
+     *  @deprecated use {@link #STREAM_VOICE_CALL} instead */
     public static final int STREAM_BLUETOOTH_SCO = 6;
     /** @hide Used to identify the volume of audio streams for enforced system sounds in certain
      * countries (e.g camera in Japan) */
@@ -110,7 +111,7 @@
     public static final int STREAM_ASSISTANT = 11;
     /**
      * @hide
-     * @deprecated Use {@link #numStreamTypes() instead}
+     * @deprecated Use {@link #numStreamTypes()} instead
      */
     public static final int NUM_STREAMS = 5;
 
@@ -1066,6 +1067,8 @@
     /** @hide */
     public static final int DEVICE_OUT_IP = 0x800000;
     /** @hide */
+    public static final int DEVICE_OUT_MULTICHANNEL_GROUP = 0x800001;
+    /** @hide */
     public static final int DEVICE_OUT_BUS = 0x1000000;
     /** @hide */
     public static final int DEVICE_OUT_PROXY = 0x2000000;
@@ -1134,6 +1137,7 @@
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_AUX_LINE);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_SPEAKER_SAFE);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_IP);
+        DEVICE_OUT_ALL_SET.add(DEVICE_OUT_MULTICHANNEL_GROUP);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_BUS);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_PROXY);
         DEVICE_OUT_ALL_SET.add(DEVICE_OUT_USB_HEADSET);
@@ -1422,6 +1426,8 @@
     /** @hide */ public static final String DEVICE_OUT_AUX_LINE_NAME = "aux_line";
     /** @hide */ public static final String DEVICE_OUT_SPEAKER_SAFE_NAME = "speaker_safe";
     /** @hide */ public static final String DEVICE_OUT_IP_NAME = "ip";
+    /** @hide */
+    public static final String DEVICE_OUT_MULTICHANNEL_GROUP_NAME = "multichannel_group";
     /** @hide */ public static final String DEVICE_OUT_BUS_NAME = "bus";
     /** @hide */ public static final String DEVICE_OUT_PROXY_NAME = "proxy";
     /** @hide */ public static final String DEVICE_OUT_USB_HEADSET_NAME = "usb_headset";
@@ -1515,6 +1521,8 @@
             return DEVICE_OUT_SPEAKER_SAFE_NAME;
         case DEVICE_OUT_IP:
             return DEVICE_OUT_IP_NAME;
+        case DEVICE_OUT_MULTICHANNEL_GROUP:
+            return DEVICE_OUT_MULTICHANNEL_GROUP_NAME;
         case DEVICE_OUT_BUS:
             return DEVICE_OUT_BUS_NAME;
         case DEVICE_OUT_PROXY:
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index 3a19f46..96edd63 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -23,6 +23,7 @@
 import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
 import static android.media.codec.Flags.FLAG_NULL_OUTPUT_SURFACE;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.codec.Flags.FLAG_APV_SUPPORT;
 import static android.media.MediaCodec.GetFlag;
 
 import android.annotation.FlaggedApi;
@@ -4496,6 +4497,265 @@
         @SuppressLint("AllUpper")
         public static final int AC4Level4       = 0x10;
 
+        // Profiles and levels/bands for APV Codec, corresponding to the definitions in
+        // "Advanced Professional Video", 10.1.3 Profiles, 10.1.4 Levels and Bands
+        // found at https://www.ietf.org/archive/id/draft-lim-apv-02.html
+
+        /**
+         * APV codec profile 422-10 as per IETF lim-apv-02, 10.1.3.1.1
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVProfile422_10 =  0x01;
+
+        /**
+         * APV codec profile 422-10 as per IETF lim-apv-02, 10.1.3.1.1
+         * with HDR10.
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVProfile422_10HDR10 =  0x1000;
+
+        /**
+         * APV codec profile 422-10 as per IETF lim-apv-02, 10.1.3.1.1
+         * with HDR10Plus.
+         */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVProfile422_10HDR10Plus =  0x2000;
+
+        // For APV Levels, the numerical values are constructed as follows:
+        //   ((0x100 << (level_num - 1)) | (1 << band))
+        // where:
+        //   - "level_num" is the APV Level numbered consecutively
+        //     (i.e., Level 1 == 1, Level 1.1 == 2, etc.)
+        //   - "band" is the APV Band
+
+        /** APV Codec Level 1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band0 =  0x101;
+        /** APV Codec Level 1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band1 =  0x102;
+        /** APV Codec Level 1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band2 =  0x104;
+        /** APV Codec Level 1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel1Band3 =  0x108;
+        /** APV Codec Level 1.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band0 = 0x201;
+        /** APV Codec Level 1.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band1 = 0x202;
+        /** APV Codec Level 1.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band2 = 0x204;
+        /** APV Codec Level 1.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel11Band3 = 0x208;
+        /** APV Codec Level 2, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band0 =  0x401;
+        /** APV Codec Level 2, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band1 =  0x402;
+        /** APV Codec Level 2, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band2 =  0x404;
+        /** APV Codec Level 2, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel2Band3 =  0x408;
+        /** APV Codec Level 2.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band0 = 0x801;
+        /** APV Codec Level 2.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band1 = 0x802;
+        /** APV Codec Level 2.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band2 = 0x804;
+        /** APV Codec Level 2.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel21Band3 = 0x808;
+        /** APV Codec Level 3, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band0 =  0x1001;
+        /** APV Codec Level 3, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band1 =  0x1002;
+        /** APV Codec Level 3, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band2 =  0x1004;
+        /** APV Codec Level 3, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel3Band3 =  0x1008;
+        /** APV Codec Level 3.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band0 = 0x2001;
+        /** APV Codec Level 3.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band1 = 0x2002;
+        /** APV Codec Level 3.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band2 = 0x2004;
+        /** APV Codec Level 3.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel31Band3 = 0x2008;
+        /** APV Codec Level 4, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band0 =  0x4001;
+        /** APV Codec Level 4, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band1 =  0x4002;
+        /** APV Codec Level 4, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band2 =  0x4004;
+        /** APV Codec Level 4, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel4Band3 =  0x4008;
+        /** APV Codec Level 4.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band0 = 0x8001;
+        /** APV Codec Level 4.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band1 = 0x8002;
+        /** APV Codec Level 4.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band2 = 0x8004;
+        /** APV Codec Level 4.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel41Band3 = 0x8008;
+        /** APV Codec Level 5, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band0 =  0x10001;
+        /** APV Codec Level 5, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band1 =  0x10002;
+        /** APV Codec Level 5, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band2 =  0x10004;
+        /** APV Codec Level 5, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel5Band3 =  0x10008;
+        /** APV Codec Level 5.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band0 = 0x20001;
+        /** APV Codec Level 5.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band1 = 0x20002;
+        /** APV Codec Level 5.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band2 = 0x20004;
+        /** APV Codec Level 5.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel51Band3 = 0x20008;
+        /** APV Codec Level 6, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band0 =  0x40001;
+        /** APV Codec Level 6, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band1 =  0x40002;
+        /** APV Codec Level 6, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band2 =  0x40004;
+        /** APV Codec Level 6, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel6Band3 =  0x40008;
+        /** APV Codec Level 6.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band0 = 0x80001;
+        /** APV Codec Level 6.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band1 = 0x80002;
+        /** APV Codec Level 6.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band2 = 0x80004;
+        /** APV Codec Level 6.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel61Band3 = 0x80008;
+        /** APV Codec Level 7, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band0 =  0x100001;
+        /** APV Codec Level 7, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band1 =  0x100002;
+        /** APV Codec Level 7, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band2 =  0x100004;
+        /** APV Codec Level 7, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel7Band3 =  0x100008;
+        /** APV Codec Level 7.1, Band 0 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band0 = 0x200001;
+        /** APV Codec Level 7.1, Band 1 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band1 = 0x200002;
+        /** APV Codec Level 7.1, Band 2 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band2 = 0x200004;
+        /** APV Codec Level 7.1, Band 3 as per IETF lim-apv-02, 10.1.4 */
+        @SuppressLint("AllUpper")
+        @FlaggedApi(FLAG_APV_SUPPORT)
+        public static final int APVLevel71Band3 = 0x200008;
+
         /**
          * The profile of the media content. Depending on the type of media this can be
          * one of the profile values defined in this class.
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index cd0654c..b08a86e 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -18,6 +18,7 @@
 
 import static android.media.codec.Flags.FLAG_IN_PROCESS_SW_AUDIO_CODEC;
 import static android.media.codec.Flags.FLAG_REGION_OF_INTEREST;
+import static android.media.codec.Flags.FLAG_APV_SUPPORT;
 
 import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
 import static com.android.media.codec.flags.Flags.FLAG_LARGE_AUDIO_FRAME;
@@ -157,6 +158,8 @@
 public final class MediaFormat {
     public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
     public static final String MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
+    @FlaggedApi(FLAG_APV_SUPPORT)
+    public static final String MIMETYPE_VIDEO_APV = "video/apv";
     public static final String MIMETYPE_VIDEO_AV1 = "video/av01";
     public static final String MIMETYPE_VIDEO_AVC = "video/avc";
     public static final String MIMETYPE_VIDEO_HEVC = "video/hevc";
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index bdfa6301..2d17bf5 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -387,13 +387,13 @@
         /**
          * Audio source for capturing broadcast radio tuner output.
          * Capturing the radio tuner output requires the
-         * {@link android.Manifest.permission#CAPTURE_AUDIO_OUTPUT} permission.
+         * {@link android.Manifest.permission#CAPTURE_TUNER_AUDIO_INPUT} permission.
          * This permission is reserved for use by system components and is not available to
          * third-party applications.
          * @hide
          */
         @SystemApi
-        @RequiresPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)
+        @RequiresPermission(android.Manifest.permission.CAPTURE_TUNER_AUDIO_INPUT)
         public static final int RADIO_TUNER = 1998;
 
         /**
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index e048d5c..816729d 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import static android.media.MediaRouter2Utils.toUniqueId;
+import static android.media.audio.Flags.FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE;
 
 import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
 import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
@@ -272,6 +273,19 @@
     public static final int TYPE_BLE_HEADSET = AudioDeviceInfo.TYPE_BLE_HEADSET;
 
     /**
+     * Indicates the route is a speaker group supporting multichannel contents.
+     *
+     * <p>The speakers in the group are connected together using local network based protocols. The
+     * speaker group requires additional input of the physical positions of each individual speaker
+     * to provide a better experience on multichannel contents.
+     *
+     * @see #getType
+     */
+    @FlaggedApi(FLAG_ENABLE_MULTICHANNEL_GROUP_DEVICE)
+    public static final int TYPE_MULTICHANNEL_SPEAKER_GROUP =
+            AudioDeviceInfo.TYPE_MULTICHANNEL_GROUP;
+
+    /**
      * Indicates the route is a remote TV.
      *
      * <p>A remote device uses a routing protocol managed by the application, as opposed to the
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.aidl b/media/java/android/media/quality/AmbientBacklightEvent.aidl
new file mode 100644
index 0000000..174cd46
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightEvent.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.media.quality;
+
+parcelable AmbientBacklightEvent;
diff --git a/media/java/android/media/quality/AmbientBacklightEvent.java b/media/java/android/media/quality/AmbientBacklightEvent.java
new file mode 100644
index 0000000..3bc6b86
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightEvent.java
@@ -0,0 +1,136 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class AmbientBacklightEvent implements Parcelable {
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({AMBIENT_BACKLIGHT_EVENT_ENABLED, AMBIENT_BACKLIGHT_EVENT_DISABLED,
+            AMBIENT_BACKLIGHT_EVENT_METADATA,
+            AMBIENT_BACKLIGHT_EVENT_INTERRUPTED})
+    public @interface AmbientBacklightEventTypes {}
+
+    /**
+     * Event type for ambient backlight events. The ambient backlight is enabled.
+     */
+    public static final int AMBIENT_BACKLIGHT_EVENT_ENABLED = 1;
+
+    /**
+     * Event type for ambient backlight events. The ambient backlight is disabled.
+     */
+    public static final int AMBIENT_BACKLIGHT_EVENT_DISABLED = 2;
+
+    /**
+     * Event type for ambient backlight events. The ambient backlight metadata is
+     * available.
+     */
+    public static final int AMBIENT_BACKLIGHT_EVENT_METADATA = 3;
+
+    /**
+     * Event type for ambient backlight events. The ambient backlight event is preempted by another
+     * application.
+     */
+    public static final int AMBIENT_BACKLIGHT_EVENT_INTERRUPTED = 4;
+
+    private final int mEventType;
+    @Nullable
+    private final AmbientBacklightMetadata mMetadata;
+
+    public AmbientBacklightEvent(int eventType,
+            @Nullable AmbientBacklightMetadata metadata) {
+        mEventType = eventType;
+        mMetadata = metadata;
+    }
+
+    private AmbientBacklightEvent(Parcel in) {
+        mEventType = in.readInt();
+        mMetadata = in.readParcelable(AmbientBacklightMetadata.class.getClassLoader());
+    }
+
+    public int getEventType() {
+        return mEventType;
+    }
+
+    @Nullable
+    public AmbientBacklightMetadata getMetadata() {
+        return mMetadata;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mEventType);
+        dest.writeParcelable(mMetadata, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Parcelable.Creator<AmbientBacklightEvent> CREATOR =
+            new Parcelable.Creator<AmbientBacklightEvent>() {
+                public AmbientBacklightEvent createFromParcel(Parcel in) {
+                    return new AmbientBacklightEvent(in);
+                }
+
+                public AmbientBacklightEvent[] newArray(int size) {
+                    return new AmbientBacklightEvent[size];
+                }
+            };
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (!(obj instanceof AmbientBacklightEvent)) {
+            return false;
+        }
+
+        AmbientBacklightEvent other = (AmbientBacklightEvent) obj;
+        return mEventType == other.mEventType
+                && Objects.equals(mMetadata, other.mMetadata);
+    }
+
+    @Override
+    public int hashCode() {
+        return mEventType * 31 + (mMetadata != null ? mMetadata.hashCode() : 0);
+    }
+
+    @Override
+    public String toString() {
+        return "AmbientBacklightEvent{"
+                + "mEventType=" + mEventType
+                + ", mMetadata=" + mMetadata
+                + '}';
+    }
+}
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.aidl b/media/java/android/media/quality/AmbientBacklightMetadata.aidl
new file mode 100644
index 0000000..b95a474f
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.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.media.quality;
+
+parcelable AmbientBacklightMetadata;
\ No newline at end of file
diff --git a/media/java/android/media/quality/AmbientBacklightMetadata.java b/media/java/android/media/quality/AmbientBacklightMetadata.java
new file mode 100644
index 0000000..fc77934
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightMetadata.java
@@ -0,0 +1,127 @@
+/*
+ * 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.media.quality;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Arrays;
+
+/**
+ * @hide
+ */
+public class AmbientBacklightMetadata implements Parcelable {
+    @NonNull
+    private final String mPackageName;
+    private final int mCompressAlgorithm;
+    private final int mSource;
+    private final int mColorFormat;
+    private final int mHorizontalZonesNumber;
+    private final int mVerticalZonesNumber;
+    @NonNull
+    private final int[] mZonesColors;
+
+    public AmbientBacklightMetadata(@NonNull String packageName, int compressAlgorithm,
+            int source, int colorFormat, int horizontalZonesNumber, int verticalZonesNumber,
+            @NonNull int[] zonesColors) {
+        mPackageName = packageName;
+        mCompressAlgorithm = compressAlgorithm;
+        mSource = source;
+        mColorFormat = colorFormat;
+        mHorizontalZonesNumber = horizontalZonesNumber;
+        mVerticalZonesNumber = verticalZonesNumber;
+        mZonesColors = zonesColors;
+    }
+
+    private AmbientBacklightMetadata(Parcel in) {
+        mPackageName = in.readString();
+        mCompressAlgorithm = in.readInt();
+        mSource = in.readInt();
+        mColorFormat = in.readInt();
+        mHorizontalZonesNumber = in.readInt();
+        mVerticalZonesNumber = in.readInt();
+        mZonesColors = in.createIntArray();
+    }
+
+    @NonNull
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public int getCompressAlgorithm() {
+        return mCompressAlgorithm;
+    }
+
+    public int getSource() {
+        return mSource;
+    }
+
+    public int getColorFormat() {
+        return mColorFormat;
+    }
+
+    public int getHorizontalZonesNumber() {
+        return mHorizontalZonesNumber;
+    }
+
+    public int getVerticalZonesNumber() {
+        return mVerticalZonesNumber;
+    }
+
+    @NonNull
+    public int[] getZonesColors() {
+        return mZonesColors;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mPackageName);
+        dest.writeInt(mCompressAlgorithm);
+        dest.writeInt(mSource);
+        dest.writeInt(mColorFormat);
+        dest.writeInt(mHorizontalZonesNumber);
+        dest.writeInt(mVerticalZonesNumber);
+        dest.writeIntArray(mZonesColors);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Parcelable.Creator<AmbientBacklightMetadata> CREATOR =
+            new Parcelable.Creator<AmbientBacklightMetadata>() {
+                public AmbientBacklightMetadata createFromParcel(Parcel in) {
+                    return new AmbientBacklightMetadata(in);
+                }
+
+                public AmbientBacklightMetadata[] newArray(int size) {
+                    return new AmbientBacklightMetadata[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "AmbientBacklightMetadata{packageName=" + mPackageName
+                + ", compressAlgorithm=" + mCompressAlgorithm + ", source=" + mSource
+                + ", colorFormat=" + mColorFormat + ", horizontalZonesNumber="
+                + mHorizontalZonesNumber + ", verticalZonesNumber=" + mVerticalZonesNumber
+                + ", zonesColors=" + Arrays.toString(mZonesColors) + "}";
+    }
+}
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.aidl b/media/java/android/media/quality/AmbientBacklightSettings.aidl
new file mode 100644
index 0000000..e2cdd03
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightSettings.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.media.quality;
+
+parcelable AmbientBacklightSettings;
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
new file mode 100644
index 0000000..391eb22
--- /dev/null
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -0,0 +1,203 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * @hide
+ */
+public class AmbientBacklightSettings implements Parcelable {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SOURCE_NONE, SOURCE_AUDIO, SOURCE_VIDEO, SOURCE_AUDIO_VIDEO})
+    public @interface Source {}
+
+    /**
+     * The detection is disabled.
+     */
+    public static final int SOURCE_NONE = 0;
+
+    /**
+     * The detection is enabled for audio.
+     */
+    public static final int SOURCE_AUDIO = 1;
+
+    /**
+     * The detection is enabled for video.
+     */
+    public static final int SOURCE_VIDEO = 2;
+
+    /**
+     * The detection is enabled for audio and video.
+     */
+    public static final int SOURCE_AUDIO_VIDEO = 3;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({COLOR_FORMAT_RGB888})
+    public @interface ColorFormat {}
+
+    /**
+     * The color format is RGB888.
+     */
+    public static final int COLOR_FORMAT_RGB888 = 1;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({ALGORITHM_NONE, ALGORITHM_RLE})
+    public @interface CompressAlgorithm {}
+
+    /**
+     * The compress algorithm is disabled.
+     */
+    public static final int ALGORITHM_NONE = 0;
+
+    /**
+     * The compress algorithm is RLE.
+     */
+    public static final int ALGORITHM_RLE = 1;
+
+    /**
+     * The source of the ambient backlight.
+     */
+    private final int mSource;
+
+    /**
+     * The maximum framerate for the ambient backlight.
+     */
+    private final int mMaxFps;
+
+    /**
+     * The color format for the ambient backlight.
+     */
+    private final int mColorFormat;
+
+    /**
+     * The number of zones in horizontal direction.
+     */
+    private final int mHorizontalZonesNumber;
+
+    /**
+     * The number of zones in vertical direction.
+     */
+    private final int mVerticalZonesNumber;
+
+    /**
+     * The flag to indicate whether the letterbox is omitted.
+     */
+    private final boolean mIsLetterboxOmitted;
+
+    /**
+     * The color threshold for the ambient backlight.
+     */
+    private final int mThreshold;
+
+    public AmbientBacklightSettings(int source, int maxFps, int colorFormat,
+            int horizontalZonesNumber, int verticalZonesNumber, boolean isLetterboxOmitted,
+            int threshold) {
+        mSource = source;
+        mMaxFps = maxFps;
+        mColorFormat = colorFormat;
+        mHorizontalZonesNumber = horizontalZonesNumber;
+        mVerticalZonesNumber = verticalZonesNumber;
+        mIsLetterboxOmitted = isLetterboxOmitted;
+        mThreshold = threshold;
+    }
+
+    private AmbientBacklightSettings(Parcel in) {
+        mSource = in.readInt();
+        mMaxFps = in.readInt();
+        mColorFormat = in.readInt();
+        mHorizontalZonesNumber = in.readInt();
+        mVerticalZonesNumber = in.readInt();
+        mIsLetterboxOmitted = in.readBoolean();
+        mThreshold = in.readInt();
+    }
+
+    @Source
+    public int getSource() {
+        return mSource;
+    }
+
+    public int getMaxFps() {
+        return mMaxFps;
+    }
+
+    @ColorFormat
+    public int getColorFormat() {
+        return mColorFormat;
+    }
+
+    public int getHorizontalZonesNumber() {
+        return mHorizontalZonesNumber;
+    }
+
+    public int getVerticalZonesNumber() {
+        return mVerticalZonesNumber;
+    }
+
+    public boolean isLetterboxOmitted() {
+        return mIsLetterboxOmitted;
+    }
+
+    public int getThreshold() {
+        return mThreshold;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mSource);
+        dest.writeInt(mMaxFps);
+        dest.writeInt(mColorFormat);
+        dest.writeInt(mHorizontalZonesNumber);
+        dest.writeInt(mVerticalZonesNumber);
+        dest.writeBoolean(mIsLetterboxOmitted);
+        dest.writeInt(mThreshold);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Parcelable.Creator<AmbientBacklightSettings> CREATOR =
+            new Parcelable.Creator<AmbientBacklightSettings>() {
+                public AmbientBacklightSettings createFromParcel(Parcel in) {
+                    return new AmbientBacklightSettings(in);
+                }
+
+                public AmbientBacklightSettings[] newArray(int size) {
+                    return new AmbientBacklightSettings[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return "AmbientBacklightSettings{Source=" + mSource + ", MaxFps=" + mMaxFps
+                + ", ColorFormat=" + mColorFormat + ", HorizontalZonesNumber="
+                + mHorizontalZonesNumber + ", VerticalZonesNumber=" + mVerticalZonesNumber
+                + ", IsLetterboxOmitted=" + mIsLetterboxOmitted + ", Threshold=" + mThreshold + "}";
+    }
+}
diff --git a/media/java/android/media/quality/IAmbientBacklightCallback.aidl b/media/java/android/media/quality/IAmbientBacklightCallback.aidl
new file mode 100644
index 0000000..159f5b7
--- /dev/null
+++ b/media/java/android/media/quality/IAmbientBacklightCallback.aidl
@@ -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 android.media.quality;
+
+import android.media.quality.AmbientBacklightEvent;
+
+/** @hide */
+oneway interface IAmbientBacklightCallback {
+    void onAmbientBacklightEvent(in AmbientBacklightEvent event);
+}
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index 4f1210a7..e6c79dd 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -16,9 +16,49 @@
 
 package android.media.quality;
 
+import android.media.quality.AmbientBacklightSettings;
+import android.media.quality.IAmbientBacklightCallback;
+import android.media.quality.IPictureProfileCallback;
+import android.media.quality.ISoundProfileCallback;
+import android.media.quality.ParamCapability;
+import android.media.quality.PictureProfile;
+import android.media.quality.SoundProfile;
+
 /**
  * Interface for Media Quality Manager
  * @hide
  */
 interface IMediaQualityManager {
-}
\ No newline at end of file
+    PictureProfile createPictureProfile(in PictureProfile pp);
+    void updatePictureProfile(in long id, in PictureProfile pp);
+    void removePictureProfile(in long id);
+    PictureProfile getPictureProfileById(in long id);
+    List<PictureProfile> getPictureProfilesByPackage(in String packageName);
+    List<PictureProfile> getAvailablePictureProfiles();
+    List<PictureProfile> getAllPictureProfiles();
+
+    SoundProfile createSoundProfile(in SoundProfile pp);
+    void updateSoundProfile(in long id, in SoundProfile pp);
+    void removeSoundProfile(in long id);
+    SoundProfile getSoundProfileById(in long id);
+    List<SoundProfile> getSoundProfilesByPackage(in String packageName);
+    List<SoundProfile> getAvailableSoundProfiles();
+    List<SoundProfile> getAllSoundProfiles();
+
+    void registerPictureProfileCallback(in IPictureProfileCallback cb);
+    void registerSoundProfileCallback(in ISoundProfileCallback cb);
+    void registerAmbientBacklightCallback(in IAmbientBacklightCallback cb);
+
+    List<ParamCapability> getParamCapabilities(in List<String> names);
+
+    boolean isSupported();
+    void setAutoPictureQualityEnabled(in boolean enabled);
+    boolean isAutoPictureQualityEnabled();
+    void setSuperResolutionEnabled(in boolean enabled);
+    boolean isSuperResolutionEnabled();
+    void setAutoSoundQualityEnabled(in boolean enabled);
+    boolean isAutoSoundQualityEnabled();
+
+    void setAmbientBacklightSettings(in AmbientBacklightSettings settings);
+    void setAmbientBacklightEnabled(in boolean enabled);
+}
diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/IPictureProfileCallback.aidl
new file mode 100644
index 0000000..05441cd
--- /dev/null
+++ b/media/java/android/media/quality/IPictureProfileCallback.aidl
@@ -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 android.media.quality;
+
+import android.media.quality.PictureProfile;
+
+/**
+ * Interface to receive callbacks from IMediaQuality.
+ * @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);
+}
diff --git a/media/java/android/media/quality/ISoundProfileCallback.aidl b/media/java/android/media/quality/ISoundProfileCallback.aidl
new file mode 100644
index 0000000..72d1524
--- /dev/null
+++ b/media/java/android/media/quality/ISoundProfileCallback.aidl
@@ -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 android.media.quality;
+
+import android.media.quality.SoundProfile;
+
+/**
+ * Interface to receive callbacks from IMediaQuality.
+ * @hide
+ */
+oneway interface ISoundProfileCallback {
+    void onSoundProfileAdded(in long id, in SoundProfile p);
+    void onSoundProfileUpdated(in long id, in SoundProfile p);
+    void onSoundProfileRemoved(in long id, in SoundProfile p);
+}
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
new file mode 100644
index 0000000..5cbc81d
--- /dev/null
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -0,0 +1,51 @@
+/*
+ * 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.media.quality;
+
+
+import android.annotation.FlaggedApi;
+import android.media.tv.flags.Flags;
+
+/**
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public class MediaQualityContract {
+
+    public interface BaseParameters {
+        String PARAMETER_ID = "_id";
+        String PARAMETER_NAME = "_name";
+        String PARAMETER_PACKAGE = "_package";
+        String PARAMETER_INPUT_ID = "_input_id";
+
+    }
+    public static final class PictureQuality implements BaseParameters {
+        public static final String PARAMETER_BRIGHTNESS = "brightness";
+        public static final String PARAMETER_CONTRAST = "contrast";
+        public static final String PARAMETER_SHARPNESS = "sharpness";
+        public static final String PARAMETER_SATURATION = "saturation";
+    }
+
+    public static final class SoundQuality implements BaseParameters {
+        public static final String PARAMETER_BALANCE = "balance";
+        public static final String PARAMETER_BASS = "bass";
+        public static final String PARAMETER_TREBLE = "treble";
+    }
+
+    private MediaQualityContract() {
+    }
+}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 86dbcdb..1237d67 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -16,10 +16,22 @@
 
 package android.media.quality;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.media.tv.flags.Flags;
+import android.os.RemoteException;
+
+import androidx.annotation.RequiresPermission;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Expose TV setting APIs for the application to use
@@ -27,12 +39,20 @@
  */
 @FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
 @SystemService(Context.MEDIA_QUALITY_SERVICE)
-public class MediaQualityManager {
+public final class MediaQualityManager {
     // TODO: unhide the APIs for api review
     private static final String TAG = "MediaQualityManager";
 
     private final IMediaQualityManager mService;
     private final Context mContext;
+    private final Object mLock = new Object();
+    // @GuardedBy("mLock")
+    private final List<PictureProfileCallbackRecord> mPpCallbackRecords = new ArrayList<>();
+    // @GuardedBy("mLock")
+    private final List<SoundProfileCallbackRecord> mSpCallbackRecords = new ArrayList<>();
+    // @GuardedBy("mLock")
+    private final List<AmbientBacklightCallbackRecord> mAbCallbackRecords = new ArrayList<>();
+
 
     /**
      * @hide
@@ -40,5 +60,648 @@
     public MediaQualityManager(Context context, IMediaQualityManager service) {
         mContext = context;
         mService = service;
+        IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
+            @Override
+            public void onPictureProfileAdded(long profileId, PictureProfile profile) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postPictureProfileAdded(profileId, profile);
+                    }
+                }
+            }
+            @Override
+            public void onPictureProfileUpdated(long profileId, PictureProfile profile) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postPictureProfileUpdated(profileId, profile);
+                    }
+                }
+            }
+            @Override
+            public void onPictureProfileRemoved(long profileId, PictureProfile profile) {
+                synchronized (mLock) {
+                    for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postPictureProfileRemoved(profileId, profile);
+                    }
+                }
+            }
+        };
+        ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
+            @Override
+            public void onSoundProfileAdded(long profileId, SoundProfile profile) {
+                synchronized (mLock) {
+                    for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postSoundProfileAdded(profileId, profile);
+                    }
+                }
+            }
+            @Override
+            public void onSoundProfileUpdated(long profileId, SoundProfile profile) {
+                synchronized (mLock) {
+                    for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postSoundProfileUpdated(profileId, profile);
+                    }
+                }
+            }
+            @Override
+            public void onSoundProfileRemoved(long profileId, SoundProfile profile) {
+                synchronized (mLock) {
+                    for (SoundProfileCallbackRecord record : mSpCallbackRecords) {
+                        // TODO: filter callback record
+                        record.postSoundProfileRemoved(profileId, profile);
+                    }
+                }
+            }
+        };
+        IAmbientBacklightCallback abCallback = new IAmbientBacklightCallback.Stub() {
+            @Override
+            public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+                synchronized (mLock) {
+                    for (AmbientBacklightCallbackRecord record : mAbCallbackRecords) {
+                        record.postAmbientBacklightEvent(event);
+                    }
+                }
+            }
+        };
+
+        try {
+            if (mService != null) {
+                mService.registerPictureProfileCallback(ppCallback);
+                mService.registerSoundProfileCallback(spCallback);
+                mService.registerAmbientBacklightCallback(abCallback);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a {@link PictureProfileCallback}.
+     * @hide
+     */
+    public void registerPictureProfileCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull PictureProfileCallback callback) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(executor);
+        synchronized (mLock) {
+            mPpCallbackRecords.add(new PictureProfileCallbackRecord(callback, executor));
+        }
+    }
+
+    /**
+     * Unregisters the existing {@link PictureProfileCallback}.
+     * @hide
+     */
+    public void unregisterPictureProfileCallback(@NonNull final PictureProfileCallback callback) {
+        Preconditions.checkNotNull(callback);
+        synchronized (mLock) {
+            for (Iterator<PictureProfileCallbackRecord> it = mPpCallbackRecords.iterator();
+                    it.hasNext(); ) {
+                PictureProfileCallbackRecord record = it.next();
+                if (record.getCallback() == callback) {
+                    it.remove();
+                    break;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 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.
+     */
+    public PictureProfile getPictureProfileById(long profileId) {
+        try {
+            return mService.getPictureProfileById(profileId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /** @SystemApi gets profiles that available to the given package */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
+        try {
+            return mService.getPictureProfilesByPackage(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Gets profiles that available to the caller package */
+    public List<PictureProfile> getAvailablePictureProfiles() {
+        try {
+            return mService.getAvailablePictureProfiles();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @SystemApi all stored picture profiles of all packages */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    public List<PictureProfile> getAllPictureProfiles() {
+        try {
+            return mService.getAllPictureProfiles();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Creates a picture profile and store it in the system.
+     *
+     * @return the stored profile with an assigned profile ID.
+     */
+    public PictureProfile createPictureProfile(PictureProfile pp) {
+        try {
+            return mService.createPictureProfile(pp);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Updates an existing picture profile and store it in the system.
+     */
+    public void updatePictureProfile(long profileId, PictureProfile pp) {
+        try {
+            mService.updatePictureProfile(profileId, pp);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Removes a picture profile from the system.
+     */
+    public void removePictureProfile(long profileId) {
+        try {
+            mService.removePictureProfile(profileId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a {@link SoundProfileCallback}.
+     * @hide
+     */
+    public void registerSoundProfileCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull SoundProfileCallback callback) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(executor);
+        synchronized (mLock) {
+            mSpCallbackRecords.add(new SoundProfileCallbackRecord(callback, executor));
+        }
+    }
+
+    /**
+     * Unregisters the existing {@link SoundProfileCallback}.
+     * @hide
+     */
+    public void unregisterSoundProfileCallback(@NonNull final SoundProfileCallback callback) {
+        Preconditions.checkNotNull(callback);
+        synchronized (mLock) {
+            for (Iterator<SoundProfileCallbackRecord> it = mSpCallbackRecords.iterator();
+                    it.hasNext(); ) {
+                SoundProfileCallbackRecord record = it.next();
+                if (record.getCallback() == callback) {
+                    it.remove();
+                    break;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Gets sound profile by given profile ID.
+     * @return the corresponding sound profile if available; {@code null} if the ID doesn't
+     *         exist or the profile is not accessible to the caller.
+     */
+    public SoundProfile getSoundProfileById(long profileId) {
+        try {
+            return mService.getSoundProfileById(profileId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /** @SystemApi gets profiles that available to the given package */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+    public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
+        try {
+            return mService.getSoundProfilesByPackage(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Gets profiles that available to the caller package */
+    public List<SoundProfile> getAvailableSoundProfiles() {
+        try {
+            return mService.getAvailableSoundProfiles();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @SystemApi all stored sound profiles of all packages */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+    public List<SoundProfile> getAllSoundProfiles() {
+        try {
+            return mService.getAllSoundProfiles();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Creates a sound profile and store it in the system.
+     *
+     * @return the stored profile with an assigned profile ID.
+     */
+    public SoundProfile createSoundProfile(SoundProfile sp) {
+        try {
+            return mService.createSoundProfile(sp);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Updates an existing sound profile and store it in the system.
+     */
+    public void updateSoundProfile(long profileId, SoundProfile sp) {
+        try {
+            mService.updateSoundProfile(profileId, sp);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    /**
+     * Removes a sound profile from the system.
+     */
+    public void removeSoundProfile(long profileId) {
+        try {
+            mService.removeSoundProfile(profileId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets capability information of the given parameters.
+     */
+    public List<ParamCapability> getParamCapabilities(List<String> names) {
+        try {
+            return mService.getParamCapabilities(names);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
+     */
+    public boolean isSupported() {
+        try {
+            return mService.isSupported();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables auto picture quality.
+     * <p>If enabled, picture quality parameters can be adjusted dynamically by hardware based on
+     * different use cases.
+     *
+     * @param enabled {@code true} to enable, {@code false} to disable.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    public void setAutoPictureQualityEnabled(boolean enabled) {
+        try {
+            mService.setAutoPictureQualityEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if auto picture quality is enabled; {@code false} otherwise.
+     */
+    public boolean isAutoPictureQualityEnabled() {
+        try {
+            return mService.isAutoPictureQualityEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables super resolution.
+     * <p>Super resolution is a feature to improve resolution.
+     *
+     * @param enabled {@code true} to enable, {@code false} to disable.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+    public void setSuperResolutionEnabled(boolean enabled) {
+        try {
+            mService.setSuperResolutionEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if super resolution is enabled; {@code false} otherwise.
+     */
+    public boolean isSuperResolutionEnabled() {
+        try {
+            return mService.isSuperResolutionEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables auto sound quality.
+     * <p>If enabled, sound quality parameters can be adjusted dynamically by hardware based on
+     * different use cases.
+     *
+     * @param enabled {@code true} to enable, {@code false} to disable.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
+    public void setAutoSoundQualityEnabled(boolean enabled) {
+        try {
+            mService.setAutoSoundQualityEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns {@code true} if auto sound quality is enabled; {@code false} otherwise.
+     */
+    public boolean isAutoSoundQualityEnabled() {
+        try {
+            return mService.isAutoSoundQualityEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers a {@link AmbientBacklightCallback}.
+     * @hide
+     */
+    public void registerAmbientBacklightCallback(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull AmbientBacklightCallback callback) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(executor);
+        synchronized (mLock) {
+            mAbCallbackRecords.add(new AmbientBacklightCallbackRecord(callback, executor));
+        }
+    }
+
+    /**
+     * Unregisters the existing {@link AmbientBacklightCallback}.
+     * @hide
+     */
+    public void unregisterAmbientBacklightCallback(
+            @NonNull final AmbientBacklightCallback callback) {
+        Preconditions.checkNotNull(callback);
+        synchronized (mLock) {
+            for (Iterator<AmbientBacklightCallbackRecord> it = mAbCallbackRecords.iterator();
+                    it.hasNext(); ) {
+                AmbientBacklightCallbackRecord record = it.next();
+                if (record.getCallback() == callback) {
+                    it.remove();
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the ambient backlight settings.
+     *
+     * @param settings The settings to use for the backlight detector.
+     */
+    public void setAmbientBacklightSettings(
+            @NonNull AmbientBacklightSettings settings) {
+        Preconditions.checkNotNull(settings);
+        try {
+            mService.setAmbientBacklightSettings(settings);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables the ambient backlight detection.
+     *
+     * @param enabled {@code true} to enable, {@code false} to disable.
+     */
+    public void setAmbientBacklightEnabled(boolean enabled) {
+        try {
+            mService.setAmbientBacklightEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+
+    private static final class PictureProfileCallbackRecord {
+        private final PictureProfileCallback mCallback;
+        private final Executor mExecutor;
+
+        PictureProfileCallbackRecord(PictureProfileCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        public PictureProfileCallback getCallback() {
+            return mCallback;
+        }
+
+        public void postPictureProfileAdded(final long id, PictureProfile profile) {
+
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onPictureProfileAdded(id, profile);
+                }
+            });
+        }
+
+        public void postPictureProfileUpdated(final long id, PictureProfile profile) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onPictureProfileUpdated(id, profile);
+                }
+            });
+        }
+
+        public void postPictureProfileRemoved(final long id, PictureProfile profile) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onPictureProfileRemoved(id, profile);
+                }
+            });
+        }
+    }
+
+    private static final class SoundProfileCallbackRecord {
+        private final SoundProfileCallback mCallback;
+        private final Executor mExecutor;
+
+        SoundProfileCallbackRecord(SoundProfileCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        public SoundProfileCallback getCallback() {
+            return mCallback;
+        }
+
+        public void postSoundProfileAdded(final long id, SoundProfile profile) {
+
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onSoundProfileAdded(id, profile);
+                }
+            });
+        }
+
+        public void postSoundProfileUpdated(final long id, SoundProfile profile) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onSoundProfileUpdated(id, profile);
+                }
+            });
+        }
+
+        public void postSoundProfileRemoved(final long id, SoundProfile profile) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onSoundProfileRemoved(id, profile);
+                }
+            });
+        }
+    }
+
+    private static final class AmbientBacklightCallbackRecord {
+        private final AmbientBacklightCallback mCallback;
+        private final Executor mExecutor;
+
+        AmbientBacklightCallbackRecord(AmbientBacklightCallback callback, Executor executor) {
+            mCallback = callback;
+            mExecutor = executor;
+        }
+
+        public AmbientBacklightCallback getCallback() {
+            return mCallback;
+        }
+
+        public void postAmbientBacklightEvent(AmbientBacklightEvent event) {
+            mExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onAmbientBacklightEvent(event);
+                }
+            });
+        }
+    }
+
+    /**
+     * Callback used to monitor status of picture profiles.
+     * @hide
+     */
+    public abstract static class PictureProfileCallback {
+        /**
+         * @hide
+         */
+        public void onPictureProfileAdded(long id, PictureProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onPictureProfileUpdated(long id, PictureProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onPictureProfileRemoved(long id, PictureProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onError(int errorCode) {
+        }
+    }
+
+    /**
+     * Callback used to monitor status of sound profiles.
+     * @hide
+     */
+    public abstract static class SoundProfileCallback {
+        /**
+         * @hide
+         */
+        public void onSoundProfileAdded(long id, SoundProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onSoundProfileUpdated(long id, SoundProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onSoundProfileRemoved(long id, SoundProfile profile) {
+        }
+        /**
+         * @hide
+         */
+        public void onError(int errorCode) {
+        }
+    }
+
+    /**
+     * Callback used to monitor status of ambient backlight.
+     * @hide
+     */
+    public abstract static class AmbientBacklightCallback {
+        /**
+         * Called when new ambient backlight event is emitted.
+         */
+        public void onAmbientBacklightEvent(AmbientBacklightEvent event) {
+        }
     }
 }
diff --git a/media/java/android/media/quality/ParamCapability.aidl b/media/java/android/media/quality/ParamCapability.aidl
new file mode 100644
index 0000000..b43409d
--- /dev/null
+++ b/media/java/android/media/quality/ParamCapability.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.media.quality;
+
+parcelable ParamCapability;
diff --git a/media/java/android/media/quality/ParamCapability.java b/media/java/android/media/quality/ParamCapability.java
new file mode 100644
index 0000000..70e8592
--- /dev/null
+++ b/media/java/android/media/quality/ParamCapability.java
@@ -0,0 +1,187 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.StringDef;
+import android.media.tv.flags.Flags;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Capability info of media quality parameters
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public class ParamCapability implements Parcelable {
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "TYPE_" }, value = {
+            TYPE_INT,
+            TYPE_LONG,
+            TYPE_DOUBLE,
+            TYPE_STRING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ParamType {}
+
+    /**
+     * Integer parameter type
+     */
+    public static final int TYPE_INT = 1;
+
+    /**
+     * Long integer parameter type
+     */
+    public static final int TYPE_LONG = 2;
+
+    /**
+     * Double parameter type
+     */
+    public static final int TYPE_DOUBLE = 3;
+
+    /**
+     * String parameter type
+     */
+    public static final int TYPE_STRING = 4;
+
+    /** @hide */
+    @StringDef(prefix = { "CAPABILITY_" }, value = {
+            CAPABILITY_MAX,
+            CAPABILITY_MIN,
+            CAPABILITY_DEFAULT,
+            CAPABILITY_ENUM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Capability {}
+
+    /**
+     * The key for the max possible value of this parameter.
+     */
+    public static final String CAPABILITY_MAX = "max";
+
+    /**
+     * The key for the min possible value of this parameter.
+     */
+    public static final String CAPABILITY_MIN = "min";
+
+    /**
+     * The key for the default value of this parameter.
+     */
+    public static final String CAPABILITY_DEFAULT = "default";
+
+    /**
+     * The key for the enumeration of this parameter.
+     */
+    public static final String CAPABILITY_ENUM = "enum";
+
+    @NonNull
+    private final String mName;
+    private final boolean mIsSupported;
+    @ParamType
+    private final int mType;
+    @NonNull
+    private final Bundle mCaps;
+
+    protected ParamCapability(Parcel in) {
+        mName = in.readString();
+        mIsSupported = in.readBoolean();
+        mType = in.readInt();
+        mCaps = in.readBundle();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mName);
+        dest.writeBoolean(mIsSupported);
+        dest.writeInt(mType);
+        dest.writeBundle(mCaps);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<ParamCapability> CREATOR = new Creator<ParamCapability>() {
+        @Override
+        public ParamCapability createFromParcel(Parcel in) {
+            return new ParamCapability(in);
+        }
+
+        @Override
+        public ParamCapability[] newArray(int size) {
+            return new ParamCapability[size];
+        }
+    };
+
+
+    /**
+     * Creates a new ParamCapability.
+     *
+     * @hide
+     */
+    public ParamCapability(
+            @NonNull String name,
+            boolean isSupported,
+            int type,
+            @NonNull Bundle caps) {
+        this.mName = name;
+        this.mIsSupported = isSupported;
+        this.mType = type;
+        this.mCaps = caps;
+    }
+
+    /**
+     * Gets parameter name.
+     */
+    @NonNull
+    public String getParamName() {
+        return mName;
+    }
+
+    /**
+     * Returns whether this parameter is supported or not.
+     */
+    public boolean isSupported() {
+        return mIsSupported;
+    }
+
+    /**
+     * Gets parameter type.
+     */
+    @ParamType
+    public int getParamType() {
+        return mType;
+    }
+
+    /**
+     * Gets capability information.
+     * <p>e.g. use the key {@link #CAPABILITY_MAX} to get the max value.
+     */
+    @NonNull
+    public Bundle getCapabilities() {
+        return new Bundle(mCaps);
+    }
+}
diff --git a/media/java/android/media/quality/PictureProfile.aidl b/media/java/android/media/quality/PictureProfile.aidl
new file mode 100644
index 0000000..41d018b
--- /dev/null
+++ b/media/java/android/media/quality/PictureProfile.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.media.quality;
+
+parcelable PictureProfile;
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
new file mode 100644
index 0000000..36c49d8
--- /dev/null
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -0,0 +1,223 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.FlaggedApi;
+import android.media.tv.flags.Flags;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * @hide
+ */
+
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public class PictureProfile implements Parcelable {
+    @Nullable
+    private Long mId;
+    @NonNull
+    private final String mName;
+    @Nullable
+    private final String mInputId;
+    @Nullable
+    private final String mPackageName;
+    @NonNull
+    private final Bundle mParams;
+
+    protected PictureProfile(Parcel in) {
+        if (in.readByte() == 0) {
+            mId = null;
+        } else {
+            mId = in.readLong();
+        }
+        mName = in.readString();
+        mInputId = in.readString();
+        mPackageName = in.readString();
+        mParams = in.readBundle();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (mId == null) {
+            dest.writeByte((byte) 0);
+        } else {
+            dest.writeByte((byte) 1);
+            dest.writeLong(mId);
+        }
+        dest.writeString(mName);
+        dest.writeString(mInputId);
+        dest.writeString(mPackageName);
+        dest.writeBundle(mParams);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<PictureProfile> CREATOR = new Creator<PictureProfile>() {
+        @Override
+        public PictureProfile createFromParcel(Parcel in) {
+            return new PictureProfile(in);
+        }
+
+        @Override
+        public PictureProfile[] newArray(int size) {
+            return new PictureProfile[size];
+        }
+    };
+
+
+    /**
+     * Creates a new PictureProfile.
+     *
+     * @hide
+     */
+    public PictureProfile(
+            @Nullable Long id,
+            @NonNull String name,
+            @Nullable String inputId,
+            @Nullable String packageName,
+            @NonNull Bundle params) {
+        this.mId = id;
+        this.mName = name;
+        com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
+        this.mInputId = inputId;
+        this.mPackageName = packageName;
+        this.mParams = params;
+    }
+
+    @Nullable
+    public Long getProfileId() {
+        return mId;
+    }
+
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    @Nullable
+    public String getInputId() {
+        return mInputId;
+    }
+
+    @Nullable
+    public String getPackageName() {
+        return mPackageName;
+    }
+    @NonNull
+    public Bundle getParameters() {
+        return new Bundle(mParams);
+    }
+
+    /**
+     * A builder for {@link PictureProfile}
+     */
+    public static class Builder {
+        @Nullable
+        private Long mId;
+        @NonNull
+        private String mName;
+        @Nullable
+        private String mInputId;
+        @Nullable
+        private String mPackageName;
+        @NonNull
+        private Bundle mParams;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @hide
+         */
+        public Builder(@NonNull String name) {
+            mName = name;
+            com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
+        }
+
+        /**
+         * Copy constructor.
+         *
+         * @hide
+         */
+        public Builder(@NonNull PictureProfile p) {
+            mId = null; // ID needs to be reset
+            mName = p.getName();
+            mPackageName = p.getPackageName();
+            mInputId = p.getInputId();
+        }
+
+        /* @hide using by MediaQualityService */
+
+        /**
+         * Sets profile ID.
+         * @hide using by MediaQualityService
+         */
+        @NonNull
+        public Builder setProfileId(@Nullable Long id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Sets input ID.
+         */
+        @NonNull
+        public Builder setInputId(@NonNull String value) {
+            mInputId = value;
+            return this;
+        }
+
+        /**
+         * Sets package name of the profile.
+         */
+        @NonNull
+        public Builder setPackageName(@NonNull String value) {
+            mPackageName = value;
+            return this;
+        }
+
+        /**
+         * Sets profile parameters.
+         */
+        @NonNull
+        public Builder setParameters(@NonNull Bundle params) {
+            mParams = new Bundle(params);
+            return this;
+        }
+
+        /**
+         * Builds the instance.
+         */
+        @NonNull
+        public PictureProfile build() {
+
+            PictureProfile o = new PictureProfile(
+                    mId,
+                    mName,
+                    mInputId,
+                    mPackageName,
+                    mParams);
+            return o;
+        }
+    }
+}
diff --git a/media/java/android/media/quality/SoundProfile.aidl b/media/java/android/media/quality/SoundProfile.aidl
new file mode 100644
index 0000000..e79fcaa
--- /dev/null
+++ b/media/java/android/media/quality/SoundProfile.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.media.quality;
+
+parcelable SoundProfile;
diff --git a/media/java/android/media/quality/SoundProfile.java b/media/java/android/media/quality/SoundProfile.java
new file mode 100644
index 0000000..20d117b
--- /dev/null
+++ b/media/java/android/media/quality/SoundProfile.java
@@ -0,0 +1,219 @@
+/*
+ * 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.media.quality;
+
+import android.annotation.FlaggedApi;
+import android.media.tv.flags.Flags;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
+public class SoundProfile implements Parcelable {
+    @Nullable
+    private Long mId;
+    @NonNull
+    private final String mName;
+    @Nullable
+    private final String mInputId;
+    @Nullable
+    private final String mPackageName;
+    @NonNull
+    private final Bundle mParams;
+
+    protected SoundProfile(Parcel in) {
+        if (in.readByte() == 0) {
+            mId = null;
+        } else {
+            mId = in.readLong();
+        }
+        mName = in.readString();
+        mInputId = in.readString();
+        mPackageName = in.readString();
+        mParams = in.readBundle();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (mId == null) {
+            dest.writeByte((byte) 0);
+        } else {
+            dest.writeByte((byte) 1);
+            dest.writeLong(mId);
+        }
+        dest.writeString(mName);
+        dest.writeString(mInputId);
+        dest.writeString(mPackageName);
+        dest.writeBundle(mParams);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Creator<SoundProfile> CREATOR = new Creator<SoundProfile>() {
+        @Override
+        public SoundProfile createFromParcel(Parcel in) {
+            return new SoundProfile(in);
+        }
+
+        @Override
+        public SoundProfile[] newArray(int size) {
+            return new SoundProfile[size];
+        }
+    };
+
+
+    /**
+     * Creates a new SoundProfile.
+     *
+     * @hide
+     */
+    public SoundProfile(
+            @Nullable Long id,
+            @NonNull String name,
+            @Nullable String inputId,
+            @Nullable String packageName,
+            @NonNull Bundle params) {
+        this.mId = id;
+        this.mName = name;
+        com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
+        this.mInputId = inputId;
+        this.mPackageName = packageName;
+        this.mParams = params;
+    }
+
+    @Nullable
+    public Long getProfileId() {
+        return mId;
+    }
+
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    @Nullable
+    public String getInputId() {
+        return mInputId;
+    }
+
+    @Nullable
+    public String getPackageName() {
+        return mPackageName;
+    }
+    @NonNull
+    public Bundle getParameters() {
+        return new Bundle(mParams);
+    }
+
+    /**
+     * A builder for {@link SoundProfile}
+     */
+    public static class Builder {
+        @Nullable
+        private Long mId;
+        @NonNull
+        private String mName;
+        @Nullable
+        private String mInputId;
+        @Nullable
+        private String mPackageName;
+        @NonNull
+        private Bundle mParams;
+
+        /**
+         * Creates a new Builder.
+         *
+         * @hide
+         */
+        public Builder(@NonNull String name) {
+            mName = name;
+            com.android.internal.util.AnnotationValidations.validate(NonNull.class, null, name);
+        }
+
+        /**
+         * Copy constructor.
+         *
+         * @hide
+         */
+        public Builder(@NonNull SoundProfile p) {
+            mId = null; // ID needs to be reset
+            mName = p.getName();
+            mPackageName = p.getPackageName();
+            mInputId = p.getInputId();
+        }
+
+        /**
+         * Sets profile ID.
+         * @hide using by MediaQualityService
+         */
+        @NonNull
+        public Builder setProfileId(@Nullable Long id) {
+            mId = id;
+            return this;
+        }
+
+        /**
+         * Sets input ID.
+         */
+        @NonNull
+        public Builder setInputId(@NonNull String value) {
+            mInputId = value;
+            return this;
+        }
+
+        /**
+         * Sets package name of the profile.
+         */
+        @NonNull
+        public Builder setPackageName(@NonNull String value) {
+            mPackageName = value;
+            return this;
+        }
+
+        /**
+         * Sets profile parameters.
+         */
+        @NonNull
+        public Builder setParameters(@NonNull Bundle params) {
+            mParams = new Bundle(params);
+            return this;
+        }
+        /**
+         * Builds the instance.
+         */
+        @NonNull
+        public SoundProfile build() {
+
+            SoundProfile o = new SoundProfile(
+                    mId,
+                    mName,
+                    mInputId,
+                    mPackageName,
+                    mParams);
+            return o;
+        }
+    }
+}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index b673e03..c2f6896 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -27,6 +27,7 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
+import android.annotation.UserIdInt;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.Intent;
@@ -760,6 +761,7 @@
      * @hide
      */
     public static final int UNKNOWN_CLIENT_PID = -1;
+
     /**
      * An unknown state of the client userId gets from the TvInputManager. Client gets this value
      * when query through {@link #getClientUserId(String sessionId)} fails.
@@ -2526,9 +2528,10 @@
      *
      * @hide
      */
-    @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+    @SystemApi
     @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
-    public int getClientUserId(@NonNull String sessionId) {
+    @RequiresPermission(android.Manifest.permission.SINGLE_USER_TIS_ACCESS)
+    public @UserIdInt int getClientUserId(@NonNull String sessionId) {
         return getClientUserIdInternal(sessionId);
     }
 
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 520ba89..474ff8c 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -82,6 +82,13 @@
     private boolean mRfDiscoveryStarted = false;
 
     /**
+     * Broadcast Action: Sent on NFC stack initialization when NFC OEM extensions are enabled.
+     * <p> OEM extension modules should use this intent to start their extension service </p>
+     * @hide
+     */
+    public static final String ACTION_OEM_EXTENSION_INIT = "android.nfc.action.OEM_EXTENSION_INIT";
+
+    /**
      * Mode Type for {@link #setControllerAlwaysOnMode(int)}.
      * Enables the controller in default mode when NFC is disabled (existing API behavior).
      * works same as {@link NfcAdapter#setControllerAlwaysOn(boolean)}.
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 6a7e693..34f0200 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -165,3 +165,11 @@
     description: "Enabling security log for nfc state change"
     bug: "319934052"
 }
+
+flag {
+    name: "nfc_associated_role_services"
+    is_exported: true
+    namespace: "nfc"
+    description: "Share wallet role routing priority with associated services"
+    bug: "366243361"
+}
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/HandlerExecutor.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/HandlerExecutor.kt
index be08606..99d3c8d 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/HandlerExecutor.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/HandlerExecutor.kt
@@ -24,7 +24,7 @@
  * Adapter of [Handler] and [Executor], where the task is executed on handler with given looper.
  *
  * When current looper is same with the given looper, task passed to [Executor.execute] will be
- * executed immediately to improve better performance.
+ * executed immediately to achieve better performance.
  *
  * @param looper Looper of the handler.
  */
diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
index 9cb0ebb..843d2aa 100644
--- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
+++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt
@@ -114,6 +114,10 @@
     fun notifyChange(key: K, reason: Int)
 }
 
+/** Delegation of [KeyedObservable]. */
+open class KeyedObservableDelegate<K>(delegate: KeyedObservable<K>) :
+    KeyedObservable<K> by delegate
+
 /** A thread safe implementation of [KeyedObservable]. */
 open class KeyedDataObservable<K> : KeyedObservable<K> {
     // Instead of @GuardedBy("this"), guarded by itself because KeyedDataObservable object could be
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index cbe602e..6b93cd7 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -77,6 +77,8 @@
   // Intent to show and locate the preference (might have highlight animation on
   // the preference).
   optional IntentProto launch_intent = 14;
+  // Descriptor of the preference value.
+  optional PreferenceValueDescriptorProto value_descriptor = 15;
 
   // Target of an Intent
   message ActionTarget {
@@ -103,9 +105,28 @@
 message PreferenceValueProto {
   oneof value {
     bool boolean_value = 1;
+    int32 int_value = 2;
   }
 }
 
+// Proto of preference value descriptor.
+message PreferenceValueDescriptorProto {
+  oneof type {
+    bool boolean_type = 1;
+    RangeValueProto range_value = 2;
+  }
+}
+
+// Proto of preference value that is between a range.
+message RangeValueProto {
+  // The lower bound (inclusive) of the range.
+  optional int32 min = 1;
+  // The upper bound (inclusive) of the range.
+  optional int32 max = 2;
+  // The increment step within the range. 0 means unset, which implies step size is 1.
+  optional int32 step = 3;
+}
+
 // Proto of android.content.Intent
 message IntentProto {
   // The action of the Intent.
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
index fdffe5d..5ceee6d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -65,6 +65,7 @@
     val visitedScreens: Set<String> = setOf(),
     val locale: Locale? = null,
     val includeValue: Boolean = true,
+    val includeValueDescriptor: Boolean = true,
 )
 
 object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index 9cb872a..2256bb3 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -49,6 +49,7 @@
 import com.android.settingslib.metadata.PreferenceScreenRegistry
 import com.android.settingslib.metadata.PreferenceSummaryProvider
 import com.android.settingslib.metadata.PreferenceTitleProvider
+import com.android.settingslib.metadata.RangeValue
 import com.android.settingslib.preference.PreferenceScreenFactory
 import com.android.settingslib.preference.PreferenceScreenProvider
 import java.util.Locale
@@ -66,6 +67,7 @@
     private val builder by lazy { PreferenceGraphProto.newBuilder() }
     private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) }
     private val includeValue = request.includeValue
+    private val includeValueDescriptor = request.includeValueDescriptor
 
     private suspend fun init() {
         for (key in request.screenKeys) {
@@ -270,7 +272,8 @@
                 summary = textProto { string = it.toString() }
             }
         }
-        if (metadata.icon != 0) icon = metadata.icon
+        val metadataIcon = metadata.getPreferenceIcon(context)
+        if (metadataIcon != 0) icon = metadataIcon
         if (metadata.keywords != 0) keywords = metadata.keywords
         val preferenceExtras = metadata.extras(context)
         preferenceExtras?.let { extras = it.toProto() }
@@ -283,14 +286,37 @@
             restricted = metadata.isRestricted(context)
         }
         persistent = metadata.isPersistent(context)
-        if (
-            includeValue &&
-                persistent &&
-                metadata is BooleanValue &&
-                metadata is PersistentPreference<*>
-        ) {
-            metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let {
-                value = preferenceValueProto { booleanValue = it }
+        if (persistent) {
+            if (includeValue && metadata is PersistentPreference<*>) {
+                value = preferenceValueProto {
+                    when (metadata) {
+                        is BooleanValue ->
+                            metadata
+                                .storage(context)
+                                .getValue(metadata.key, Boolean::class.javaObjectType)
+                                ?.let { booleanValue = it }
+                        is RangeValue -> {
+                            metadata
+                                .storage(context)
+                                .getValue(metadata.key, Int::class.javaObjectType)
+                                ?.let { intValue = it }
+                        }
+                        else -> {}
+                    }
+                }
+            }
+            if (includeValueDescriptor) {
+                valueDescriptor = preferenceValueDescriptorProto {
+                    when (metadata) {
+                        is BooleanValue -> booleanType = true
+                        is RangeValue -> rangeValue = rangeValueProto {
+                                min = metadata.getMinValue(context)
+                                max = metadata.getMaxValue(context)
+                                step = metadata.getIncrementStep(context)
+                            }
+                        else -> {}
+                    }
+                }
             }
         }
         if (metadata is PreferenceScreenMetadata) {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index d8db1bb..6e4db1d 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -27,8 +27,10 @@
 import com.android.settingslib.metadata.BooleanValue
 import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
+import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.PreferenceRestrictionProvider
 import com.android.settingslib.metadata.PreferenceScreenRegistry
+import com.android.settingslib.metadata.RangeValue
 import com.android.settingslib.metadata.ReadWritePermit
 
 /** Request to set preference value. */
@@ -114,27 +116,39 @@
         if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) {
             return PreferenceSetterResult.UNAVAILABLE
         }
+
+        fun <T> PreferenceMetadata.checkWritePermit(value: T): Int {
+            @Suppress("UNCHECKED_CAST") val preference = (this as PersistentPreference<T>)
+            return when (preference.getWritePermit(application, value, myUid, callingUid)) {
+                ReadWritePermit.ALLOW -> PreferenceSetterResult.OK
+                ReadWritePermit.DISALLOW -> PreferenceSetterResult.DISALLOW
+                ReadWritePermit.REQUIRE_APP_PERMISSION ->
+                    PreferenceSetterResult.REQUIRE_APP_PERMISSION
+                ReadWritePermit.REQUIRE_USER_AGREEMENT ->
+                    PreferenceSetterResult.REQUIRE_USER_AGREEMENT
+                else -> PreferenceSetterResult.INTERNAL_ERROR
+            }
+        }
+
         val storage = metadata.storage(application)
         val value = request.value
         try {
             if (value.hasBooleanValue()) {
                 if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST
                 val booleanValue = value.booleanValue
-                @Suppress("UNCHECKED_CAST")
-                val booleanPreference = metadata as PersistentPreference<Boolean>
-                when (
-                    booleanPreference.getWritePermit(application, booleanValue, myUid, callingUid)
-                ) {
-                    ReadWritePermit.ALLOW -> {}
-                    ReadWritePermit.DISALLOW -> return PreferenceSetterResult.DISALLOW
-                    ReadWritePermit.REQUIRE_APP_PERMISSION ->
-                        return PreferenceSetterResult.REQUIRE_APP_PERMISSION
-                    ReadWritePermit.REQUIRE_USER_AGREEMENT ->
-                        return PreferenceSetterResult.REQUIRE_USER_AGREEMENT
-                    else -> return PreferenceSetterResult.INTERNAL_ERROR
-                }
+                val resultCode = metadata.checkWritePermit(booleanValue)
+                if (resultCode != PreferenceSetterResult.OK) return resultCode
                 storage.setValue(key, Boolean::class.javaObjectType, booleanValue)
                 return PreferenceSetterResult.OK
+            } else if (value.hasIntValue()) {
+                val intValue = value.intValue
+                val resultCode = metadata.checkWritePermit(intValue)
+                if (resultCode != PreferenceSetterResult.OK) return resultCode
+                if (metadata is RangeValue && !metadata.isValidValue(application, intValue)) {
+                    return PreferenceSetterResult.INVALID_REQUEST
+                }
+                storage.setValue(key, Int::class.javaObjectType, intValue)
+                return PreferenceSetterResult.OK
             }
         } catch (e: Exception) {
             return PreferenceSetterResult.INTERNAL_ERROR
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
index d7dae77..dee32d9 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -24,7 +24,9 @@
 import com.android.settingslib.graph.proto.PreferenceProto
 import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
 import com.android.settingslib.graph.proto.PreferenceScreenProto
+import com.android.settingslib.graph.proto.PreferenceValueDescriptorProto
 import com.android.settingslib.graph.proto.PreferenceValueProto
+import com.android.settingslib.graph.proto.RangeValueProto
 import com.android.settingslib.graph.proto.TextProto
 
 /** Returns root or null. */
@@ -89,6 +91,16 @@
 inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) =
     PreferenceValueProto.newBuilder().also(init).build()
 
+/** Kotlin DSL-style builder for [PreferenceValueDescriptorProto]. */
+@JvmSynthetic
+inline fun preferenceValueDescriptorProto(init: PreferenceValueDescriptorProto.Builder.() -> Unit) =
+    PreferenceValueDescriptorProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [RangeValueProto]. */
+@JvmSynthetic
+inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit) =
+    RangeValueProto.newBuilder().also(init).build()
+
 /** Kotlin DSL-style builder for [TextProto]. */
 @JvmSynthetic
 inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build()
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
index d40a6f6..762f5ea 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceBindings.kt
@@ -23,7 +23,6 @@
 import androidx.preference.SwitchPreferenceCompat
 import androidx.preference.TwoStatePreference
 import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
-import com.android.settingslib.metadata.PersistentPreference
 import com.android.settingslib.metadata.PreferenceMetadata
 import com.android.settingslib.metadata.PreferenceScreenMetadata
 import com.android.settingslib.metadata.PreferenceTitleProvider
@@ -71,10 +70,10 @@
 
     override fun bind(preference: Preference, metadata: PreferenceMetadata) {
         super.bind(preference, metadata)
-        (metadata as? PersistentPreference<*>)
-            ?.storage(preference.context)
-            ?.getValue(metadata.key, Boolean::class.javaObjectType)
-            ?.let { (preference as TwoStatePreference).isChecked = it }
+        (preference as TwoStatePreference).apply {
+            // "false" is kind of placeholder, metadata datastore should provide the default value
+            isChecked = preferenceDataStore!!.getBoolean(key, false)
+        }
     }
 }
 
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
index c26ba18..657f69a 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceHierarchyInflater.kt
@@ -40,11 +40,11 @@
             addPreference(preferenceGroup)
             preferenceGroup.inflatePreferenceHierarchy(preferenceBindingFactory, it)
         } else {
-            preferenceBindingFactory.bind(widget, it, preferenceBinding)
             (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
                 widget.preferenceDataStore =
                     storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
             }
+            preferenceBindingFactory.bind(widget, it, preferenceBinding)
             // MUST add preference after binding for persistent preference to get initial value
             // (preference key is set within bind method)
             addPreference(widget)
diff --git a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
index 7af9c81..022fb1d 100644
--- a/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
+++ b/packages/SettingsLib/Preference/src/com/android/settingslib/preference/PreferenceScreenBindingHelper.kt
@@ -236,11 +236,12 @@
                     preference.bindRecursively(preferenceBindingFactory, preferences, storages)
                 } else {
                     preferences[preference.key]?.let {
-                        preferenceBindingFactory.bind(preference, it)
-                        (it as? PersistentPreference<*>)?.storage(context)?.let { storage ->
+                        val metadata = it.metadata
+                        (metadata as? PersistentPreference<*>)?.storage(context)?.let { storage ->
                             preference.preferenceDataStore =
                                 storages.getOrPut(storage) { PreferenceDataStoreAdapter(storage) }
                         }
+                        preferenceBindingFactory.bind(preference, it)
                     }
                 }
             }
diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
new file mode 100644
index 0000000..f3142d0
--- /dev/null
+++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/PreferenceBindingTestUtils.kt
@@ -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 com.android.settingslib.preference
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.preference.Preference
+import com.android.settingslib.metadata.PersistentPreference
+import com.android.settingslib.metadata.PreferenceMetadata
+
+/** Creates [Preference] widget and binds with metadata. */
+@VisibleForTesting
+fun <P : Preference> PreferenceMetadata.createAndBindWidget(context: Context): P {
+    val binding = DefaultPreferenceBindingFactory.getPreferenceBinding(this)
+    return (binding.createWidget(context) as P).also {
+        if (this is PersistentPreference<*>) {
+            storage(context)?.let { keyValueStore ->
+                it.preferenceDataStore = PreferenceDataStoreAdapter(keyValueStore)
+            }
+        }
+        binding.bind(it, this)
+    }
+}
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index b76b028..4d33466 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Veoma brzo"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Isteklo"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Isključen"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Nije povezano"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Prekidanje veze…"</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Povezivanje…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"Povezano<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-in/arrays.xml b/packages/SettingsLib/res/values-in/arrays.xml
index 038800c..00ea04d 100644
--- a/packages/SettingsLib/res/values-in/arrays.xml
+++ b/packages/SettingsLib/res/values-in/arrays.xml
@@ -29,7 +29,7 @@
     <item msgid="4613015005934755724">"Terhubung"</item>
     <item msgid="3763530049995655072">"Ditangguhkan"</item>
     <item msgid="7852381437933824454">"Memutus sambungan..."</item>
-    <item msgid="5046795712175415059">"Sambungan terputus"</item>
+    <item msgid="5046795712175415059">"Tidak terhubung"</item>
     <item msgid="2473654476624070462">"Gagal"</item>
     <item msgid="9146847076036105115">"Diblokir"</item>
     <item msgid="4543924085816294893">"Menghindari sambungan buruk untuk sementara"</item>
@@ -43,7 +43,7 @@
     <item msgid="1043944043827424501">"Terhubung ke <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</item>
     <item msgid="7445993821842009653">"Ditangguhkan"</item>
     <item msgid="1175040558087735707">"Diputus dari <xliff:g id="NETWORK_NAME">%1$s</xliff:g>…"</item>
-    <item msgid="699832486578171722">"Sambungan terputus"</item>
+    <item msgid="699832486578171722">"Tidak terhubung"</item>
     <item msgid="522383512264986901">"Gagal"</item>
     <item msgid="3602596701217484364">"Diblokir"</item>
     <item msgid="1999413958589971747">"Menghindari sambungan buruk untuk sementara"</item>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index 3b265f8..bfd8f39 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -50,7 +50,7 @@
     <string name="wifi_security_owe" msgid="3343421403561657809">"Enhanced Open"</string>
     <string name="wifi_security_eap_suiteb" msgid="415842785991698142">"WPA3-Enterprise 192-bit"</string>
     <string name="wifi_remembered" msgid="3266709779723179188">"Disimpan"</string>
-    <string name="wifi_disconnected" msgid="7054450256284661757">"Terputus"</string>
+    <string name="wifi_disconnected" msgid="7054450256284661757">"Tidak terhubung"</string>
     <string name="wifi_disabled_generic" msgid="2651916945380294607">"Nonaktif"</string>
     <string name="wifi_disabled_network_failure" msgid="2660396183242399585">"Kegagalan Konfigurasi IP"</string>
     <string name="wifi_disabled_password_failure" msgid="6892387079613226738">"Masalah autentikasi"</string>
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Sangat Cepat"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Sudah tidak berlaku"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Sambungan terputus"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Tidak terhubung"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Memutus sambungan..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Menghubungkan…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"Terhubung<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 2fb036f..785bf43 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -258,7 +258,7 @@
     <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"QR кодунун сканерин колдонуп, жаңы түзмөктөрдү жупташтырыңыз"</string>
     <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Түзмөктү атайын код аркылуу жупташтыруу"</string>
     <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Жаңы түзмөктөрдү алты сандан турган код аркылуу жупташтырасыз"</string>
-    <string name="adb_paired_devices_title" msgid="5268997341526217362">"Жупташтырылган түзмөктөр"</string>
+    <string name="adb_paired_devices_title" msgid="5268997341526217362">"Байланышкан түзмөктөр"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Учурда туташып турган түзмөктөр"</string>
     <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Түзмөктүн чоо-жайы"</string>
     <string name="adb_device_forget" msgid="193072400783068417">"Унутулсун"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index f5e84a2..b9d970d 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -85,7 +85,7 @@
     <string name="bluetooth_disconnected" msgid="7739366554710388701">"Не е поврзано"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Се исклучува..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Се поврзува..."</string>
-    <string name="bluetooth_connected" msgid="8065345572198502293">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
+    <string name="bluetooth_connected" msgid="8065345572198502293">"Поврзано<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
     <string name="bluetooth_pairing" msgid="4269046942588193600">"Се спарува..."</string>
     <string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> (без телефон)"</string>
     <string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"Поврзан со <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> (без аудиовизуелни содржини)"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 73b0996..ae1df21 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"ଅତି ଦ୍ରୁତ"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"ମିଆଦ ଶେଷ ହୋଇଯାଇଛି"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"ବିଛିନ୍ନ ହେଲା"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"ଡିସକନେକ୍ଟ ହୋଇଛି"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"ବିଚ୍ଛିନ୍ନ କରୁଛି…"</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"କନେକ୍ଟ ହେଉଛି…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"ସଂଯୁକ୍ତ ହେଲା<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 74c294b..4c41b7d 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Veľmi rýchla"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Vypršalo"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Odpojený"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Odpojené"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Prebieha odpájanie..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Prebieha pripájanie…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"Pripojené k zariadeniu <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 1c1117d..1d7f62a 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"Mycket snabb"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"Har upphört att gälla"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Kopplas ifrån"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"Frånkopplad"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"Kopplar ifrån…"</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"Ansluter…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"Ansluten<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 57b8f59..060b7b2 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -82,7 +82,7 @@
     <string name="speed_label_very_fast" msgid="8215718029533182439">"เร็วมาก"</string>
     <string name="wifi_passpoint_expired" msgid="6540867261754427561">"หมดอายุแล้ว"</string>
     <string name="preference_summary_default_combination" msgid="2644094566845577901">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="DESCRIPTION">%2$s</xliff:g>"</string>
-    <string name="bluetooth_disconnected" msgid="7739366554710388701">"ตัดการเชื่อมต่อ"</string>
+    <string name="bluetooth_disconnected" msgid="7739366554710388701">"ยกเลิกการเชื่อมต่อแล้ว"</string>
     <string name="bluetooth_disconnecting" msgid="7638892134401574338">"กำลังตัดการเชื่อมต่อ..."</string>
     <string name="bluetooth_connecting" msgid="5871702668260192755">"กำลังเชื่อมต่อ…"</string>
     <string name="bluetooth_connected" msgid="8065345572198502293">"เชื่อมต่อแล้ว<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
index e7c7476..f4b79db 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
@@ -120,4 +120,10 @@
 
     /** Device setting ID for ANC. */
     int DEVICE_SETTING_ID_ANC = 1001;
+
+    /** Device setting expandable ID 1. */
+    int DEVICE_SETTING_ID_EXPANDABLE_1 = 3001;
+
+    /** Device setting expandable ID 2. */
+    int DEVICE_SETTING_ID_EXPANDABLE_2 = 3100;
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
index 3d4282c..b997e4d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingPendingIntentAction.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.bluetooth.devicesettings;
 
 import android.app.PendingIntent;
-import android.content.Intent;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -47,7 +46,7 @@
     /** Read a {@link DeviceSettingPendingIntentAction} instance from {@link Parcel} */
     @NonNull
     public static DeviceSettingPendingIntentAction readFromParcel(@NonNull Parcel in) {
-        PendingIntent pendingIntent = in.readParcelable(Intent.class.getClassLoader());
+        PendingIntent pendingIntent = in.readParcelable(PendingIntent.class.getClassLoader());
         Bundle extras = in.readBundle(Bundle.class.getClassLoader());
         return new DeviceSettingPendingIntentAction(pendingIntent, extras);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index 8537897..c68dbee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -106,26 +106,40 @@
 
     private fun DeviceSettingsConfig.toModel(): DeviceSettingConfigModel =
         DeviceSettingConfigModel(
-            mainItems = mainContentItems.map { it.toModel() },
-            moreSettingsItems = moreSettingsItems.map { it.toModel() },
+            mainItems = mainContentItems.toModel(),
+            moreSettingsItems = moreSettingsItems.toModel(),
             moreSettingsHelpItem = moreSettingsHelpItem?.toModel(),
         )
 
-    private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel {
+    private fun List<DeviceSettingItem>.toModel(): List<DeviceSettingConfigItemModel> {
+        return this.flatMap { item ->
+            if (item.settingId in EXPANDABLE_SETTING_IDS) {
+                IntRange(item.settingId, item.settingId + SETTING_ID_EXPAND_LIMIT - 1).map {
+                    item.toModel(overrideSettingId = it)
+                }
+            } else {
+                listOf(item.toModel())
+            }
+        }
+    }
+
+    private fun DeviceSettingItem.toModel(
+        overrideSettingId: Int? = null
+    ): DeviceSettingConfigItemModel {
         return if (!TextUtils.isEmpty(preferenceKey)) {
             if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) {
                 BluetoothProfilesItem(
-                    settingId,
+                    overrideSettingId ?: settingId,
                     highlighted,
                     preferenceKey!!,
                     extras.getStringArrayList(DeviceSettingContract.INVISIBLE_PROFILES)
                         ?: emptyList(),
                 )
             } else {
-                CommonBuiltinItem(settingId, highlighted, preferenceKey!!)
+                CommonBuiltinItem(overrideSettingId ?: settingId, highlighted, preferenceKey!!)
             }
         } else {
-            AppProvidedItem(settingId, highlighted)
+            AppProvidedItem(overrideSettingId ?: settingId, highlighted)
         }
     }
 
@@ -134,6 +148,7 @@
             is DeviceSettingIntentAction -> DeviceSettingActionModel.IntentAction(this.intent)
             is DeviceSettingPendingIntentAction ->
                 DeviceSettingActionModel.PendingIntentAction(this.pendingIntent)
+
             else -> null
         }
 
@@ -150,7 +165,7 @@
                     summary = pref.summary,
                     icon = pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) },
                     isAllowedChangingState = pref.isAllowedChangingState,
-                    action = pref.action?.toModel(),
+                    action = pref.action.toModel(),
                     switchState =
                         if (pref.hasSwitch()) {
                             DeviceSettingStateModel.ActionSwitchPreferenceState(pref.checked)
@@ -163,6 +178,7 @@
                         }
                     },
                 )
+
             is MultiTogglePreference ->
                 DeviceSettingModel.MultiTogglePreference(
                     cachedDevice = cachedDevice,
@@ -178,21 +194,33 @@
                         }
                     },
                 )
+
             is DeviceSettingFooterPreference ->
                 DeviceSettingModel.FooterPreference(
                     cachedDevice = cachedDevice,
                     id = settingId,
                     footerText = pref.footerText,
                 )
+
             is DeviceSettingHelpPreference ->
                 DeviceSettingModel.HelpPreference(
                     cachedDevice = cachedDevice,
                     id = settingId,
                     intent = pref.intent,
                 )
+
             else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
         }
 
     private fun ToggleInfo.toModel(): ToggleModel =
         ToggleModel(label, DeviceSettingIcon.BitmapIcon(icon))
+
+    companion object {
+        private val EXPANDABLE_SETTING_IDS =
+            listOf(
+                DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_1,
+                DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_2,
+            )
+        private const val SETTING_ID_EXPAND_LIMIT = 15
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index 4e62fd3b..3d4e449 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -167,6 +167,26 @@
     }
 
     @Test
+    fun getDeviceSettingsConfig_expandable_success() {
+        testScope.runTest {
+            setUpConfigService(true, DEVICE_SETTING_CONFIG_EXPANDABLE)
+            `when`(settingProviderService1.serviceStatus)
+                .thenReturn(DeviceSettingsProviderServiceStatus(true))
+            `when`(settingProviderService2.serviceStatus)
+                .thenReturn(DeviceSettingsProviderServiceStatus(true))
+
+            val config = underTest.getDeviceSettingsConfig(cachedDevice)!!
+
+            assertThat(config.mainItems.map { it.settingId }).isEqualTo(
+                IntRange(
+                    DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_1,
+                    DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_1 + 14
+                ).toList()
+            )
+        }
+    }
+
+    @Test
     fun getDeviceSettingsConfig_noMetadata_returnNull() {
         testScope.runTest {
             `when`(
@@ -510,6 +530,13 @@
                 SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
                 SETTING_PROVIDER_SERVICE_INTENT_ACTION_2,
             )
+        val DEVICE_SETTING_APP_PROVIDED_ITEM_EXPANDABLE =
+            DeviceSettingItem(
+                DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_1,
+                SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
+                SETTING_PROVIDER_SERVICE_CLASS_NAME_1,
+                SETTING_PROVIDER_SERVICE_INTENT_ACTION_1,
+            )
         val DEVICE_SETTING_BUILT_IN_ITEM =
             DeviceSettingItem(
                 DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_AUDIO_DEVICE_TYPE_GROUP,
@@ -581,5 +608,13 @@
                 listOf(DEVICE_SETTING_APP_PROVIDED_ITEM_2),
                 DEVICE_SETTING_HELP_ITEM,
             )
+        val DEVICE_SETTING_CONFIG_EXPANDABLE =
+            DeviceSettingsConfig(
+                listOf(
+                    DEVICE_SETTING_APP_PROVIDED_ITEM_EXPANDABLE,
+                ),
+                listOf(),
+                DEVICE_SETTING_HELP_ITEM,
+            )
     }
 }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 05c5e5d..1919572 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -951,6 +951,9 @@
     <!-- Permission required for CTS test - CtsAppTestCases -->
     <uses-permission android:name="android.permission.KILL_UID" />
 
+    <!-- Permission required for CTS test - CtsTelephonyTestCases -->
+    <uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/StatementService/Android.bp b/packages/StatementService/Android.bp
index ff1a756..90e1808 100644
--- a/packages/StatementService/Android.bp
+++ b/packages/StatementService/Android.bp
@@ -35,6 +35,7 @@
     privileged: true,
     certificate: "platform",
     static_libs: [
+        "StatementServiceParser",
         "androidx.appcompat_appcompat",
         "androidx.collection_collection-ktx",
         "androidx.work_work-runtime",
diff --git a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
index 455e8085..ad137400 100644
--- a/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
+++ b/packages/StatementService/src/com/android/statementservice/network/retriever/StatementParser.kt
@@ -28,6 +28,8 @@
 import java.util.ArrayList
 import com.android.statementservice.retriever.WebAsset
 import com.android.statementservice.retriever.AndroidAppAsset
+import com.android.statementservice.retriever.DynamicAppLinkComponent
+import org.json.JSONObject
 
 /**
  * Parses JSON from the Digital Asset Links specification. For examples, see [WebAsset],
@@ -97,13 +99,45 @@
                 FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
             )
         val target = AssetFactory.create(targetObject)
+        val dynamicAppLinkComponents = parseDynamicAppLinkComponents(
+            statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS)
+        )
 
         val statements = (0 until relations.length())
             .map { relations.getString(it) }
             .map(Relation::create)
-            .map { Statement.create(source, target, it) }
+            .map { Statement.create(source, target, it, dynamicAppLinkComponents) }
         return Result.Success(ParsedStatement(statements, listOfNotNull(delegate)))
     }
 
+    private fun parseDynamicAppLinkComponents(
+        statement: JSONObject?
+    ): List<DynamicAppLinkComponent> {
+        val relationExtensions = statement?.optJSONObject(
+            StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS
+        ) ?: return emptyList()
+        val handleAllUrlsRelationExtension = relationExtensions.optJSONObject(
+            StatementUtils.RELATION.toString()
+        ) ?: return emptyList()
+        val components = handleAllUrlsRelationExtension.optJSONArray(
+            StatementUtils.RELATION_EXTENSION_FIELD_DAL_COMPONENTS
+        ) ?: return emptyList()
+
+        return (0 until components.length())
+            .map { components.getJSONObject(it) }
+            .map { parseComponent(it) }
+    }
+
+    private fun parseComponent(component: JSONObject): DynamicAppLinkComponent {
+        val query = component.optJSONObject("?")
+        return DynamicAppLinkComponent.create(
+            component.optBoolean("exclude", false),
+            component.optString("#"),
+            component.optString("/"),
+            query?.keys()?.asSequence()?.associateWith { query.getString(it) },
+            component.optString("comments")
+        )
+    }
+
     data class ParsedStatement(val statements: List<Statement>, val delegates: List<String>)
 }
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
new file mode 100644
index 0000000..dc27e12
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DynamicAppLinkComponent.java
@@ -0,0 +1,145 @@
+/*
+ * 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.statementservice.retriever;
+
+import android.annotation.Nullable;
+
+import java.util.Map;
+
+/**
+ * A immutable value type representing a dynamic app link component
+ */
+public final class DynamicAppLinkComponent {
+    private final boolean mExclude;
+    private final String mFragment;
+    private final String mPath;
+    private final Map<String, String> mQuery;
+    private final String mComments;
+
+    private DynamicAppLinkComponent(boolean exclude, String fragment, String path,
+                                    Map<String, String> query, String comments) {
+        mExclude = exclude;
+        mFragment = fragment;
+        mPath = path;
+        mQuery = query;
+        mComments = comments;
+    }
+
+    /**
+     * Returns true or false indicating whether this rule should be a exclusion rule.
+     */
+    public boolean getExclude() {
+        return mExclude;
+    }
+
+    /**
+     * Returns a optional pattern string for matching URL fragments.
+     */
+    @Nullable
+    public String getFragment() {
+        return mFragment;
+    }
+
+    /**
+     * Returns a optional pattern string for matching URL paths.
+     */
+    @Nullable
+    public String getPath() {
+        return mPath;
+    }
+
+    /**
+     * Returns a optional pattern string for matching a single key-value pair in the URL query
+     * params.
+     */
+    @Nullable
+    public Map<String, String> getQuery() {
+        return mQuery;
+    }
+
+    /**
+     * Returns a optional comment string for this component.
+     */
+    @Nullable
+    public String getComments() {
+        return mComments;
+    }
+
+    /**
+     * Creates a new DynamicAppLinkComponent object.
+     */
+    public static DynamicAppLinkComponent create(boolean exclude, String fragment, String path,
+                                                 Map<String, String> query, String comments) {
+        return new DynamicAppLinkComponent(exclude, fragment, path, query, comments);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DynamicAppLinkComponent rule = (DynamicAppLinkComponent) o;
+
+        if (mExclude != rule.mExclude) {
+            return false;
+        }
+        if (!mFragment.equals(rule.mFragment)) {
+            return false;
+        }
+        if (!mPath.equals(rule.mPath)) {
+            return false;
+        }
+        if (!mQuery.equals(rule.mQuery)) {
+            return false;
+        }
+        if (!mComments.equals(rule.mComments)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = Boolean.hashCode(mExclude);
+        result = 31 * result + mFragment.hashCode();
+        result = 31 * result + mPath.hashCode();
+        result = 31 * result + mQuery.hashCode();
+        result = 31 * result + mComments.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder statement = new StringBuilder();
+        statement.append("HandleAllUriRule: ");
+        statement.append(mExclude);
+        statement.append(", ");
+        statement.append(mFragment);
+        statement.append(", ");
+        statement.append(mPath);
+        statement.append(", ");
+        statement.append(mQuery);
+        statement.append(", ");
+        statement.append(mComments);
+        return statement.toString();
+    }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
index ce063ea..7635e82 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
@@ -46,12 +46,6 @@
         while (reader.hasNext()) {
             String fieldName = reader.nextName();
 
-            if (output.has(fieldName)) {
-                errorMsg = "Duplicate field name.";
-                reader.skipValue();
-                continue;
-            }
-
             JsonToken token = reader.peek();
             if (token.equals(JsonToken.BEGIN_ARRAY)) {
                 output.put(fieldName, new JSONArray(parseArray(reader)));
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/Statement.java b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
index f8bab3e..b5e2046 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/Statement.java
@@ -23,6 +23,10 @@
 
 import kotlin.coroutines.Continuation;
 
+import java.util.Collections;
+import java.util.List;
+
+
 /**
  * An immutable value type representing a statement, consisting of a source, target, and relation.
  * This reflects an assertion that the relation holds for the source, target pair. For example, if a
@@ -32,7 +36,21 @@
  * {
  * "relation": ["delegate_permission/common.handle_all_urls"],
  * "target"  : {"namespace": "android_app", "package_name": "com.example.app",
- *              "sha256_cert_fingerprints": ["00:11:22:33"] }
+ *              "sha256_cert_fingerprints": ["00:11:22:33"] },
+ * "relation_extensions": {
+ *     "delegate_permission/common_handle_all_urls": {
+ *         "dynamic_app_link_components": [
+ *             {
+ *                 "/": "/foo*",
+ *                 "exclude": true,
+ *                 "comments": "App should not handle paths that start with foo"
+ *             },
+ *             {
+ *                 "/": "*",
+ *                 "comments": "Catch all other paths"
+ *             }
+ *         ]
+ *     }
  * }
  * </pre>
  *
@@ -40,7 +58,7 @@
  * return a {@link Statement} with {@link #getSource} equal to the input parameter,
  * {@link #getRelation} equal to
  *
- * <pre>Relation.create("delegate_permission", "common.get_login_creds");</pre>
+ * <pre>Relation.create("delegate_permission", "common.handle_all_urls");</pre>
  *
  * and with {@link #getTarget} equal to
  *
@@ -48,17 +66,23 @@
  *                           + "\"package_name\": \"com.example.app\"}"
  *                           + "\"sha256_cert_fingerprints\": \"[\"00:11:22:33\"]\"}");
  * </pre>
+ *
+ * If extensions exist for the handle_all_urls relation then {@link #getDynamicAppLinkComponents}
+ * will return a list of parsed {@link DynamicAppLinkComponent}s.
  */
 public final class Statement {
 
     private final AbstractAsset mTarget;
     private final Relation mRelation;
     private final AbstractAsset mSource;
+    private final List<DynamicAppLinkComponent> mDynamicAppLinkComponents;
 
-    private Statement(AbstractAsset source, AbstractAsset target, Relation relation) {
+    private Statement(AbstractAsset source, AbstractAsset target, Relation relation,
+                      List<DynamicAppLinkComponent> components) {
         mSource = source;
         mTarget = target;
         mRelation = relation;
+        mDynamicAppLinkComponents = Collections.unmodifiableList(components);
     }
 
     /**
@@ -86,6 +110,14 @@
     }
 
     /**
+     * Returns the relation matching rules of the statement.
+     */
+    @NonNull
+    public List<DynamicAppLinkComponent> getDynamicAppLinkComponents() {
+        return mDynamicAppLinkComponents;
+    }
+
+    /**
      * Creates a new Statement object for the specified target asset and relation. For example:
      * <pre>
      *   Asset asset = Asset.Factory.create(
@@ -95,8 +127,9 @@
      * </pre>
      */
     public static Statement create(@NonNull AbstractAsset source, @NonNull AbstractAsset target,
-                                   @NonNull Relation relation) {
-        return new Statement(source, target, relation);
+                                   @NonNull Relation relation,
+                                   @NonNull List<DynamicAppLinkComponent> components) {
+        return new Statement(source, target, relation, components);
     }
 
     @Override
@@ -119,6 +152,9 @@
         if (!mSource.equals(statement.mSource)) {
             return false;
         }
+        if (!mDynamicAppLinkComponents.equals(statement.mDynamicAppLinkComponents)) {
+            return false;
+        }
 
         return true;
     }
@@ -128,6 +164,7 @@
         int result = mTarget.hashCode();
         result = 31 * result + mRelation.hashCode();
         result = 31 * result + mSource.hashCode();
+        result = 31 * result + mDynamicAppLinkComponents.hashCode();
         return result;
     }
 
@@ -140,6 +177,10 @@
         statement.append(mTarget);
         statement.append(", ");
         statement.append(mRelation);
+        if (!mDynamicAppLinkComponents.isEmpty()) {
+            statement.append(", ");
+            statement.append(mDynamicAppLinkComponents);
+        }
         return statement.toString();
     }
 }
diff --git a/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
index 4837aad..47c69b4 100644
--- a/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
+++ b/packages/StatementService/src/com/android/statementservice/utils/StatementUtils.kt
@@ -17,8 +17,17 @@
 package com.android.statementservice.utils
 
 import android.content.Context
+import android.content.UriRelativeFilter
+import android.content.UriRelativeFilter.FRAGMENT
+import android.content.UriRelativeFilter.PATH
+import android.content.UriRelativeFilter.QUERY
+import android.content.UriRelativeFilterGroup
+import android.content.UriRelativeFilterGroup.ACTION_ALLOW
+import android.content.UriRelativeFilterGroup.ACTION_BLOCK
 import android.content.pm.PackageManager
 import android.util.Patterns
+import com.android.statementservice.parser.parseMatchingExpression
+import com.android.statementservice.retriever.DynamicAppLinkComponent
 import com.android.statementservice.retriever.Relation
 import java.net.URL
 import java.security.MessageDigest
@@ -52,7 +61,9 @@
      */
     const val ASSET_DESCRIPTOR_FIELD_RELATION = "relation"
     const val ASSET_DESCRIPTOR_FIELD_TARGET = "target"
+    const val ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS = "relation_extensions"
     const val DELEGATE_FIELD_DELEGATE = "include"
+    const val RELATION_EXTENSION_FIELD_DAL_COMPONENTS = "dynamic_app_link_components"
 
     val HEX_DIGITS =
         charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
@@ -160,4 +171,23 @@
     // Hosts with *. for wildcard subdomain support are verified against their root domain
     fun createWebAssetString(host: String) =
         WEB_ASSET_FORMAT.format(URL("https", host.removePrefix("*."), "").toString())
+
+    fun createUriRelativeFilterGroup(component: DynamicAppLinkComponent): UriRelativeFilterGroup {
+        val group = UriRelativeFilterGroup(if (component.exclude) ACTION_BLOCK else ACTION_ALLOW)
+        component.fragment?.let {
+            val (type, filter) = parseMatchingExpression(it)
+            group.addUriRelativeFilter(UriRelativeFilter(FRAGMENT, type, filter))
+        }
+        component.path?.let {
+            val (type, filter) = parseMatchingExpression(it)
+            group.addUriRelativeFilter(UriRelativeFilter(PATH, type, filter))
+        }
+        component.query?.let {
+            for ((k, v) in it) {
+                val (type, filter) = parseMatchingExpression(k + "=" + v)
+                group.addUriRelativeFilter(UriRelativeFilter(QUERY, type, filter))
+            }
+        }
+        return group
+    }
 }
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0537b14..f0e1b43 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -1016,6 +1016,10 @@
             android:exported="false">
         </activity>
 
+        <service
+            android:name="com.android.systemui.communal.widgets.GlanceableHubWidgetManagerService"
+            android:exported="false" />
+
         <!-- Doze with notifications, run in main sysui process for every user  -->
         <service
             android:name=".doze.DozeService"
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a56c2fe..b0bd5b7 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -26,6 +26,13 @@
 }
 
 flag {
+   name: "modes_ui_dialog_paging"
+   namespace: "systemui"
+   description: "Add pagination to the Modes dialog in quick settings."
+   bug: "376450983"
+}
+
+flag {
    name: "priority_people_section"
    namespace: "systemui"
    description: "Add a new section for priority people (aka important conversations)."
@@ -1154,6 +1161,13 @@
 }
 
 flag {
+  name: "communal_hub_on_mobile"
+  namespace: "systemui"
+  description: "Brings the glanceable hub experience to mobile phones"
+  bug: "375689917"
+}
+
+flag {
     name: "dream_overlay_updated_font"
     namespace: "systemui"
     description: "Flag to enable updated font settings for dream overlay"
@@ -1694,3 +1708,9 @@
     bug: "365064144"
 }
 
+flag {
+   name: "touchpad_three_finger_tap_customization"
+   namespace: "systemui"
+   description: "Customize touchpad three finger tap"
+   bug: "365063048"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6fc51e4..66a0908 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -53,6 +53,7 @@
 import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
 import com.android.systemui.res.R
 import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -84,7 +85,7 @@
     stackScrollLayout: NotificationStackScrollLayout,
     sharedNotificationContainerBinder: SharedNotificationContainerBinder,
     private val keyguardRootViewModel: KeyguardRootViewModel,
-    private val configurationState: ConfigurationState,
+    @ShadeDisplayAware private val configurationState: ConfigurationState,
     private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
index 5cb45e5..94c18cd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.notifications.ui.composable
 
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.util.fastCoerceAtLeast
 import androidx.compose.ui.util.fastCoerceAtMost
+import com.android.compose.nestedscroll.OnStopScope
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 import com.android.compose.nestedscroll.ScrollController
 
@@ -43,6 +45,7 @@
     isCurrentGestureOverscroll: () -> Boolean,
     onStart: (Float) -> Unit = {},
     onStop: (Float) -> Unit = {},
+    flingBehavior: FlingBehavior,
 ): PriorityNestedScrollConnection {
     return PriorityNestedScrollConnection(
         orientation = Orientation.Vertical,
@@ -77,8 +80,9 @@
                     return amountConsumed
                 }
 
-                override suspend fun onStop(initialVelocity: Float): Float {
-                    onStop(initialVelocity)
+                override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
+                    val consumedByScroll = flingToScroll(initialVelocity, flingBehavior)
+                    onStop(initialVelocity - consumedByScroll)
                     if (scrimOffset() < minScrimOffset()) {
                         animateScrimOffset(minScrimOffset())
                     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index e1b74a9..d8abfd7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -18,7 +18,9 @@
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.tween
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.layout.offset
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
@@ -30,6 +32,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastCoerceAtLeast
+import com.android.compose.nestedscroll.OnStopScope
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 import com.android.compose.nestedscroll.ScrollController
 import kotlin.math.max
@@ -46,32 +49,35 @@
     val screenHeight =
         with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
     val overscrollOffset = remember { Animatable(0f) }
-    val stackNestedScrollConnection = remember {
-        NotificationStackNestedScrollConnection(
-            stackOffset = { overscrollOffset.value },
-            canScrollForward = canScrollForward,
-            onScroll = { offsetAvailable ->
-                coroutineScope.launch {
-                    val maxProgress = screenHeight * 0.2f
-                    val tilt = 3f
-                    var offset =
-                        overscrollOffset.value +
-                            maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt))
-                    offset = max(offset, -1f * maxProgress)
-                    overscrollOffset.snapTo(offset)
-                }
-            },
-            onStop = { velocityAvailable ->
-                coroutineScope.launch {
-                    overscrollOffset.animateTo(
-                        targetValue = 0f,
-                        initialVelocity = velocityAvailable,
-                        animationSpec = tween(),
-                    )
-                }
-            },
-        )
-    }
+    val flingBehavior = ScrollableDefaults.flingBehavior()
+    val stackNestedScrollConnection =
+        remember(flingBehavior) {
+            NotificationStackNestedScrollConnection(
+                stackOffset = { overscrollOffset.value },
+                canScrollForward = canScrollForward,
+                onScroll = { offsetAvailable ->
+                    coroutineScope.launch {
+                        val maxProgress = screenHeight * 0.2f
+                        val tilt = 3f
+                        var offset =
+                            overscrollOffset.value +
+                                maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt))
+                        offset = max(offset, -1f * maxProgress)
+                        overscrollOffset.snapTo(offset)
+                    }
+                },
+                onStop = { velocityAvailable ->
+                    coroutineScope.launch {
+                        overscrollOffset.animateTo(
+                            targetValue = 0f,
+                            initialVelocity = velocityAvailable,
+                            animationSpec = tween(),
+                        )
+                    }
+                },
+                flingBehavior = flingBehavior,
+            )
+        }
 
     return this.then(
         Modifier.nestedScroll(stackNestedScrollConnection).offset {
@@ -86,6 +92,7 @@
     onStart: (Float) -> Unit = {},
     onScroll: (Float) -> Unit,
     onStop: (Float) -> Unit = {},
+    flingBehavior: FlingBehavior,
 ): PriorityNestedScrollConnection {
     return PriorityNestedScrollConnection(
         orientation = Orientation.Vertical,
@@ -106,8 +113,9 @@
                     return consumed
                 }
 
-                override suspend fun onStop(initialVelocity: Float): Float {
-                    onStop(initialVelocity)
+                override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
+                    val consumedByScroll = flingToScroll(initialVelocity, flingBehavior)
+                    onStop(initialVelocity - consumedByScroll)
                     return initialVelocity
                 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 4fa1984..ae273d8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableDefaults
 import androidx.compose.foundation.gestures.animateScrollBy
 import androidx.compose.foundation.gestures.rememberScrollableState
 import androidx.compose.foundation.gestures.scrollBy
@@ -451,12 +452,14 @@
         }
     }
 
+    val flingBehavior = ScrollableDefaults.flingBehavior()
     val scrimNestedScrollConnection =
         shadeSession.rememberSession(
             scrimOffset,
             maxScrimTop,
             minScrimTop,
             isCurrentGestureOverscroll,
+            flingBehavior,
         ) {
             NotificationScrimNestedScrollConnection(
                 scrimOffset = { scrimOffset.value },
@@ -469,6 +472,7 @@
                 contentHeight = { stackHeight.intValue.toFloat() },
                 minVisibleScrimHeight = minVisibleScrimHeight,
                 isCurrentGestureOverscroll = { isCurrentGestureOverscroll.value },
+                flingBehavior = flingBehavior,
             )
         }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 9d4408a..46f5ecd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -121,14 +121,15 @@
     val systemBars = WindowInsets.systemBarsIgnoringVisibility
     val displayCutout = WindowInsets.displayCutout
     val waterfall = WindowInsets.waterfall
-    val contentPadding = PaddingValues(all = OverlayShade.Dimensions.ScrimContentPadding)
+    val horizontalPadding =
+        PaddingValues(horizontal = dimensionResource(id = R.dimen.shade_panel_margin_horizontal))
 
     val combinedPadding =
         combinePaddings(
             systemBars.asPaddingValues(),
             displayCutout.asPaddingValues(),
             waterfall.asPaddingValues(),
-            contentPadding,
+            horizontalPadding,
         )
 
     return if (widthSizeClass == WindowWidthSizeClass.Compact) {
@@ -174,7 +175,6 @@
     }
 
     object Dimensions {
-        val ScrimContentPadding = 16.dp
         val PanelCornerRadius = 46.dp
         val OverscrollLimit = 32.dp
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
index 28a12f8..581fb9d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt
@@ -92,7 +92,7 @@
                 onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
                 onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
-                hapticsViewModelFactory = sliderViewModel.hapticsViewModelFactory,
+                hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
             )
 
             ExpandButton(
@@ -142,7 +142,8 @@
                             onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
                             onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                             sliderColors = sliderColors,
-                            hapticsViewModelFactory = sliderViewModel.hapticsViewModelFactory,
+                            hapticsViewModelFactory =
+                                sliderViewModel.getSliderHapticsViewModelFactory(),
                         )
                     }
                 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
index a0e46d5..3718b10 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt
@@ -49,7 +49,7 @@
                 onValueChangeFinished = { sliderViewModel.onValueChangeFinished() },
                 onIconTapped = { sliderViewModel.toggleMuted(sliderState) },
                 sliderColors = sliderColors,
-                hapticsViewModelFactory = sliderViewModel.hapticsViewModelFactory,
+                hapticsViewModelFactory = sliderViewModel.getSliderHapticsViewModelFactory(),
             )
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index eb79b90..97ce429 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -48,7 +48,6 @@
 import androidx.compose.ui.unit.dp
 import com.android.compose.PlatformSlider
 import com.android.compose.PlatformSliderColors
-import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.ui.compose.Icon
 import com.android.systemui.compose.modifiers.sysuiResTag
@@ -66,15 +65,15 @@
     onIconTapped: () -> Unit,
     modifier: Modifier = Modifier,
     sliderColors: PlatformSliderColors,
-    hapticsViewModelFactory: SliderHapticsViewModel.Factory,
+    hapticsViewModelFactory: SliderHapticsViewModel.Factory?,
 ) {
     val value by valueState(state)
     val interactionSource = remember { MutableInteractionSource() }
     val sliderStepSize = 1f / (state.valueRange.endInclusive - state.valueRange.start)
     val hapticsViewModel: SliderHapticsViewModel? =
-        if (Flags.hapticsForComposeSliders()) {
+        hapticsViewModelFactory?.let {
             rememberViewModel(traceName = "SliderHapticsViewModel") {
-                hapticsViewModelFactory.create(
+                it.create(
                     interactionSource,
                     state.valueRange,
                     Orientation.Horizontal,
@@ -93,8 +92,6 @@
                     ),
                 )
             }
-        } else {
-            null
         }
 
     // Perform haptics due to UI composition
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 7c7202a..7872ffa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -27,6 +27,7 @@
 import com.android.compose.animation.scene.content.Content
 import com.android.compose.animation.scene.content.state.TransitionState
 import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
+import com.android.compose.nestedscroll.OnStopScope
 import com.android.compose.nestedscroll.PriorityNestedScrollConnection
 import com.android.compose.nestedscroll.ScrollController
 import kotlin.math.absoluteValue
@@ -749,7 +750,7 @@
             return dragController.onDrag(delta = deltaScroll)
         }
 
-        override suspend fun onStop(initialVelocity: Float): Float {
+        override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
             return dragController
                 .onStop(velocity = initialVelocity, canChangeContent = canChangeScene)
                 .invoke()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
index b00c8ad..8a6a0d6 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PredictiveBackHandler.kt
@@ -70,6 +70,7 @@
             // The predictive back APIs will automatically animate the progress for us in this case
             // so there is no need to animate it.
             cancelSpec = snap(),
+            animationScope = layoutImpl.animationScope,
         )
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
index 715d979..2b33224 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/Seek.kt
@@ -30,6 +30,8 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.compose.animation.scene.createSwipeAnimation
 import kotlin.coroutines.cancellation.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.collectLatest
@@ -141,6 +143,7 @@
     progress: Flow<Float>,
     commitSpec: AnimationSpec<Float>?,
     cancelSpec: AnimationSpec<Float>?,
+    animationScope: CoroutineScope? = null,
 ) {
     fun animateOffset(targetContent: T, spec: AnimationSpec<Float>?) {
         if (state.transitionState != animation.contentTransition || animation.isAnimatingOffset()) {
@@ -176,12 +179,20 @@
         }
 
         // Start the transition.
-        state.startTransition(animation.contentTransition)
+        animationScope?.launch { startTransition(state, animation, collectionJob) }
+            ?: startTransition(state, animation, collectionJob)
+    }
+}
 
-        // The transition is done. Cancel the collection in case the transition was finished because
-        // it was interrupted by another transition.
-        if (collectionJob.isActive) {
-            collectionJob.cancel()
-        }
+private suspend fun <T : ContentKey> startTransition(
+    state: MutableSceneTransitionLayoutStateImpl,
+    animation: SwipeAnimation<T>,
+    progressCollectionJob: Job,
+) {
+    state.startTransition(animation.contentTransition)
+    // The transition is done. Cancel the collection in case the transition was finished
+    // because it was interrupted by another transition.
+    if (progressCollectionJob.isActive) {
+        progressCollectionJob.cancel()
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index 255da31..a5be4dc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -16,6 +16,7 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -41,6 +42,7 @@
     onHeightChanged: (Float) -> Unit,
     minHeight: () -> Float,
     maxHeight: () -> Float,
+    flingBehavior: FlingBehavior,
 ): PriorityNestedScrollConnection {
     return PriorityNestedScrollConnection(
         orientation = Orientation.Vertical,
@@ -55,7 +57,15 @@
             offsetAvailable > 0 && height() < maxHeight()
         },
         canStartPostFling = { false },
-        onStart = { LargeTopAppBarScrollController(height, maxHeight, minHeight, onHeightChanged) },
+        onStart = {
+            LargeTopAppBarScrollController(
+                height = height,
+                maxHeight = maxHeight,
+                minHeight = minHeight,
+                onHeightChanged = onHeightChanged,
+                flingBehavior = flingBehavior,
+            )
+        },
     )
 }
 
@@ -64,6 +74,7 @@
     val maxHeight: () -> Float,
     val minHeight: () -> Float,
     val onHeightChanged: (Float) -> Unit,
+    val flingBehavior: FlingBehavior,
 ) : ScrollController {
     override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
         val currentHeight = height()
@@ -79,9 +90,8 @@
         return amountConsumed
     }
 
-    override suspend fun onStop(initialVelocity: Float): Float {
-        // Don't consume the velocity on pre/post fling
-        return 0f
+    override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
+        return flingToScroll(initialVelocity, flingBehavior)
     }
 
     override fun onCancel() {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index ca44a5c..e924ebf 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -16,7 +16,9 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -83,7 +85,16 @@
      * @param initialVelocity The initial velocity of the scroll when stopping.
      * @return The consumed [initialVelocity] when the animation completes.
      */
-    suspend fun onStop(initialVelocity: Float): Float
+    suspend fun OnStopScope.onStop(initialVelocity: Float): Float
+}
+
+interface OnStopScope {
+    /**
+     * Emits scroll events by using the [initialVelocity] and the [FlingBehavior].
+     *
+     * @return consumed velocity
+     */
+    suspend fun flingToScroll(initialVelocity: Float, flingBehavior: FlingBehavior): Float
 }
 
 /**
@@ -307,7 +318,11 @@
         val controller = requireController(isStopping = false)
         return coroutineScope {
             try {
-                async { controller.onStop(velocity) }
+                async {
+                        with(controller) {
+                            OnStopScopeImpl(controller = controller).onStop(velocity)
+                        }
+                    }
                     // Allows others to interrupt the job.
                     .also { stoppingJob = it }
                     // Note: this can be cancelled by [interruptStopping]
@@ -336,3 +351,19 @@
         offsetScrolledBeforePriorityMode = 0f
     }
 }
+
+private class OnStopScopeImpl(private val controller: ScrollController) : OnStopScope {
+    override suspend fun flingToScroll(
+        initialVelocity: Float,
+        flingBehavior: FlingBehavior,
+    ): Float {
+        return with(flingBehavior) {
+            object : ScrollScope {
+                    override fun scrollBy(pixels: Float): Float {
+                        return controller.onScroll(pixels, NestedScrollSource.SideEffect)
+                    }
+                }
+                .performFling(initialVelocity)
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index a406e13..e27f9b5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
@@ -29,6 +31,13 @@
     val scrollSource = testCase.scrollSource
 
     private var height = 0f
+    private val customFlingBehavior =
+        object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                scrollBy(initialVelocity)
+                return initialVelocity / 2f
+            }
+        }
 
     private fun buildScrollConnection(heightRange: ClosedFloatingPointRange<Float>) =
         LargeTopAppBarNestedScrollConnection(
@@ -36,6 +45,7 @@
             onHeightChanged = { height = it },
             minHeight = { heightRange.start },
             maxHeight = { heightRange.endInclusive },
+            flingBehavior = customFlingBehavior,
         )
 
     private fun NestedScrollConnection.scroll(
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 0364cdc..5442840 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -18,12 +18,15 @@
 
 package com.android.compose.nestedscroll
 
+import androidx.compose.foundation.gestures.FlingBehavior
 import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
 import androidx.compose.ui.unit.Velocity
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.runMonotonicClockTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
@@ -41,7 +44,16 @@
     private var consumeScroll = true
     private var lastStop: Float? = null
     private var isCancelled: Boolean = false
-    private var consumeStop = true
+    private var onStopConsumeFlingToScroll = false
+    private var onStopConsumeAll = true
+
+    private val customFlingBehavior =
+        object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                scrollBy(initialVelocity)
+                return initialVelocity / 2f
+            }
+        }
 
     private val scrollConnection =
         PriorityNestedScrollConnection(
@@ -57,9 +69,16 @@
                         return if (consumeScroll) deltaScroll else 0f
                     }
 
-                    override suspend fun onStop(initialVelocity: Float): Float {
+                    override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
                         lastStop = initialVelocity
-                        return if (consumeStop) initialVelocity else 0f
+                        var velocityConsumed = 0f
+                        if (onStopConsumeFlingToScroll) {
+                            velocityConsumed = flingToScroll(initialVelocity, customFlingBehavior)
+                        }
+                        if (onStopConsumeAll) {
+                            velocityConsumed = initialVelocity
+                        }
+                        return velocityConsumed
                     }
 
                     override fun onCancel() {
@@ -178,6 +197,22 @@
     }
 
     @Test
+    fun onStopScrollUsingFlingToScroll() = runMonotonicClockTest {
+        startPriorityModePostScroll()
+        onStopConsumeFlingToScroll = true
+        onStopConsumeAll = false
+        lastScroll = Float.NaN
+
+        val consumed = scrollConnection.onPreFling(available = Velocity(2f, 2f))
+
+        assertThat(lastStop).isEqualTo(2f)
+        // flingToScroll should try to scroll the content, customFlingBehavior uses the velocity.
+        assertThat(lastScroll).isEqualTo(2f)
+        // customFlingBehavior returns half of the vertical velocity.
+        assertThat(consumed).isEqualTo(Velocity(0f, 1f))
+    }
+
+    @Test
     fun ifCannotStopOnPreFling_shouldStopOnPostFling() = runTest {
         startPriorityModePostScroll()
         canStopOnPreFling = false
diff --git a/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
index be72d0b..3951e4c 100644
--- a/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
+++ b/packages/SystemUI/customization/res/drawable/clock_default_thumbnail.xml
@@ -14,7 +14,22 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="#FFFF00FF" />
-</shape>
+  <vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="36dp"
+    android:height="50dp"
+    android:viewportWidth="36"
+    android:viewportHeight="50">
+
+    <path
+        android:pathData="M8.592,22.712C11.052,22.712 13.063,21.635 14.628,19.48C16.193,17.326 16.976,14.623 16.976,11.372C16.976,8.059 16.198,5.346 14.644,3.232C13.099,1.109 11.082,0.047 8.592,0.047C6.113,0.047 4.096,1.109 2.541,3.232C0.997,5.346 0.225,8.059 0.225,11.372C0.225,14.664 0.997,17.377 2.541,19.511C4.096,21.645 6.113,22.712 8.592,22.712ZM8.592,18.901C7.322,18.901 6.301,18.225 5.529,16.874C4.757,15.523 4.37,13.699 4.37,11.402C4.37,9.045 4.757,7.206 5.529,5.885C6.301,4.553 7.322,3.888 8.592,3.888C9.873,3.888 10.899,4.543 11.671,5.854C12.444,7.155 12.83,9.004 12.83,11.402C12.83,13.78 12.449,15.624 11.686,16.935C10.924,18.246 9.893,18.901 8.592,18.901Z"
+        android:fillColor="#FFFFFF"/>
+    <path
+        android:pathData="M8.141,39.462C9.289,39.462 10.168,39.742 10.778,40.301C11.387,40.849 11.692,41.627 11.692,42.633C11.692,43.7 11.377,44.538 10.747,45.148C10.117,45.747 9.254,46.047 8.156,46.047C7.14,46.047 6.353,45.778 5.794,45.239C5.245,44.701 4.859,44.132 4.635,43.532C4.483,43.115 4.249,42.831 3.934,42.679C3.629,42.516 3.182,42.521 2.593,42.694C2.034,42.856 1.638,43.136 1.404,43.532C1.18,43.918 1.155,44.375 1.328,44.904C1.694,46.113 2.486,47.2 3.706,48.166C4.925,49.121 6.49,49.598 8.4,49.598C10.585,49.598 12.343,48.948 13.674,47.647C15.005,46.336 15.67,44.695 15.67,42.724C15.67,41.383 15.279,40.209 14.497,39.203C13.714,38.197 12.703,37.583 11.464,37.359V37.298C12.51,36.973 13.323,36.373 13.902,35.5C14.482,34.616 14.771,33.574 14.771,32.375C14.771,30.597 14.192,29.225 13.034,28.26C11.885,27.284 10.336,26.796 8.385,26.796C6.789,26.796 5.443,27.172 4.346,27.924C3.258,28.666 2.496,29.57 2.059,30.637C1.826,31.135 1.795,31.587 1.968,31.994C2.151,32.4 2.501,32.715 3.02,32.939C3.579,33.183 4.015,33.244 4.33,33.122C4.656,32.99 4.92,32.741 5.123,32.375C5.469,31.704 5.87,31.196 6.327,30.851C6.784,30.495 7.409,30.317 8.202,30.317H8.217C9.152,30.317 9.879,30.581 10.397,31.11C10.925,31.638 11.189,32.38 11.189,33.335C11.189,34.209 10.89,34.915 10.29,35.454C9.691,35.992 8.923,36.262 7.989,36.262H6.906C6.378,36.262 5.971,36.389 5.687,36.643C5.402,36.887 5.26,37.278 5.26,37.816C5.26,38.334 5.402,38.741 5.687,39.036C5.971,39.32 6.378,39.462 6.906,39.462H8.141Z"
+        android:fillColor="#FFFFFF"/>
+    <path
+        android:pathData="M23.263,7.531C23.263,6.372 23.644,5.432 24.406,4.711C25.178,3.979 26.153,3.613 27.332,3.613C28.531,3.613 29.507,3.984 30.259,4.726C31.021,5.458 31.402,6.393 31.402,7.531C31.402,8.679 30.99,9.639 30.167,10.411C29.354,11.184 28.404,11.57 27.317,11.57C26.169,11.57 25.203,11.194 24.421,10.442C23.649,9.68 23.263,8.709 23.263,7.531ZM26.936,22.026C27.271,21.558 27.759,20.867 28.399,19.953C29.049,19.038 30.04,17.595 31.371,15.624C32.144,14.476 32.956,13.236 33.81,11.905C34.664,10.574 35.09,9.116 35.09,7.531C35.09,5.468 34.333,3.705 32.819,2.242C31.315,0.778 29.466,0.047 27.271,0.047C25.198,0.047 23.4,0.778 21.875,2.242C20.361,3.705 19.604,5.498 19.604,7.622C19.604,9.756 20.331,11.524 21.784,12.926C23.237,14.318 24.959,15.014 26.951,15.014C27.754,15.014 28.303,14.959 28.597,14.847C28.892,14.725 29.151,14.603 29.375,14.481L28.536,13.292L28.277,13.643C27.932,14.12 27.545,14.659 27.119,15.258C26.702,15.858 26.285,16.468 25.869,17.087C25.351,17.829 24.929,18.429 24.604,18.886C24.289,19.343 24.06,19.678 23.918,19.892C23.573,20.4 23.456,20.872 23.567,21.309C23.689,21.736 24.004,22.107 24.512,22.422C25.01,22.767 25.457,22.905 25.854,22.834C26.26,22.773 26.621,22.503 26.936,22.026Z"
+        android:fillColor="#FFFFFF"/>
+    <path
+        android:pathData="M27.406,49.537C29.865,49.537 31.877,48.46 33.442,46.306C35.007,44.152 35.789,41.449 35.789,38.197C35.789,34.885 35.012,32.172 33.457,30.058C31.912,27.934 29.895,26.873 27.406,26.873C24.927,26.873 22.91,27.934 21.355,30.058C19.81,32.172 19.038,34.885 19.038,38.197C19.038,41.49 19.81,44.203 21.355,46.336C22.91,48.47 24.927,49.537 27.406,49.537ZM27.406,45.727C26.136,45.727 25.115,45.051 24.342,43.7C23.57,42.348 23.184,40.524 23.184,38.228C23.184,35.87 23.57,34.031 24.342,32.71C25.115,31.379 26.136,30.713 27.406,30.713C28.686,30.713 29.712,31.369 30.485,32.68C31.257,33.98 31.643,35.83 31.643,38.228C31.643,40.605 31.262,42.45 30.5,43.761C29.738,45.071 28.707,45.727 27.406,45.727Z"
+        android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
index f5e8432..bcf055b 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockDesign.kt
@@ -166,12 +166,6 @@
     // A color literal like `#FF00FF` or a color resource like `@android:color/system_accent1_100`
     val fillColorDark: String? = null,
     override val fontSizeScale: Float? = null,
-    /**
-     * use `wdth` for width, `wght` for weight, 'opsz' for optical size single quote for tag name,
-     * and no quote for value separate different axis with `,` e.g. "'wght' 1000, 'wdth' 108, 'opsz'
-     * 90"
-     */
-    var fontVariation: String? = null,
     // used when alternate in one font file is needed
     var fontFeatureSettings: String? = null,
     val renderType: RenderType = RenderType.STROKE_TEXT,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 9da3022..12b20a5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -44,6 +44,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import org.json.JSONObject
 
 private val KEY_TIMESTAMP = "appliedTimestamp"
 private val KNOWN_PLUGINS =
@@ -299,7 +300,7 @@
                         )
                     }
 
-                ClockSettings.deserialize(json)
+                ClockSettings.fromJson(JSONObject(json))
             } catch (ex: Exception) {
                 logger.e("Failed to parse clock settings", ex)
                 null
@@ -312,21 +313,24 @@
         assert.isNotMainThread()
 
         try {
-            value?.metadata?.put(KEY_TIMESTAMP, System.currentTimeMillis())
+            val json =
+                value?.let {
+                    it.metadata.put(KEY_TIMESTAMP, System.currentTimeMillis())
+                    ClockSettings.toJson(it)
+                } ?: ""
 
-            val json = ClockSettings.serialize(value)
             if (handleAllUsers) {
                 Settings.Secure.putStringForUser(
                     context.contentResolver,
                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
-                    json,
+                    json.toString(),
                     ActivityManager.getCurrentUser(),
                 )
             } else {
                 Settings.Secure.putString(
                     context.contentResolver,
                     Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
-                    json,
+                    json.toString(),
                 )
             }
         } catch (ex: Exception) {
@@ -557,14 +561,15 @@
     }
 
     fun getClocks(): List<ClockMetadata> {
-        if (!isEnabled) {
-            return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
-        }
+        if (!isEnabled) return listOf(availableClocks[DEFAULT_CLOCK_ID]!!.metadata)
         return availableClocks.map { (_, clock) -> clock.metadata }
     }
 
-    fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? =
-        availableClocks[clockId]?.provider?.getClockPickerConfig(clockId)
+    fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig? {
+        val clockSettings =
+            settings?.let { if (clockId == it.clockId) it else null } ?: ClockSettings(clockId)
+        return availableClocks[clockId]?.provider?.getClockPickerConfig(clockSettings)
+    }
 
     fun createExampleClock(clockId: ClockId): ClockController? = createClock(clockId)
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index d70d61c..e9b58b0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -19,10 +19,9 @@
 import com.android.systemui.customization.R
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.LogcatOnlyMessageBuffer
-import com.android.systemui.plugins.clocks.AxisType
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFontAxis
-import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockPickerConfig
@@ -61,7 +60,15 @@
                 messageBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.INFO)
             val assets = AssetLoader(ctx, ctx, "clocks/", buffer)
             assets.setSeedColor(settings.seedColor, null)
-            FlexClockController(ctx, resources, assets, FLEX_DESIGN, messageBuffers)
+            val fontAxes = ClockFontAxis.merge(FlexClockController.FONT_AXES, settings.axes)
+            FlexClockController(
+                ctx,
+                resources,
+                settings.copy(axes = fontAxes.map { it.toSetting() }),
+                assets,
+                FLEX_DESIGN,
+                messageBuffers,
+            )
         } else {
             DefaultClockController(
                 ctx,
@@ -75,64 +82,42 @@
         }
     }
 
-    override fun getClockPickerConfig(id: ClockId): ClockPickerConfig {
-        if (id != DEFAULT_CLOCK_ID) {
-            throw IllegalArgumentException("$id is unsupported by $TAG")
+    override fun getClockPickerConfig(settings: ClockSettings): ClockPickerConfig {
+        if (settings.clockId != DEFAULT_CLOCK_ID) {
+            throw IllegalArgumentException("${settings.clockId} is unsupported by $TAG")
         }
 
+        val fontAxes =
+            if (!isClockReactiveVariantsEnabled) listOf()
+            else ClockFontAxis.merge(FlexClockController.FONT_AXES, settings.axes)
         return ClockPickerConfig(
             DEFAULT_CLOCK_ID,
             resources.getString(R.string.clock_default_name),
             resources.getString(R.string.clock_default_description),
-            // TODO(b/352049256): Update placeholder to actual resource
             resources.getDrawable(R.drawable.clock_default_thumbnail, null),
             isReactiveToTone = true,
-            // TODO(b/364673969): Populate the rest of this
-            axes =
-                if (isClockReactiveVariantsEnabled)
-                    listOf(
-                        ClockFontAxis(
-                            key = "wght",
-                            type = AxisType.Float,
-                            minValue = 1f,
-                            currentValue = 400f,
-                            maxValue = 1000f,
-                            name = "Weight",
-                            description = "Glyph Weight",
-                        ),
-                        ClockFontAxis(
-                            key = "wdth",
-                            type = AxisType.Float,
-                            minValue = 25f,
-                            currentValue = 100f,
-                            maxValue = 151f,
-                            name = "Width",
-                            description = "Glyph Width",
-                        ),
-                        ClockFontAxis(
-                            key = "ROND",
-                            type = AxisType.Boolean,
-                            minValue = 0f,
-                            currentValue = 0f,
-                            maxValue = 100f,
-                            name = "Round",
-                            description = "Glyph Roundness",
-                        ),
-                        ClockFontAxis(
-                            key = "slnt",
-                            type = AxisType.Boolean,
-                            minValue = 0f,
-                            currentValue = 0f,
-                            maxValue = -10f,
-                            name = "Slant",
-                            description = "Glyph Slant",
-                        ),
-                    )
-                else listOf(),
+            axes = fontAxes,
         )
     }
 
     companion object {
+        // TODO(b/364681643): Variations for retargetted DIGITAL_CLOCK_FLEX
+        val LEGACY_FLEX_LS_VARIATION =
+            listOf(
+                ClockFontAxisSetting("wght", 600f),
+                ClockFontAxisSetting("wdth", 100f),
+                ClockFontAxisSetting("ROND", 100f),
+                ClockFontAxisSetting("slnt", 0f),
+            )
+
+        val LEGACY_FLEX_AOD_VARIATION =
+            listOf(
+                ClockFontAxisSetting("wght", 74f),
+                ClockFontAxisSetting("wdth", 43f),
+                ClockFontAxisSetting("ROND", 100f),
+                ClockFontAxisSetting("slnt", 0f),
+            )
+
         val FLEX_DESIGN = run {
             val largeLayer =
                 listOf(
@@ -144,16 +129,9 @@
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
                                     timespec = DigitalTimespec.FIRST_DIGIT,
-                                    style =
-                                        FontTextStyle(
-                                            lineHeight = 147.25f,
-                                            fontVariation =
-                                                "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                        ),
+                                    style = FontTextStyle(lineHeight = 147.25f),
                                     aodStyle =
                                         FontTextStyle(
-                                            fontVariation =
-                                                "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -170,16 +148,9 @@
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
                                     timespec = DigitalTimespec.SECOND_DIGIT,
-                                    style =
-                                        FontTextStyle(
-                                            lineHeight = 147.25f,
-                                            fontVariation =
-                                                "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                        ),
+                                    style = FontTextStyle(lineHeight = 147.25f),
                                     aodStyle =
                                         FontTextStyle(
-                                            fontVariation =
-                                                "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -196,16 +167,9 @@
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
                                     timespec = DigitalTimespec.FIRST_DIGIT,
-                                    style =
-                                        FontTextStyle(
-                                            lineHeight = 147.25f,
-                                            fontVariation =
-                                                "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                        ),
+                                    style = FontTextStyle(lineHeight = 147.25f),
                                     aodStyle =
                                         FontTextStyle(
-                                            fontVariation =
-                                                "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -222,16 +186,9 @@
                                 DigitalHandLayer(
                                     layerBounds = LayerBounds.FIT,
                                     timespec = DigitalTimespec.SECOND_DIGIT,
-                                    style =
-                                        FontTextStyle(
-                                            lineHeight = 147.25f,
-                                            fontVariation =
-                                                "'wght' 603, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                        ),
+                                    style = FontTextStyle(lineHeight = 147.25f),
                                     aodStyle =
                                         FontTextStyle(
-                                            fontVariation =
-                                                "'wght' 74, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                             fillColorLight = "#FFFFFFFF",
                                             outlineColor = "#00000000",
                                             renderType = RenderType.CHANGE_WEIGHT,
@@ -254,16 +211,9 @@
                     DigitalHandLayer(
                         layerBounds = LayerBounds.FIT,
                         timespec = DigitalTimespec.TIME_FULL_FORMAT,
-                        style =
-                            FontTextStyle(
-                                fontVariation =
-                                    "'wght' 600, 'wdth' 100, 'opsz' 144, 'ROND' 100, 'slnt' 0",
-                                fontSizeScale = 0.98f,
-                            ),
+                        style = FontTextStyle(fontSizeScale = 0.98f),
                         aodStyle =
                             FontTextStyle(
-                                fontVariation =
-                                    "'wght' 133, 'wdth' 43, 'opsz' 144, 'ROND' 100, 'slnt' 0",
                                 fillColorLight = "#FFFFFFFF",
                                 outlineColor = "#00000000",
                                 renderType = RenderType.CHANGE_WEIGHT,
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
index d8fd776..6c627e2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockController.kt
@@ -20,11 +20,14 @@
 import android.content.res.Resources
 import com.android.systemui.customization.R
 import com.android.systemui.plugins.clocks.AlarmData
+import com.android.systemui.plugins.clocks.AxisType
 import com.android.systemui.plugins.clocks.ClockConfig
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockEvents
+import com.android.systemui.plugins.clocks.ClockFontAxis
 import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
+import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.plugins.clocks.ThemeConfig
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.clocks.ZenData
@@ -37,6 +40,7 @@
 class FlexClockController(
     private val ctx: Context,
     private val resources: Resources,
+    private val settings: ClockSettings,
     private val assets: AssetLoader, // TODO(b/364680879): Remove and replace w/ resources
     val design: ClockDesign, // TODO(b/364680879): Remove when done inlining
     val messageBuffers: ClockMessageBuffers?,
@@ -114,13 +118,16 @@
             }
 
             override fun onFontAxesChanged(axes: List<ClockFontAxisSetting>) {
-                smallClock.events.onFontAxesChanged(axes)
-                largeClock.events.onFontAxesChanged(axes)
+                val fontAxes = ClockFontAxis.merge(FONT_AXES, axes).map { it.toSetting() }
+                smallClock.events.onFontAxesChanged(fontAxes)
+                largeClock.events.onFontAxesChanged(fontAxes)
             }
         }
 
     override fun initialize(isDarkTheme: Boolean, dozeFraction: Float, foldFraction: Float) {
         val theme = ThemeConfig(isDarkTheme, assets.seedColor)
+        events.onFontAxesChanged(settings.axes)
+
         smallClock.run {
             events.onThemeChanged(theme)
             animations.doze(dozeFraction)
@@ -137,4 +144,46 @@
     }
 
     override fun dump(pw: PrintWriter) {}
+
+    companion object {
+        val FONT_AXES =
+            listOf(
+                ClockFontAxis(
+                    key = "wght",
+                    type = AxisType.Float,
+                    minValue = 1f,
+                    currentValue = 400f,
+                    maxValue = 1000f,
+                    name = "Weight",
+                    description = "Glyph Weight",
+                ),
+                ClockFontAxis(
+                    key = "wdth",
+                    type = AxisType.Float,
+                    minValue = 25f,
+                    currentValue = 100f,
+                    maxValue = 151f,
+                    name = "Width",
+                    description = "Glyph Width",
+                ),
+                ClockFontAxis(
+                    key = "ROND",
+                    type = AxisType.Boolean,
+                    minValue = 0f,
+                    currentValue = 0f,
+                    maxValue = 100f,
+                    name = "Round",
+                    description = "Glyph Roundness",
+                ),
+                ClockFontAxis(
+                    key = "slnt",
+                    type = AxisType.Boolean,
+                    minValue = 0f,
+                    currentValue = 0f,
+                    maxValue = -10f,
+                    name = "Slant",
+                    description = "Glyph Slant",
+                ),
+            )
+    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index edcee9d..0b55a6e 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.graphics.Canvas
 import android.graphics.Point
-import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.widget.RelativeLayout
@@ -28,7 +27,6 @@
 import com.android.systemui.log.core.MessageBuffer
 import com.android.systemui.shared.clocks.AssetLoader
 import com.android.systemui.shared.clocks.DigitTranslateAnimator
-import com.android.systemui.shared.clocks.FontTextStyle
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
@@ -52,65 +50,6 @@
             )
     }
 
-    private var prevX = 0f
-    private var prevY = 0f
-    private var isDown = false
-
-    private var wght = 603f
-    private var wdth = 100f
-
-    private val MAX_WGHT = 950f
-    private val MIN_WGHT = 50f
-    private val WGHT_SCALE = 0.5f
-
-    private val MAX_WDTH = 150f
-    private val MIN_WDTH = 0f
-    private val WDTH_SCALE = 0.2f
-
-    override fun onTouchEvent(evt: MotionEvent): Boolean {
-        if (!isReactiveTouchInteractionEnabled) {
-            return super.onTouchEvent(evt)
-        }
-
-        when (evt.action) {
-            MotionEvent.ACTION_DOWN -> {
-                isDown = true
-                prevX = evt.x
-                prevY = evt.y
-                return true
-            }
-
-            MotionEvent.ACTION_MOVE -> {
-                if (!isDown) {
-                    return super.onTouchEvent(evt)
-                }
-
-                wdth = clamp(wdth + (evt.x - prevX) * WDTH_SCALE, MIN_WDTH, MAX_WDTH)
-                wght = clamp(wght + (evt.y - prevY) * WGHT_SCALE, MIN_WGHT, MAX_WGHT)
-                prevX = evt.x
-                prevY = evt.y
-
-                val fvar = "'wght' $wght, 'wdth' $wdth, 'opsz' 144, 'ROND' 100"
-                digitalClockTextViewMap.forEach { (_, view) ->
-                    val textStyle = view.textStyle as FontTextStyle
-                    textStyle.fontVariation = fvar
-                    view.applyStyles(assets, textStyle, view.aodStyle)
-                }
-
-                requestLayout()
-                invalidate()
-                return true
-            }
-
-            MotionEvent.ACTION_UP -> {
-                isDown = false
-                return true
-            }
-        }
-
-        return super.onTouchEvent(evt)
-    }
-
     override fun addView(child: View?) {
         super.addView(child)
         (child as SimpleDigitalClockTextView).digitTranslateAnimator =
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index 0dacce1..c0899e3 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -65,6 +65,9 @@
     val lockScreenPaint = TextPaint()
     override lateinit var textStyle: FontTextStyle
     lateinit var aodStyle: FontTextStyle
+
+    private var lsFontVariation = ClockFontAxisSetting.toFVar(DEFAULT_LS_VARIATION)
+    private var aodFontVariation = ClockFontAxisSetting.toFVar(DEFAULT_AOD_VARIATION)
     private val parser = DimensionParser(ctx)
     var maxSingleDigitHeight = -1
     var maxSingleDigitWidth = -1
@@ -142,18 +145,10 @@
     }
 
     override fun updateAxes(axes: List<ClockFontAxisSetting>) {
-        val sb = StringBuilder()
-        sb.append("'opsz' 144")
-
-        for (axis in axes) {
-            if (sb.length > 0) sb.append(", ")
-            sb.append("'${axis.key}' ${axis.value.toInt()}")
-        }
-
-        val fvar = sb.toString()
-        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
+        lsFontVariation = ClockFontAxisSetting.toFVar(axes + OPTICAL_SIZE_AXIS)
+        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
         typeface = lockScreenPaint.typeface
-        textAnimator.setTextStyle(fvar = fvar, animate = true)
+        textAnimator.setTextStyle(fvar = lsFontVariation, animate = true)
         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
         recomputeMaxSingleDigitSizes()
         requestLayout()
@@ -287,15 +282,12 @@
     }
 
     override fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
-        if (!this::textAnimator.isInitialized) {
-            return
-        }
-        val fvar = if (isDozing) aodStyle.fontVariation else textStyle.fontVariation
+        if (!this::textAnimator.isInitialized) return
         textAnimator.setTextStyle(
             animate = isAnimated && isAnimationEnabled,
             color = if (isDozing) AOD_COLOR else lockscreenColor,
             textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
-            fvar = fvar,
+            fvar = if (isDozing) aodFontVariation else lsFontVariation,
             duration = aodStyle.transitionDuration,
             interpolator = aodDozingInterpolator,
         )
@@ -308,14 +300,15 @@
             return
         }
         logger.d("animateCharge()")
-        val middleFvar = if (dozeFraction == 0F) aodStyle.fontVariation else textStyle.fontVariation
-        val endFvar = if (dozeFraction == 0F) textStyle.fontVariation else aodStyle.fontVariation
         val startAnimPhase2 = Runnable {
-            textAnimator.setTextStyle(fvar = endFvar, animate = isAnimationEnabled)
+            textAnimator.setTextStyle(
+                fvar = if (dozeFraction == 0F) lsFontVariation else aodFontVariation,
+                animate = isAnimationEnabled,
+            )
             updateTextBoundsForTextAnimator()
         }
         textAnimator.setTextStyle(
-            fvar = middleFvar,
+            fvar = if (dozeFraction == 0F) aodFontVariation else lsFontVariation,
             animate = isAnimationEnabled,
             onAnimationEnd = startAnimPhase2,
         )
@@ -444,7 +437,7 @@
         val typefaceName = "fonts/" + textStyle.fontFamily
         setTypefaceCache(assets.typefaceCache.getVariantCache(typefaceName))
         lockScreenPaint.strokeJoin = Paint.Join.ROUND
-        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(textStyle.fontVariation)
+        lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
         textStyle.fontFeatureSettings?.let {
             lockScreenPaint.fontFeatureSettings = it
             fontFeatureSettings = it
@@ -515,7 +508,7 @@
             textAnimator.textInterpolator.targetPaint.set(lockScreenPaint)
             textAnimator.textInterpolator.onTargetPaintModified()
             textAnimator.setTextStyle(
-                fvar = textStyle.fontVariation,
+                fvar = lsFontVariation,
                 textSize = lockScreenPaint.textSize,
                 color = lockscreenColor,
                 animate = false,
@@ -555,5 +548,22 @@
     companion object {
         val AOD_STROKE_WIDTH = "2dp"
         val AOD_COLOR = Color.WHITE
+        val OPTICAL_SIZE_AXIS = ClockFontAxisSetting("opsz", 144f)
+        val DEFAULT_LS_VARIATION =
+            listOf(
+                OPTICAL_SIZE_AXIS,
+                ClockFontAxisSetting("wght", 400f),
+                ClockFontAxisSetting("wdth", 100f),
+                ClockFontAxisSetting("ROND", 0f),
+                ClockFontAxisSetting("slnt", 0f),
+            )
+        val DEFAULT_AOD_VARIATION =
+            listOf(
+                OPTICAL_SIZE_AXIS,
+                ClockFontAxisSetting("wght", 200f),
+                ClockFontAxisSetting("wdth", 100f),
+                ClockFontAxisSetting("ROND", 0f),
+                ClockFontAxisSetting("slnt", 0f),
+            )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
new file mode 100644
index 0000000..8635bb0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
@@ -0,0 +1,124 @@
+/*
+ * 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
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.JUnitCore
+
+@Suppress("JUnitMalformedDeclaration")
+@SmallTest
+class OnTeardownRuleTest : SysuiTestCase() {
+    // None of these inner classes should be run except as part of this utilities-testing test
+    class HasTeardown {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Before
+        fun setUp() {
+            teardownWasRun = false
+            teardownRule.onTeardown { teardownWasRun = true }
+        }
+
+        @Test fun doTest() {}
+
+        companion object {
+            var teardownWasRun = false
+        }
+    }
+
+    @Test
+    fun teardownRuns() {
+        val result = JUnitCore().run(HasTeardown::class.java)
+        assertThat(result.failures).isEmpty()
+        assertThat(HasTeardown.teardownWasRun).isTrue()
+    }
+
+    class FirstTeardownFails {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Before
+        fun setUp() {
+            teardownWasRun = false
+            teardownRule.onTeardown { fail("One fails") }
+            teardownRule.onTeardown { teardownWasRun = true }
+        }
+
+        @Test fun doTest() {}
+
+        companion object {
+            var teardownWasRun = false
+        }
+    }
+
+    @Test
+    fun allTeardownsRun() {
+        val result = JUnitCore().run(FirstTeardownFails::class.java)
+        assertThat(result.failures.map { it.message }).isEqualTo(listOf("One fails"))
+        assertThat(FirstTeardownFails.teardownWasRun).isTrue()
+    }
+
+    class ThreeTeardowns {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Before
+        fun setUp() {
+            messages.clear()
+        }
+
+        @Test
+        fun doTest() {
+            teardownRule.onTeardown { messages.add("A") }
+            teardownRule.onTeardown { messages.add("B") }
+            teardownRule.onTeardown { messages.add("C") }
+        }
+
+        companion object {
+            val messages = mutableListOf<String>()
+        }
+    }
+
+    @Test
+    fun reverseOrder() {
+        val result = JUnitCore().run(ThreeTeardowns::class.java)
+        assertThat(result.failures).isEmpty()
+        assertThat(ThreeTeardowns.messages).isEqualTo(listOf("C", "B", "A"))
+    }
+
+    class TryToDoABadThing {
+        @get:Rule val teardownRule = OnTeardownRule()
+
+        @Test
+        fun doTest() {
+            teardownRule.onTeardown {
+                teardownRule.onTeardown {
+                    // do nothing
+                }
+            }
+        }
+    }
+
+    @Test
+    fun prohibitTeardownDuringTeardown() {
+        val result = JUnitCore().run(TryToDoABadThing::class.java)
+        assertThat(result.failures.map { it.message })
+            .isEqualTo(listOf("Cannot add new teardown routines after test complete."))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
similarity index 98%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
index 8ae9d2e..55d7d08 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryLocalImplTest.kt
@@ -73,7 +73,7 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
+class CommunalWidgetRepositoryLocalImplTest : SysuiTestCase() {
     @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
     @Mock private lateinit var providerInfoA: AppWidgetProviderInfo
     @Mock private lateinit var providerInfoB: AppWidgetProviderInfo
@@ -105,14 +105,14 @@
             "com.android.fake/WidgetProviderC",
         )
 
-    private lateinit var underTest: CommunalWidgetRepositoryImpl
+    private lateinit var underTest: CommunalWidgetRepositoryLocalImpl
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         fakeWidgets = MutableStateFlow(emptyMap())
         fakeProviders = MutableStateFlow(emptyMap())
-        logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
+        logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoLocalImplTest")
         backupUtils = CommunalBackupUtils(kosmos.applicationContext)
 
         setAppWidgetIds(emptyList())
@@ -126,7 +126,7 @@
         restoreUser(mainUser)
 
         underTest =
-            CommunalWidgetRepositoryImpl(
+            CommunalWidgetRepositoryLocalImpl(
                 appWidgetHost,
                 testScope.backgroundScope,
                 kosmos.testDispatcher,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModelTest.kt
new file mode 100644
index 0000000..c210154
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModelTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.communal.shared.model
+
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.os.Parcel
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalWidgetContentModelTest : SysuiTestCase() {
+    @Test
+    fun testParcelizeAvailableWidget() {
+        val widgetToParcelize =
+            CommunalWidgetContentModel.Available(
+                appWidgetId = 1,
+                providerInfo =
+                    AppWidgetProviderInfo().apply { provider = ComponentName("pkg", "cls") },
+                rank = 2,
+                spanY = 3,
+            )
+
+        val parcel = Parcel.obtain()
+        widgetToParcelize.writeToParcel(parcel, flags = 0)
+
+        parcel.setDataPosition(0)
+
+        // Only checking fields are equal and not complete equality because not all fields are
+        // specified in the fake AppWidgetProviderInfo
+        val widgetFromParcel =
+            CommunalWidgetContentModel.createFromParcel(parcel)
+                as CommunalWidgetContentModel.Available
+        assertThat(widgetFromParcel.appWidgetId).isEqualTo(widgetToParcelize.appWidgetId)
+        assertThat(widgetFromParcel.rank).isEqualTo(widgetToParcelize.rank)
+        assertThat(widgetFromParcel.spanY).isEqualTo(widgetToParcelize.spanY)
+        assertThat(widgetFromParcel.providerInfo.provider)
+            .isEqualTo(widgetToParcelize.providerInfo.provider)
+    }
+
+    @Test
+    fun testParcelizePendingWidget() {
+        val widgetToParcelize =
+            CommunalWidgetContentModel.Pending(
+                appWidgetId = 2,
+                rank = 3,
+                componentName = ComponentName("pkg", "cls"),
+                icon = null,
+                user = UserHandle(0),
+                spanY = 6,
+            )
+
+        val parcel = Parcel.obtain()
+        widgetToParcelize.writeToParcel(parcel, flags = 0)
+
+        parcel.setDataPosition(0)
+
+        val widgetFromParcel = CommunalWidgetContentModel.createFromParcel(parcel)
+        assertThat(widgetFromParcel).isEqualTo(widgetToParcelize)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
index 7816d3b..bea1010 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModelTest.kt
@@ -367,6 +367,39 @@
         }
 
     @Test
+    fun testMaxSpansLessThanCurrentSpan() =
+        testScope.runTest {
+            // heightPerSpan =
+            // (viewportHeightPx - verticalContentPaddingPx - (totalSpans - 1)
+            // * verticalItemSpacingPx) / totalSpans
+            // = 145.3333
+            // maxSpans = (maxHeightPx + verticalItemSpacing) /
+            // (heightPerSpanPx + verticalItemSpacingPx)
+            // = 4.72
+            // This is invalid because the max span calculation comes out to be less than
+            // the current span. Ensure we handle this case correctly.
+            val layout =
+                GridLayout(
+                    verticalItemSpacingPx = 100f,
+                    currentRow = 0,
+                    minHeightPx = 480,
+                    maxHeightPx = 1060,
+                    currentSpan = 6,
+                    resizeMultiple = 3,
+                    totalSpans = 6,
+                    viewportHeightPx = 1600,
+                    verticalContentPaddingPx = 228f,
+                )
+            updateGridLayout(layout)
+
+            val topState = underTest.topDragState
+            val bottomState = underTest.bottomDragState
+
+            assertThat(topState.anchors.toList()).containsExactly(0 to 0f)
+            assertThat(bottomState.anchors.toList()).containsExactly(0 to 0f, -3 to -736f)
+        }
+
+    @Test
     fun testCanExpand_atTopPosition_withMultipleAnchors_returnsTrue() =
         testScope.runTest {
             val twoRowGrid = singleSpanGrid.copy(totalSpans = 2, currentSpan = 1, currentRow = 0)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
index 1e79112..18513fc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -61,6 +62,7 @@
                 backgroundScope = kosmos.applicationCoroutineScope,
                 hostId = 116,
                 logBuffer = logcatLogBuffer("CommunalAppWidgetHostTest"),
+                glanceableHubMultiUserHelper = kosmos.fakeGlanceableHubMultiUserHelper,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index c9f3f14..9ef2b19 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -24,10 +24,14 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.shared.model.FakeGlanceableHubMultiUserHelper
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
@@ -58,6 +62,9 @@
     @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
     @Mock private lateinit var communalWidgetHost: CommunalWidgetHost
 
+    private lateinit var widgetManager: GlanceableHubWidgetManager
+    private lateinit var helper: FakeGlanceableHubMultiUserHelper
+
     private lateinit var appWidgetIdToRemove: MutableSharedFlow<Int>
 
     private lateinit var underTest: CommunalAppWidgetHostStartable
@@ -69,17 +76,23 @@
         kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
 
+        widgetManager = kosmos.mockGlanceableHubWidgetManager
+        helper = kosmos.fakeGlanceableHubMultiUserHelper
         appWidgetIdToRemove = MutableSharedFlow()
         whenever(appWidgetHost.appWidgetIdToRemove).thenReturn(appWidgetIdToRemove)
 
         underTest =
             CommunalAppWidgetHostStartable(
-                appWidgetHost,
-                communalWidgetHost,
-                kosmos.communalInteractor,
-                kosmos.fakeUserTracker,
+                { appWidgetHost },
+                { communalWidgetHost },
+                { kosmos.communalInteractor },
+                { kosmos.communalSettingsInteractor },
+                { kosmos.keyguardInteractor },
+                { kosmos.fakeUserTracker },
                 kosmos.applicationCoroutineScope,
                 kosmos.testDispatcher,
+                { widgetManager },
+                helper,
             )
     }
 
@@ -211,7 +224,7 @@
                 fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id)
                 fakeCommunalWidgetRepository.addPendingWidget(
                     appWidgetId = 2,
-                    userId = USER_INFO_WORK.id
+                    userId = USER_INFO_WORK.id,
                 )
                 fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id)
 
@@ -246,16 +259,42 @@
             }
         }
 
-    private suspend fun setCommunalAvailable(available: Boolean) =
+    @Test
+    fun onStartHeadlessSystemUser_registerWidgetManager_whenCommunalIsAvailable() =
+        with(kosmos) {
+            testScope.runTest {
+                helper.setIsInHeadlessSystemUser(true)
+                underTest.start()
+                runCurrent()
+                verify(widgetManager, never()).register()
+                verify(widgetManager, never()).unregister()
+
+                // Binding to the service does not require keyguard showing
+                setCommunalAvailable(true, setKeyguardShowing = false)
+                runCurrent()
+                verify(widgetManager).register()
+
+                setCommunalAvailable(false)
+                runCurrent()
+                verify(widgetManager).unregister()
+            }
+        }
+
+    private suspend fun setCommunalAvailable(
+        available: Boolean,
+        setKeyguardShowing: Boolean = true,
+    ) =
         with(kosmos) {
             fakeKeyguardRepository.setIsEncryptedOrLockdown(false)
             fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
-            fakeKeyguardRepository.setKeyguardShowing(true)
+            if (setKeyguardShowing) {
+                fakeKeyguardRepository.setKeyguardShowing(true)
+            }
             val settingsValue = if (available) 1 else 0
             fakeSettings.putIntForUser(
                 Settings.Secure.GLANCEABLE_HUB_ENABLED,
                 settingsValue,
-                MAIN_USER_INFO.id
+                MAIN_USER_INFO.id,
             )
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
index 054e516..017c778 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalWidgetHostTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.kosmos.applicationCoroutineScope
@@ -84,7 +85,7 @@
                     any<Int>(),
                     any<UserHandle>(),
                     any<ComponentName>(),
-                    any<Bundle>()
+                    any<Bundle>(),
                 )
             )
             .thenReturn(true)
@@ -96,6 +97,7 @@
                 appWidgetHost,
                 selectedUserInteractor,
                 logcatLogBuffer("CommunalWidgetHostTest"),
+                glanceableHubMultiUserHelper = kosmos.fakeGlanceableHubMultiUserHelper,
             )
     }
 
@@ -162,7 +164,7 @@
                         any<Int>(),
                         any<UserHandle>(),
                         any<ComponentName>(),
-                        any<Bundle>()
+                        any<Bundle>(),
                     )
                 )
                 .thenReturn(false)
@@ -283,12 +285,7 @@
             // all providers are emitted at once
             assertThat(providerInfoValues).hasSize(2)
             assertThat(providerInfoValues[1])
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
         }
 
     @Test
@@ -304,12 +301,7 @@
             // Assert that the provider info map is populated
             val providerInfo by collectLastValue(underTest.appWidgetProviders)
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
 
             // Host stop listening
             observer.onHostStopListening()
@@ -332,12 +324,7 @@
 
             // Assert that the provider info map is populated
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
 
             // Provider info for widget 1 updated
             val listener =
@@ -349,12 +336,7 @@
 
             // Assert that the update is reflected in the flow
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo3),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo3), Pair(2, providerInfo2)))
         }
 
     @Test
@@ -371,12 +353,7 @@
 
             // Assert that the provider info map is populated
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
 
             // Bind a new widget
             whenever(appWidgetHost.allocateAppWidgetId()).thenReturn(3)
@@ -388,11 +365,7 @@
             // Assert that the new provider is reflected in the flow
             assertThat(providerInfo)
                 .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                        Pair(3, providerInfo3),
-                    )
+                    mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2), Pair(3, providerInfo3))
                 )
         }
 
@@ -410,31 +383,21 @@
 
             // Assert that the provider info map is populated
             assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(1, providerInfo1),
-                        Pair(2, providerInfo2),
-                    )
-                )
+                .containsExactlyEntriesIn(mapOf(Pair(1, providerInfo1), Pair(2, providerInfo2)))
 
             // Remove widget 1
             observer.onDeleteAppWidgetId(1)
             runCurrent()
 
             // Assert that provider info for widget 1 is removed
-            assertThat(providerInfo)
-                .containsExactlyEntriesIn(
-                    mapOf(
-                        Pair(2, providerInfo2),
-                    )
-                )
+            assertThat(providerInfo).containsExactlyEntriesIn(mapOf(Pair(2, providerInfo2)))
         }
 
     private fun selectUser() {
         kosmos.fakeUserRepository.selectedUser.value =
             SelectedUserModel(
                 userInfo = UserInfo(0, "Current user", 0),
-                selectionStatus = SelectionStatus.SELECTION_COMPLETE
+                selectionStatus = SelectionStatus.SELECTION_COMPLETE,
             )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
new file mode 100644
index 0000000..44ce085
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
@@ -0,0 +1,319 @@
+/*
+ * 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.communal.widgets
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.Intent
+import android.os.UserHandle
+import android.testing.TestableLooper
+import android.widget.RemoteViews
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val appWidgetHostListenerCaptor = argumentCaptor<AppWidgetHost.AppWidgetHostListener>()
+
+    private val widgetRepository = kosmos.fakeCommunalWidgetRepository
+    private val appWidgetHost = mock<CommunalAppWidgetHost>()
+    private val communalWidgetHost = mock<CommunalWidgetHost>()
+    private val multiUserHelper = kosmos.fakeGlanceableHubMultiUserHelper
+
+    private lateinit var underTest: GlanceableHubWidgetManagerService
+
+    @Before
+    fun setup() {
+        underTest =
+            GlanceableHubWidgetManagerService(
+                widgetRepository,
+                appWidgetHost,
+                communalWidgetHost,
+                multiUserHelper,
+                logcatLogBuffer("GlanceableHubWidgetManagerServiceTest"),
+            )
+    }
+
+    @Test
+    fun appWidgetHost_listenWhenServiceIsBound() {
+        underTest.onCreate()
+        verify(appWidgetHost).startListening()
+        verify(communalWidgetHost).startObservingHost()
+        verify(appWidgetHost, never()).stopListening()
+        verify(communalWidgetHost, never()).stopObservingHost()
+
+        underTest.onDestroy()
+        verify(appWidgetHost).stopListening()
+        verify(communalWidgetHost).stopObservingHost()
+    }
+
+    @Test
+    fun widgetsListener_getWidgetUpdates() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    @Test
+    fun widgetsListener_multipleListeners_eachGetsWidgetUpdates() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update for the first listener is as expected
+            val widgets1 by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets1).hasSize(3)
+            assertThat(widgets1?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets1?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets1?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Verify the update for the second listener is as expected
+            val widgets2 by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets2).hasSize(3)
+            assertThat(widgets2?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets2?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets2?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    @Test
+    fun setAppWidgetHostListener_getUpdates() =
+        testScope.runTest {
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Set listener
+            val listener = mock<IGlanceableHubWidgetManagerService.IAppWidgetHostListener>()
+            service.setAppWidgetHostListener(1, listener)
+
+            // Verify a listener is set on the host
+            verify(appWidgetHost).setListener(eq(1), appWidgetHostListenerCaptor.capture())
+            val appWidgetHostListener = appWidgetHostListenerCaptor.firstValue
+
+            // Each update should be passed to the listener
+            val providerInfo = mock<AppWidgetProviderInfo>()
+            appWidgetHostListener.onUpdateProviderInfo(providerInfo)
+            verify(listener).onUpdateProviderInfo(providerInfo)
+
+            val remoteViews = mock<RemoteViews>()
+            appWidgetHostListener.updateAppWidget(remoteViews)
+            verify(listener).updateAppWidget(remoteViews)
+
+            appWidgetHostListener.updateAppWidgetDeferred("pkg", 1)
+            verify(listener).updateAppWidgetDeferred("pkg", 1)
+
+            appWidgetHostListener.onViewDataChanged(1)
+            verify(listener).onViewDataChanged(1)
+        }
+
+    @Test
+    fun addWidget_getWidgetUpdate() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Add a widget
+            service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3)
+            runCurrent()
+
+            // Verify an update pushed with widget 4 added
+            assertThat(widgets).hasSize(4)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+            assertThat(widgets?.get(3)?.has(4, "pkg_4/cls_4", 3, 3)).isTrue()
+        }
+
+    @Test
+    fun deleteWidget_getWidgetUpdate() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Delete a widget
+            service.deleteWidget(1)
+            runCurrent()
+
+            // Verify an update pushed with widget 1 removed
+            assertThat(widgets).hasSize(2)
+            assertThat(widgets?.get(0)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    @Test
+    fun updateWidgetOrder_getWidgetUpdate() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Update widget order
+            service.updateWidgetOrder(intArrayOf(1, 2, 3), intArrayOf(2, 1, 0))
+            runCurrent()
+
+            // Verify an update pushed with the new order
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(3, "pkg_3/cls_3", 0, 6)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(1, "pkg_1/cls_1", 2, 3)).isTrue()
+        }
+
+    @Test
+    fun resizeWidget_getWidgetUpdate() =
+        testScope.runTest {
+            setupWidgets()
+
+            // Bind service
+            val binder = underTest.onBind(Intent())
+            val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+            // Verify the update is as expected
+            val widgets by collectLastValue(service.listenForWidgetUpdates())
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+            // Resize widget 1 from spanY 3 to 6
+            service.resizeWidget(1, 6, intArrayOf(1, 2, 3), intArrayOf(0, 1, 2))
+            runCurrent()
+
+            // Verify an update pushed with the new size for widget 1
+            assertThat(widgets).hasSize(3)
+            assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 6)).isTrue()
+            assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+            assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+        }
+
+    private fun setupWidgets() {
+        widgetRepository.addWidget(
+            appWidgetId = 1,
+            componentName = "pkg_1/cls_1",
+            rank = 0,
+            spanY = 3,
+        )
+        widgetRepository.addWidget(
+            appWidgetId = 2,
+            componentName = "pkg_2/cls_2",
+            rank = 1,
+            spanY = 3,
+        )
+        widgetRepository.addWidget(
+            appWidgetId = 3,
+            componentName = "pkg_3/cls_3",
+            rank = 2,
+            spanY = 6,
+        )
+    }
+
+    private fun IGlanceableHubWidgetManagerService.listenForWidgetUpdates() =
+        conflatedCallbackFlow<List<CommunalWidgetContentModel>> {
+            val listener =
+                object : IGlanceableHubWidgetsListener.Stub() {
+                    override fun onWidgetsUpdated(widgets: List<CommunalWidgetContentModel>) {
+                        trySend(widgets)
+                    }
+                }
+            addWidgetsListener(listener)
+            awaitClose { removeWidgetsListener(listener) }
+        }
+
+    private fun CommunalWidgetContentModel.has(
+        appWidgetId: Int,
+        componentName: String,
+        rank: Int,
+        spanY: Int,
+    ): Boolean {
+        return this is CommunalWidgetContentModel.Available &&
+            this.appWidgetId == appWidgetId &&
+            this.providerInfo.provider.flattenToString() == componentName &&
+            this.rank == rank &&
+            this.spanY == spanY
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
index 55fafdf..5d4eaf0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
@@ -55,7 +56,12 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         underTest =
-            WidgetConfigurationController(ownerActivity, appWidgetHost, kosmos.testDispatcher)
+            WidgetConfigurationController(
+                ownerActivity,
+                { appWidgetHost },
+                kosmos.testDispatcher,
+                kosmos.fakeGlanceableHubMultiUserHelper,
+            )
     }
 
     @Test
@@ -68,7 +74,7 @@
                             eq(123),
                             anyInt(),
                             eq(WidgetConfigurationController.REQUEST_CODE),
-                            any()
+                            any(),
                         )
                     )
                     .thenThrow(ActivityNotFoundException())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
index ed45e8c..ef02817 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamStartableTest.kt
@@ -23,88 +23,86 @@
 import android.content.pm.UserInfo
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.service.controls.flags.Flags.FLAG_HOME_PANEL_DREAM
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_HOME_CONTROLS_DREAM_HSUM
+import com.android.systemui.Flags.homeControlsDreamHsum
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
-import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.controls.management.ControlsListingController
-import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.panels.SelectedComponentRepository
 import com.android.systemui.controls.panels.authorizedPanelsRepository
 import com.android.systemui.controls.panels.selectedComponentRepository
 import com.android.systemui.dreams.homecontrols.system.HomeControlsDreamStartable
-import com.android.systemui.dreams.homecontrols.system.domain.interactor.HomeControlsComponentInteractor
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
 import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.settings.userTracker
 import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
 import java.util.Optional
+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
-import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class HomeControlsDreamStartableTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class HomeControlsDreamStartableTest(flags: FlagsParameterization) : SysuiTestCase() {
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
 
     private val kosmos = testKosmos()
-
-    @Mock private lateinit var packageManager: PackageManager
-
-    private lateinit var homeControlsComponentInteractor: HomeControlsComponentInteractor
-    private lateinit var selectedComponentRepository: SelectedComponentRepository
-    private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
-    private lateinit var userRepository: FakeUserRepository
-    private lateinit var controlsComponent: ControlsComponent
-    private lateinit var controlsListingController: ControlsListingController
-
-    private lateinit var startable: HomeControlsDreamStartable
-    private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
     private val testScope = kosmos.testScope
 
+    private val systemUserPackageManager = mock<PackageManager>()
+    private val userPackageManager = mock<PackageManager>()
+
+    private val selectedComponentRepository = kosmos.selectedComponentRepository
+    private val userRepository =
+        kosmos.fakeUserRepository.apply { setUserInfos(listOf(PRIMARY_USER)) }
+    private val controlsListingController =
+        kosmos.controlsListingController.stub {
+            on { getCurrentServices() } doReturn
+                listOf(buildControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+        }
+    private val controlsComponent =
+        kosmos.controlsComponent.stub {
+            on { getControlsListingController() } doReturn Optional.of(controlsListingController)
+        }
+
+    private val underTest by lazy {
+        HomeControlsDreamStartable(
+            mContext,
+            systemUserPackageManager,
+            kosmos.userTracker,
+            kosmos.homeControlsComponentInteractor,
+            kosmos.applicationCoroutineScope,
+        )
+    }
+    private val componentName = ComponentName(context, HomeControlsDreamService::class.java)
+
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        selectedComponentRepository = kosmos.selectedComponentRepository
-        authorizedPanelsRepository = kosmos.authorizedPanelsRepository
-        userRepository = kosmos.fakeUserRepository
-        controlsComponent = kosmos.controlsComponent
-        controlsListingController = kosmos.controlsListingController
-
-        userRepository.setUserInfos(listOf(PRIMARY_USER))
-
-        authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE_PANEL))
-
+        kosmos.authorizedPanelsRepository.addAuthorizedPanels(setOf(TEST_PACKAGE_PANEL))
         whenever(controlsComponent.getControlsListingController())
             .thenReturn(Optional.of(controlsListingController))
-        whenever(controlsListingController.getCurrentServices())
-            .thenReturn(listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true)))
-
-        homeControlsComponentInteractor = kosmos.homeControlsComponentInteractor
-
-        startable =
-            HomeControlsDreamStartable(
-                mContext,
-                packageManager,
-                kosmos.userTracker,
-                homeControlsComponentInteractor,
-                kosmos.applicationCoroutineScope,
-            )
+        whenever(kosmos.fakeUserTracker.userContext.packageManager).thenReturn(userPackageManager)
     }
 
     @Test
@@ -113,13 +111,19 @@
         testScope.runTest {
             userRepository.setSelectedUserInfo(PRIMARY_USER)
             selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_PANEL)
-            startable.start()
+            underTest.start()
             runCurrent()
+            val packageManager =
+                if (homeControlsDreamHsum()) {
+                    userPackageManager
+                } else {
+                    systemUserPackageManager
+                }
             verify(packageManager)
                 .setComponentEnabledSetting(
-                    eq(componentName),
-                    eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
-                    eq(PackageManager.DONT_KILL_APP),
+                    componentName,
+                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                    PackageManager.DONT_KILL_APP,
                 )
         }
 
@@ -128,13 +132,19 @@
     fun testStartDisablesHomeControlsDreamServiceWhenPanelComponentIsNull() =
         testScope.runTest {
             selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
-            startable.start()
+            underTest.start()
             runCurrent()
+            val packageManager =
+                if (homeControlsDreamHsum()) {
+                    userPackageManager
+                } else {
+                    systemUserPackageManager
+                }
             verify(packageManager)
                 .setComponentEnabledSetting(
-                    eq(componentName),
-                    eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
-                    eq(PackageManager.DONT_KILL_APP),
+                    componentName,
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP,
                 )
         }
 
@@ -143,8 +153,14 @@
     fun testStartDisablesDreamServiceWhenFlagIsDisabled() =
         testScope.runTest {
             selectedComponentRepository.setSelectedComponent(TEST_SELECTED_COMPONENT_NON_PANEL)
-            startable.start()
+            underTest.start()
             runCurrent()
+            val packageManager =
+                if (homeControlsDreamHsum()) {
+                    userPackageManager
+                } else {
+                    systemUserPackageManager
+                }
             verify(packageManager)
                 .setComponentEnabledSetting(
                     eq(componentName),
@@ -153,7 +169,7 @@
                 )
         }
 
-    private fun ControlsServiceInfo(
+    private fun buildControlsServiceInfo(
         componentName: ComponentName,
         label: CharSequence,
         hasPanel: Boolean,
@@ -186,6 +202,10 @@
     }
 
     companion object {
+        @get:Parameters(name = "{0}")
+        @JvmStatic
+        val params = FlagsParameterization.allCombinationsOf(FLAG_HOME_CONTROLS_DREAM_HSUM)
+
         private const val PRIMARY_USER_ID = 0
         private val PRIMARY_USER =
             UserInfo(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
index 9e20e7d..620b8b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
@@ -16,7 +16,11 @@
 
 package com.android.systemui.keyboard.shortcut.data.repository
 
+import android.graphics.drawable.Drawable
+import android.hardware.input.KeyGlyphMap
 import android.hardware.input.fakeInputManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.view.KeyEvent.KEYCODE_1
 import android.view.KeyEvent.KEYCODE_A
 import android.view.KeyEvent.KEYCODE_B
@@ -26,10 +30,12 @@
 import android.view.KeyEvent.KEYCODE_F
 import android.view.KeyEvent.KEYCODE_G
 import android.view.KeyEvent.META_FUNCTION_ON
+import android.view.KeyEvent.META_META_ON
 import android.view.KeyboardShortcutGroup
 import android.view.KeyboardShortcutInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_SHORTCUT_HELPER_KEY_GLYPH
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.keyboard.shortcut.data.source.FakeKeyboardShortcutGroupsSource
@@ -49,6 +55,7 @@
 import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,6 +64,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -187,6 +197,79 @@
                 )
         }
 
+    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    @Test
+    fun modifierMappedToCustomDrawableWhenKeyGlyphMapExists() =
+        testScope.runTest {
+            val metaDrawable = mock(Drawable::class.java)
+            val keyGlyph = mock(KeyGlyphMap::class.java)
+            whenever(keyGlyph.getDrawableForModifierState(context, META_META_ON))
+                .thenReturn(metaDrawable)
+            whenever(kosmos.fakeInputManager.inputManager.getKeyGlyphMap(anyInt()))
+                .thenReturn(keyGlyph)
+            fakeSystemSource.setGroups(simpleGroup(simpleShortcutInfo(KEYCODE_1, META_META_ON)))
+            helper.toggle(deviceId = 123)
+
+            val categories by collectLastValue(repo.categories)
+            val systemCategory = categories?.firstOrNull { it.type == ShortcutCategoryType.System }
+
+            val expectedCategory =
+                ShortcutCategory(
+                    type = ShortcutCategoryType.System,
+                    simpleSubCategory(
+                        simpleDrawableModifierShortcut("1", modifierDrawable = metaDrawable)
+                    ),
+                )
+
+            assertThat(systemCategory).isEqualTo(expectedCategory)
+        }
+
+    @EnableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    @Test
+    fun modifierMappedToDefaultDrawableWhenNoKeyGlyphMapExists() =
+        testScope.runTest {
+            fakeSystemSource.setGroups(simpleGroup(simpleShortcutInfo(KEYCODE_1, META_META_ON)))
+            helper.toggle(deviceId = 123)
+
+            val categories by collectLastValue(repo.categories)
+            val systemCategory = categories?.firstOrNull { it.type == ShortcutCategoryType.System }
+
+            val expectedCategory =
+                ShortcutCategory(
+                    type = ShortcutCategoryType.System,
+                    simpleSubCategory(
+                        simpleResIdModifierShortcut("1", modifierResId = R.drawable.ic_ksh_key_meta)
+                    ),
+                )
+            assertThat(systemCategory).isEqualTo(expectedCategory)
+        }
+
+    @DisableFlags(FLAG_SHORTCUT_HELPER_KEY_GLYPH)
+    @Test
+    fun modifierMappedToDefaultDrawableWhenKeyGlyphDisabled() =
+        testScope.runTest {
+            val metaDrawable = mock(Drawable::class.java)
+            val keyGlyph = mock(KeyGlyphMap::class.java)
+            whenever(keyGlyph.getDrawableForModifierState(context, META_META_ON))
+                .thenReturn(metaDrawable)
+            whenever(kosmos.fakeInputManager.inputManager.getKeyGlyphMap(anyInt()))
+                .thenReturn(keyGlyph)
+            fakeSystemSource.setGroups(simpleGroup(simpleShortcutInfo(KEYCODE_1, META_META_ON)))
+            helper.toggle(deviceId = 123)
+
+            val categories by collectLastValue(repo.categories)
+            val systemCategory = categories?.firstOrNull { it.type == ShortcutCategoryType.System }
+
+            val expectedCategory =
+                ShortcutCategory(
+                    type = ShortcutCategoryType.System,
+                    simpleSubCategory(
+                        simpleResIdModifierShortcut("1", modifierResId = R.drawable.ic_ksh_key_meta)
+                    ),
+                )
+            assertThat(systemCategory).isEqualTo(expectedCategory)
+        }
+
     private fun simpleSubCategory(vararg shortcuts: Shortcut) =
         ShortcutSubCategory(simpleGroupLabel, shortcuts.asList())
 
@@ -196,6 +279,37 @@
             commands = listOf(ShortcutCommand(keys.map { ShortcutKey.Text(it) })),
         )
 
+    private fun simpleDrawableModifierShortcut(
+        vararg keys: String,
+        modifierDrawable: Drawable,
+    ): Shortcut {
+        val keyShortcuts = keys.map { ShortcutKey.Text(it) }
+        return Shortcut(
+            label = simpleShortcutLabel,
+            commands =
+                listOf(
+                    ShortcutCommand(
+                        listOf(ShortcutKey.Icon.DrawableIcon(drawable = modifierDrawable)) +
+                            keyShortcuts
+                    )
+                ),
+        )
+    }
+
+    private fun simpleResIdModifierShortcut(vararg keys: String, modifierResId: Int): Shortcut {
+        val keyShortcuts = keys.map { ShortcutKey.Text(it) }
+        return Shortcut(
+            label = simpleShortcutLabel,
+            commands =
+                listOf(
+                    ShortcutCommand(
+                        listOf(ShortcutKey.Icon.ResIdIcon(drawableResId = modifierResId)) +
+                            keyShortcuts
+                    )
+                ),
+        )
+    }
+
     private fun simpleGroup(vararg shortcuts: KeyboardShortcutInfo) =
         KeyboardShortcutGroup(simpleGroupLabel, shortcuts.asList())
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 9ca3ce6..e9e3e1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -81,7 +81,7 @@
             this.fakeKeyguardTransitionRepository =
                 FakeKeyguardTransitionRepository(
                     // This test sends transition steps manually in the test cases.
-                    sendTransitionStepsOnStartTransition = false,
+                    initiallySendTransitionStepsOnStartTransition = false,
                     testScope = testScope,
                 )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
index 9c2e631..b29a5f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -27,15 +27,13 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepositorySpy
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
-import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.shade.data.repository.FlingInfo
 import com.android.systemui.shade.data.repository.fakeShadeRepository
@@ -48,6 +46,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.reset
+import org.mockito.Mockito.spy
+import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat as assertThatRepository
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -55,7 +55,9 @@
 class FromLockscreenTransitionInteractorTest : SysuiTestCase() {
     private val kosmos =
         testKosmos().apply {
-            this.keyguardTransitionRepository = fakeKeyguardTransitionRepositorySpy
+            this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository(
+                testScope = testScope,
+            ))
         }
 
     private val testScope = kosmos.testScope
@@ -66,7 +68,7 @@
 
     @Before
     fun setup() {
-        transitionRepository = kosmos.fakeKeyguardTransitionRepositorySpy
+        transitionRepository = kosmos.fakeKeyguardTransitionRepository
     }
 
     @Test
@@ -302,4 +304,74 @@
                     to = KeyguardState.LOCKSCREEN,
                 )
         }
+
+    /**
+     * External signals can cause us to transition from PRIMARY_BOUNCER -> * while a manual
+     * transition is in progress. This test was added after a bug that caused the manual transition
+     * ID to get stuck in this scenario, preventing subsequent transitions to PRIMARY_BOUNCER.
+     */
+    @Test
+    fun testExternalTransitionAwayFromBouncer_transitionIdNotStuck() =
+        testScope.runTest {
+            underTest.start()
+            keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD)
+            keyguardRepository.setKeyguardDismissible(false)
+            shadeRepository.setLegacyShadeTracking(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            reset(transitionRepository)
+
+            // Disable automatic sending of transition steps so we can send steps through RUNNING
+            // to simulate a cancellation.
+            transitionRepository.sendTransitionStepsOnStartTransition = false
+            shadeRepository.setLegacyShadeExpansion(0.5f)
+            runCurrent()
+
+            assertThatRepository(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                )
+
+            // Partially transition to PRIMARY_BOUNCER.
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.PRIMARY_BOUNCER,
+                throughTransitionState = TransitionState.RUNNING,
+                testScope = testScope,
+            )
+
+            // Start a transition to GONE, which will cancel LS -> BOUNCER.
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.PRIMARY_BOUNCER,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+
+            // Go to AOD, then LOCKSCREEN.
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.GONE,
+                to = KeyguardState.AOD,
+                testScope = testScope,
+            )
+            transitionRepository.sendTransitionSteps(
+                from = KeyguardState.AOD,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
+
+            reset(transitionRepository)
+
+            // Start a swipe up to the bouncer, and verify that we started a transition to
+            // PRIMARY_BOUNCER, verifying the transition ID did not get stuck.
+            shadeRepository.setLegacyShadeExpansion(0.25f)
+            runCurrent()
+
+            assertThatRepository(transitionRepository)
+                .startedTransition(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                )
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 2905a73..646722b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -116,6 +116,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.LightBarController;
@@ -208,7 +209,7 @@
     @Mock
     private LightBarController mLightBarController;
     @Mock
-    private LightBarController.Factory mLightBarcontrollerFactory;
+    private LightBarControllerStore mLightBarControllerStore;
     @Mock
     private AutoHideController mAutoHideController;
     @Mock
@@ -257,7 +258,7 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
+        when(mLightBarControllerStore.forDisplay(anyInt())).thenReturn(mLightBarController);
         when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
         when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
         when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
@@ -649,8 +650,7 @@
                 mFakeExecutor,
                 mUiEventLogger,
                 mNavBarHelper,
-                mLightBarController,
-                mLightBarcontrollerFactory,
+                mLightBarControllerStore,
                 mAutoHideController,
                 mAutoHideControllerFactory,
                 Optional.of(mTelecomManager),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
index 2e2894d..b5fc52f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/AbstractQSFragmentComposeViewModelTest.kt
@@ -24,12 +24,16 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.media.controls.domain.pipeline.legacyMediaDataManagerImpl
+import com.android.systemui.media.controls.domain.pipeline.mediaDataManager
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
 import com.android.systemui.testKosmos
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestResult
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.test.setMain
 import org.junit.After
@@ -39,7 +43,7 @@
 @RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 abstract class AbstractQSFragmentComposeViewModelTest : SysuiTestCase() {
-    protected val kosmos = testKosmos()
+    protected val kosmos = testKosmos().apply { mediaDataManager = legacyMediaDataManagerImpl }
 
     protected val lifecycleOwner =
         TestLifecycleOwner(
@@ -62,11 +66,15 @@
     }
 
     protected inline fun TestScope.testWithinLifecycle(
-        crossinline block: suspend TestScope.() -> TestResult
+        usingMedia: Boolean = true,
+        crossinline block: suspend TestScope.() -> TestResult,
     ): TestResult {
         return runTest {
+            kosmos.usingMediaInComposeFragment = usingMedia
+
             lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
             underTest.activateIn(kosmos.testScope)
+            runCurrent()
             block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
         }
     }
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 3b00f86..9fe9ed2 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
@@ -25,6 +25,15 @@
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.domain.pipeline.legacyMediaDataManagerImpl
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.controls.ui.view.qqsMediaHost
+import com.android.systemui.media.controls.ui.view.qsMediaHost
+import com.android.systemui.qs.composefragment.viewmodel.MediaState.ACTIVE_MEDIA
+import com.android.systemui.qs.composefragment.viewmodel.MediaState.ANY_MEDIA
+import com.android.systemui.qs.composefragment.viewmodel.MediaState.NO_MEDIA
 import com.android.systemui.qs.fgsManagerController
 import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
 import com.android.systemui.res.R
@@ -35,9 +44,11 @@
 import com.android.systemui.statusbar.sysuiStatusBarStateController
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -185,6 +196,92 @@
             }
         }
 
+    @Test
+    fun qqsMediaHost_initializedCorrectly() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                assertThat(underTest.qqsMediaHost.location)
+                    .isEqualTo(MediaHierarchyManager.LOCATION_QQS)
+                assertThat(underTest.qqsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+                assertThat(underTest.qqsMediaHost.showsOnlyActiveMedia).isTrue()
+                assertThat(underTest.qqsMediaHost.hostView).isNotNull()
+            }
+        }
+
+    @Test
+    fun qsMediaHost_initializedCorrectly() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                assertThat(underTest.qsMediaHost.location)
+                    .isEqualTo(MediaHierarchyManager.LOCATION_QS)
+                assertThat(underTest.qsMediaHost.expansion).isEqualTo(MediaHostState.EXPANDED)
+                assertThat(underTest.qsMediaHost.showsOnlyActiveMedia).isFalse()
+                assertThat(underTest.qsMediaHost.hostView).isNotNull()
+            }
+        }
+
+    @Test
+    fun qqsMediaVisible_onlyWhenActiveMedia() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                whenever(mediaCarouselController.isLockedAndHidden()).thenReturn(false)
+
+                assertThat(underTest.qqsMediaVisible).isEqualTo(underTest.qqsMediaHost.visible)
+
+                setMediaState(NO_MEDIA)
+                assertThat(underTest.qqsMediaVisible).isFalse()
+
+                setMediaState(ANY_MEDIA)
+                assertThat(underTest.qqsMediaVisible).isFalse()
+
+                setMediaState(ACTIVE_MEDIA)
+                assertThat(underTest.qqsMediaVisible).isTrue()
+            }
+        }
+
+    @Test
+    fun qsMediaVisible_onAnyMedia() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                whenever(mediaCarouselController.isLockedAndHidden()).thenReturn(false)
+
+                assertThat(underTest.qsMediaVisible).isEqualTo(underTest.qsMediaHost.visible)
+
+                setMediaState(NO_MEDIA)
+                assertThat(underTest.qsMediaVisible).isFalse()
+
+                setMediaState(ANY_MEDIA)
+                assertThat(underTest.qsMediaVisible).isTrue()
+
+                setMediaState(ACTIVE_MEDIA)
+                assertThat(underTest.qsMediaVisible).isTrue()
+            }
+        }
+
+    @Test
+    fun notUsingMedia_mediaNotVisible() =
+        with(kosmos) {
+            testScope.testWithinLifecycle(usingMedia = false) {
+                setMediaState(ACTIVE_MEDIA)
+
+                assertThat(underTest.qqsMediaVisible).isFalse()
+                assertThat(underTest.qsMediaVisible).isFalse()
+            }
+        }
+
+    private fun TestScope.setMediaState(state: MediaState) {
+        with(kosmos) {
+            val activeMedia = state == ACTIVE_MEDIA
+            val anyMedia = state != NO_MEDIA
+            whenever(legacyMediaDataManagerImpl.hasActiveMediaOrRecommendation())
+                .thenReturn(activeMedia)
+            whenever(legacyMediaDataManagerImpl.hasAnyMediaOrRecommendation()).thenReturn(anyMedia)
+            qqsMediaHost.updateViewVisibility()
+            qsMediaHost.updateViewVisibility()
+        }
+        runCurrent()
+    }
+
     companion object {
         private const val QS_DISABLE_FLAG = StatusBarManager.DISABLE2_QUICK_SETTINGS
 
@@ -195,3 +292,9 @@
         private const val epsilon = 0.001f
     }
 }
+
+private enum class MediaState {
+    ACTIVE_MEDIA,
+    ANY_MEDIA,
+    NO_MEDIA,
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
index 2c894f9..ab5a049 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt
@@ -20,21 +20,25 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-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.lifecycle.activateIn
 import com.android.systemui.qs.panels.domain.interactor.qsPreferencesInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class QuickQuickSettingsViewModelTest : SysuiTestCase() {
@@ -65,7 +69,8 @@
             fakeConfigurationRepository.onConfigurationChange()
         }
 
-    private val underTest = kosmos.quickQuickSettingsViewModel
+    private val underTest =
+        kosmos.quickQuickSettingsViewModelFactory.create().apply { activateIn(kosmos.testScope) }
 
     @Before
     fun setUp() {
@@ -77,17 +82,15 @@
         with(kosmos) {
             testScope.runTest {
                 setRows(2)
-                val columns by collectLastValue(underTest.columns)
-                val tileViewModels by collectLastValue(underTest.tileViewModels)
 
-                assertThat(columns).isEqualTo(4)
+                assertThat(underTest.columns).isEqualTo(4)
                 // All tiles in 4 columns
                 // [1] [2] [3 3]
                 // [4] [5 5]
                 // [6 6] [7] [8]
                 // [9 9]
 
-                assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(5))
+                assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(5))
             }
         }
 
@@ -96,10 +99,8 @@
         with(kosmos) {
             testScope.runTest {
                 setRows(2)
-                val columns by collectLastValue(underTest.columns)
-                val tileViewModels by collectLastValue(underTest.tileViewModels)
 
-                assertThat(columns).isEqualTo(4)
+                assertThat(underTest.columns).isEqualTo(4)
                 // All tiles in 4 columns
                 // [1] [2] [3 3]
                 // [4] [5 5]
@@ -107,9 +108,9 @@
                 // [9 9]
 
                 setRows(3)
-                assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(8))
+                assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(8))
                 setRows(1)
-                assertThat(tileViewModels!!.map { it.tile.spec }).isEqualTo(tiles.take(3))
+                assertThat(underTest.tileViewModels.map { it.tile.spec }).isEqualTo(tiles.take(3))
             }
         }
 
@@ -118,10 +119,8 @@
         with(kosmos) {
             testScope.runTest {
                 setRows(2)
-                val columns by collectLastValue(underTest.columns)
-                val tileViewModels by collectLastValue(underTest.tileViewModels)
 
-                assertThat(columns).isEqualTo(4)
+                assertThat(underTest.columns).isEqualTo(4)
                 // All tiles in 4 columns
                 // [1] [2] [3 3]
                 // [4] [5 5]
@@ -130,8 +129,9 @@
 
                 // Remove tile small:4
                 currentTilesInteractor.removeTiles(setOf(tiles[3]))
+                runCurrent()
 
-                assertThat(tileViewModels!!.map { it.tile.spec })
+                assertThat(underTest.tileViewModels.map { it.tile.spec })
                     .isEqualTo(
                         listOf(
                                 "$PREFIX_SMALL:1",
@@ -149,12 +149,15 @@
         currentTilesInteractor.setTiles(tiles)
     }
 
-    private fun Kosmos.setRows(rows: Int) {
-        testCase.context.orCreateTestableResources.addOverride(
-            R.integer.quick_qs_paginated_grid_num_rows,
-            rows,
-        )
-        fakeConfigurationRepository.onConfigurationChange()
+    private fun TestScope.setRows(rows: Int) {
+        with(kosmos) {
+            testCase.context.orCreateTestableResources.addOverride(
+                R.integer.quick_qs_paginated_grid_num_rows,
+                rows,
+            )
+            fakeConfigurationRepository.onConfigurationChange()
+        }
+        runCurrent()
     }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index b632a8a..af895c8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -21,6 +21,8 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_OUTSIDE
 import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -43,6 +45,7 @@
 import com.android.systemui.shade.domain.interactor.shadeInteractor
 import com.android.systemui.shade.shared.flag.DualShade
 import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
 import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -68,6 +71,7 @@
     private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource }
     private val fakeShadeRepository by lazy { kosmos.fakeShadeRepository }
     private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+    private val fakeRemoteInputRepository by lazy { kosmos.fakeRemoteInputRepository }
     private val falsingManager by lazy { kosmos.fakeFalsingManager }
     private val view = mock<View>()
 
@@ -234,6 +238,35 @@
         }
 
     @Test
+    fun userInputOnEmptySpace_insideEvent() =
+        testScope.runTest {
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+            val insideMotionEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0f, 0f, 0)
+            underTest.onEmptySpaceMotionEvent(insideMotionEvent)
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+        }
+
+    @Test
+    fun userInputOnEmptySpace_outsideEvent_remoteInputActive() =
+        testScope.runTest {
+            fakeRemoteInputRepository.isRemoteInputActive.value = true
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+            val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
+            underTest.onEmptySpaceMotionEvent(outsideMotionEvent)
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isTrue()
+        }
+
+    @Test
+    fun userInputOnEmptySpace_outsideEvent_remoteInputInactive() =
+        testScope.runTest {
+            fakeRemoteInputRepository.isRemoteInputActive.value = false
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+            val outsideMotionEvent = MotionEvent.obtain(0, 0, ACTION_OUTSIDE, 0f, 0f, 0)
+            underTest.onEmptySpaceMotionEvent(outsideMotionEvent)
+            assertThat(fakeRemoteInputRepository.areRemoteInputsClosed).isFalse()
+        }
+
+    @Test
     fun remoteUserInteraction_keepsContainerVisible() =
         testScope.runTest {
             sceneInteractor.setVisible(false, "reason")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 59d0d70..041d1a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -238,7 +238,7 @@
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 mock(BouncerViewBinder::class.java),
-                mock(ConfigurationForwarder::class.java),
+                { mock(ConfigurationForwarder::class.java) },
                 brightnessMirrorShowingInteractor,
             )
         underTest.setupExpandedStatusBar()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 9b91fc7..5d1ce7c5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -203,7 +203,7 @@
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 mock(),
-                configurationForwarder,
+                { configurationForwarder },
                 brightnessMirrorShowingInteractor,
             )
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 022825a..f4a43a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -22,19 +22,20 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.PluginLifecycleManager
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.plugins.PluginManager
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.plugins.clocks.ClockFontAxisSetting
 import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockPickerConfig
 import com.android.systemui.plugins.clocks.ClockProviderPlugin
 import com.android.systemui.plugins.clocks.ClockSettings
-import com.android.systemui.plugins.PluginLifecycleManager
-import com.android.systemui.plugins.PluginListener
-import com.android.systemui.plugins.PluginManager
+import com.android.systemui.util.ThreadAssert
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.ThreadAssert
 import java.util.function.BiConsumer
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.fail
@@ -42,6 +43,8 @@
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
+import org.json.JSONArray
+import org.json.JSONObject
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -81,28 +84,32 @@
             return null!!
         }
 
-        private fun failPickerConfig(clockId: ClockId): ClockPickerConfig {
-            fail("Unexpected call to getClockPickerConfig: $clockId")
+        private fun failPickerConfig(settings: ClockSettings): ClockPickerConfig {
+            fail("Unexpected call to getClockPickerConfig: ${settings.clockId}")
             return null!!
         }
     }
 
-    private class FakeLifecycle(
-        private val tag: String,
-        private val plugin: ClockProviderPlugin?,
-    ) : PluginLifecycleManager<ClockProviderPlugin> {
+    private class FakeLifecycle(private val tag: String, private val plugin: ClockProviderPlugin?) :
+        PluginLifecycleManager<ClockProviderPlugin> {
         var onLoad: (() -> Unit)? = null
         var onUnload: (() -> Unit)? = null
 
         private var mIsLoaded: Boolean = true
+
         override fun isLoaded() = mIsLoaded
+
         override fun getPlugin(): ClockProviderPlugin? = if (isLoaded) plugin else null
 
         var mComponentName = ComponentName("Package[$tag]", "Class[$tag]")
+
         override fun toString() = "Manager[$tag]"
+
         override fun getPackage(): String = mComponentName.getPackageName()
+
         override fun getComponentName(): ComponentName = mComponentName
-        override fun setLogFunc(func: BiConsumer<String, String>) { }
+
+        override fun setLogFunc(func: BiConsumer<String, String>) {}
 
         override fun loadPlugin() {
             if (!mIsLoaded) {
@@ -122,7 +129,7 @@
     private class FakeClockPlugin : ClockProviderPlugin {
         private val metadata = mutableListOf<ClockMetadata>()
         private val createCallbacks = mutableMapOf<ClockId, (ClockId) -> ClockController>()
-        private val pickerConfigs = mutableMapOf<ClockId, (ClockId) -> ClockPickerConfig>()
+        private val pickerConfigs = mutableMapOf<ClockId, (ClockSettings) -> ClockPickerConfig>()
 
         override fun getClocks() = metadata
 
@@ -132,17 +139,17 @@
                 ?: throw NotImplementedError("No callback for '$clockId'")
         }
 
-        override fun getClockPickerConfig(clockId: ClockId): ClockPickerConfig {
-            return pickerConfigs[clockId]?.invoke(clockId)
-                ?: throw NotImplementedError("No picker config for '$clockId'")
+        override fun getClockPickerConfig(settings: ClockSettings): ClockPickerConfig {
+            return pickerConfigs[settings.clockId]?.invoke(settings)
+                ?: throw NotImplementedError("No picker config for '${settings.clockId}'")
         }
 
-        override fun initialize(buffers: ClockMessageBuffers?) { }
+        override fun initialize(buffers: ClockMessageBuffers?) {}
 
         fun addClock(
             id: ClockId,
             create: (ClockId) -> ClockController = ::failFactory,
-            getPickerConfig: (ClockId) -> ClockPickerConfig = ::failPickerConfig
+            getPickerConfig: (ClockSettings) -> ClockPickerConfig = ::failPickerConfig,
         ): FakeClockPlugin {
             metadata.add(ClockMetadata(id))
             createCallbacks[id] = create
@@ -158,29 +165,32 @@
         scope = TestScope(dispatcher)
         pickerConfig = ClockPickerConfig("CLOCK_ID", "NAME", "DESC", mockThumbnail)
 
-        fakeDefaultProvider = FakeClockPlugin()
-            .addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { pickerConfig })
+        fakeDefaultProvider =
+            FakeClockPlugin().addClock(DEFAULT_CLOCK_ID, { mockDefaultClock }, { pickerConfig })
         whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
 
         val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
-        registry = object : ClockRegistry(
-            mockContext,
-            mockPluginManager,
-            scope = scope.backgroundScope,
-            mainDispatcher = dispatcher,
-            bgDispatcher = dispatcher,
-            isEnabled = true,
-            handleAllUsers = true,
-            defaultClockProvider = fakeDefaultProvider,
-            keepAllLoaded = false,
-            subTag = "Test",
-            assert = mockThreadAssert,
-        ) {
-            override fun querySettings() { }
-            override fun applySettings(value: ClockSettings?) {
-                settings = value
+        registry =
+            object :
+                ClockRegistry(
+                    mockContext,
+                    mockPluginManager,
+                    scope = scope.backgroundScope,
+                    mainDispatcher = dispatcher,
+                    bgDispatcher = dispatcher,
+                    isEnabled = true,
+                    handleAllUsers = true,
+                    defaultClockProvider = fakeDefaultProvider,
+                    keepAllLoaded = false,
+                    subTag = "Test",
+                    assert = mockThreadAssert,
+                ) {
+                override fun querySettings() {}
+
+                override fun applySettings(value: ClockSettings?) {
+                    settings = value
+                }
             }
-        }
         registry.registerListeners()
 
         verify(mockPluginManager)
@@ -190,14 +200,10 @@
 
     @Test
     fun pluginRegistration_CorrectState() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = FakeLifecycle("1", plugin1)
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3")
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3").addClock("clock_4")
         val lifecycle2 = FakeLifecycle("2", plugin2)
 
         pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
@@ -210,8 +216,8 @@
                 ClockMetadata("clock_1"),
                 ClockMetadata("clock_2"),
                 ClockMetadata("clock_3"),
-                ClockMetadata("clock_4")
-            )
+                ClockMetadata("clock_4"),
+            ),
         )
     }
 
@@ -223,14 +229,13 @@
 
     @Test
     fun clockIdConflict_ErrorWithoutCrash_unloadDuplicate() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1", { mockClock }, { pickerConfig })
-            .addClock("clock_2", { mockClock }, { pickerConfig })
+        val plugin1 =
+            FakeClockPlugin()
+                .addClock("clock_1", { mockClock }, { pickerConfig })
+                .addClock("clock_2", { mockClock }, { pickerConfig })
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin2 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         pluginListener.onPluginLoaded(plugin1, mockContext, lifecycle1)
@@ -241,8 +246,8 @@
             setOf(
                 ClockMetadata(DEFAULT_CLOCK_ID),
                 ClockMetadata("clock_1"),
-                ClockMetadata("clock_2")
-            )
+                ClockMetadata("clock_2"),
+            ),
         )
 
         assertEquals(registry.createExampleClock("clock_1"), mockClock)
@@ -255,14 +260,10 @@
 
     @Test
     fun createCurrentClock_pluginConnected() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3", { mockClock })
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3", { mockClock }).addClock("clock_4")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         registry.applySettings(ClockSettings("clock_3", null))
@@ -275,14 +276,10 @@
 
     @Test
     fun activeClockId_changeAfterPluginConnected() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3", { mockClock })
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3", { mockClock }).addClock("clock_4")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         registry.applySettings(ClockSettings("clock_3", null))
@@ -296,14 +293,10 @@
 
     @Test
     fun createDefaultClock_pluginDisconnected() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3")
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3").addClock("clock_4")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         registry.applySettings(ClockSettings("clock_3", null))
@@ -317,22 +310,25 @@
 
     @Test
     fun pluginRemoved_clockAndListChanged() {
-        val plugin1 = FakeClockPlugin()
-            .addClock("clock_1")
-            .addClock("clock_2")
+        val plugin1 = FakeClockPlugin().addClock("clock_1").addClock("clock_2")
         val lifecycle1 = spy(FakeLifecycle("1", plugin1))
 
-        val plugin2 = FakeClockPlugin()
-            .addClock("clock_3", { mockClock })
-            .addClock("clock_4")
+        val plugin2 = FakeClockPlugin().addClock("clock_3", { mockClock }).addClock("clock_4")
         val lifecycle2 = spy(FakeLifecycle("2", plugin2))
 
         var changeCallCount = 0
         var listChangeCallCount = 0
-        registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
-            override fun onCurrentClockChanged() { changeCallCount++ }
-            override fun onAvailableClocksChanged() { listChangeCallCount++ }
-        })
+        registry.registerClockChangeListener(
+            object : ClockRegistry.ClockChangeListener {
+                override fun onCurrentClockChanged() {
+                    changeCallCount++
+                }
+
+                override fun onAvailableClocksChanged() {
+                    listChangeCallCount++
+                }
+            }
+        )
 
         registry.applySettings(ClockSettings("clock_3", null))
         scheduler.runCurrent()
@@ -372,16 +368,24 @@
 
     @Test
     fun unknownPluginAttached_clockAndListUnchanged_loadRequested() {
-        val lifecycle = FakeLifecycle("", null).apply {
-            mComponentName = ComponentName("some.other.package", "SomeClass")
-        }
+        val lifecycle =
+            FakeLifecycle("", null).apply {
+                mComponentName = ComponentName("some.other.package", "SomeClass")
+            }
 
         var changeCallCount = 0
         var listChangeCallCount = 0
-        registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
-            override fun onCurrentClockChanged() { changeCallCount++ }
-            override fun onAvailableClocksChanged() { listChangeCallCount++ }
-        })
+        registry.registerClockChangeListener(
+            object : ClockRegistry.ClockChangeListener {
+                override fun onCurrentClockChanged() {
+                    changeCallCount++
+                }
+
+                override fun onAvailableClocksChanged() {
+                    listChangeCallCount++
+                }
+            }
+        )
 
         assertEquals(true, pluginListener.onPluginAttached(lifecycle))
         scheduler.runCurrent()
@@ -391,22 +395,33 @@
 
     @Test
     fun knownPluginAttached_clockAndListChanged_loadedCurrent() {
-        val metroLifecycle = FakeLifecycle("Metro", null).apply {
-            mComponentName = ComponentName("com.android.systemui.clocks.metro", "Metro")
-        }
-        val bignumLifecycle = FakeLifecycle("BigNum", null).apply {
-            mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNum")
-        }
-        val calligraphyLifecycle = FakeLifecycle("Calligraphy", null).apply {
-            mComponentName = ComponentName("com.android.systemui.clocks.calligraphy", "Calligraphy")
-        }
+        val metroLifecycle =
+            FakeLifecycle("Metro", null).apply {
+                mComponentName = ComponentName("com.android.systemui.clocks.metro", "Metro")
+            }
+        val bignumLifecycle =
+            FakeLifecycle("BigNum", null).apply {
+                mComponentName = ComponentName("com.android.systemui.clocks.bignum", "BigNum")
+            }
+        val calligraphyLifecycle =
+            FakeLifecycle("Calligraphy", null).apply {
+                mComponentName =
+                    ComponentName("com.android.systemui.clocks.calligraphy", "Calligraphy")
+            }
 
         var changeCallCount = 0
         var listChangeCallCount = 0
-        registry.registerClockChangeListener(object : ClockRegistry.ClockChangeListener {
-            override fun onCurrentClockChanged() { changeCallCount++ }
-            override fun onAvailableClocksChanged() { listChangeCallCount++ }
-        })
+        registry.registerClockChangeListener(
+            object : ClockRegistry.ClockChangeListener {
+                override fun onCurrentClockChanged() {
+                    changeCallCount++
+                }
+
+                override fun onAvailableClocksChanged() {
+                    listChangeCallCount++
+                }
+            }
+        )
 
         registry.applySettings(ClockSettings("DIGITAL_CLOCK_CALLIGRAPHY", null))
         scheduler.runCurrent()
@@ -466,67 +481,84 @@
         scheduler.runCurrent()
 
         // Verify all plugins were correctly loaded into the registry
-        assertEquals(registry.getClocks().toSet(), setOf(
-            ClockMetadata("DEFAULT"),
-            ClockMetadata("clock_2"),
-            ClockMetadata("clock_3"),
-            ClockMetadata("clock_4")
-        ))
+        assertEquals(
+            registry.getClocks().toSet(),
+            setOf(
+                ClockMetadata("DEFAULT"),
+                ClockMetadata("clock_2"),
+                ClockMetadata("clock_3"),
+                ClockMetadata("clock_4"),
+            ),
+        )
     }
 
     @Test
-    fun jsonDeserialization_gotExpectedObject() {
-        val expected = ClockSettings("ID", null).apply {
-            metadata.put("appliedTimestamp", 500)
-        }
-        val actual = ClockSettings.deserialize("""{
-            "clockId":"ID",
-            "metadata": {
-                "appliedTimestamp":500
-            }
-        }""")
+    fun jsonDeserialization() {
+        val expected = ClockSettings("ID").apply { metadata.put("appliedTimestamp", 50) }
+        val json = JSONObject("""{"clockId":"ID", "metadata": { "appliedTimestamp":50 } }""")
+        val actual = ClockSettings.fromJson(json)
         assertEquals(expected, actual)
     }
 
     @Test
-    fun jsonDeserialization_noTimestamp_gotExpectedObject() {
-        val expected = ClockSettings("ID", null)
-        val actual = ClockSettings.deserialize("{\"clockId\":\"ID\"}")
+    fun jsonDeserialization_noTimestamp() {
+        val expected = ClockSettings("ID")
+        val actual = ClockSettings.fromJson(JSONObject("""{"clockId":"ID"}"""))
         assertEquals(expected, actual)
     }
 
     @Test
-    fun jsonDeserialization_nullTimestamp_gotExpectedObject() {
-        val expected = ClockSettings("ID", null)
-        val actual = ClockSettings.deserialize("""{
-            "clockId":"ID",
-            "metadata":null
-        }""")
+    fun jsonDeserialization_nullTimestamp() {
+        val expected = ClockSettings("ID")
+        val actual = ClockSettings.fromJson(JSONObject("""{"clockId":"ID", "metadata":null}"""))
         assertEquals(expected, actual)
     }
 
     @Test
     fun jsonDeserialization_noId_deserializedEmpty() {
-        val expected = ClockSettings(null, null).apply {
-            metadata.put("appliedTimestamp", 500)
-        }
-        val actual = ClockSettings.deserialize("{\"metadata\":{\"appliedTimestamp\":500}}")
+        val expected = ClockSettings().apply { metadata.put("appliedTimestamp", 50) }
+        val actual = ClockSettings.fromJson(JSONObject("""{"metadata":{"appliedTimestamp":50}}"""))
         assertEquals(expected, actual)
     }
 
     @Test
-    fun jsonSerialization_gotExpectedString() {
-        val expected = "{\"clockId\":\"ID\",\"metadata\":{\"appliedTimestamp\":500}}"
-        val actual = ClockSettings.serialize(ClockSettings("ID", null).apply {
-            metadata.put("appliedTimestamp", 500)
-        })
+    fun jsonDeserialization_fontAxes() {
+        val expected = ClockSettings(axes = listOf(ClockFontAxisSetting("KEY", 10f)))
+        val json = JSONObject("""{"axes":[{"key":"KEY","value":10}]}""")
+        val actual = ClockSettings.fromJson(json)
         assertEquals(expected, actual)
     }
 
     @Test
-    fun jsonSerialization_noTimestamp_gotExpectedString() {
-        val expected = "{\"clockId\":\"ID\",\"metadata\":{}}"
-        val actual = ClockSettings.serialize(ClockSettings("ID", null))
-        assertEquals(expected, actual)
+    fun jsonSerialization() {
+        val expected =
+            JSONObject().apply {
+                put("clockId", "ID")
+                put("metadata", JSONObject().apply { put("appliedTimestamp", 50) })
+                put("axes", JSONArray())
+            }
+        val settings = ClockSettings("ID", null).apply { metadata.put("appliedTimestamp", 50) }
+        val actual = ClockSettings.toJson(settings)
+        assertEquals(expected.toString(), actual.toString())
+    }
+
+    @Test
+    fun jsonSerialization_noTimestamp() {
+        val expected =
+            JSONObject().apply {
+                put("clockId", "ID")
+                put("metadata", JSONObject())
+                put("axes", JSONArray())
+            }
+        val actual = ClockSettings.toJson(ClockSettings("ID", null))
+        assertEquals(expected.toString(), actual.toString())
+    }
+
+    @Test
+    fun jsonSerialization_axisSettings() {
+        val settings = ClockSettings(axes = listOf(ClockFontAxisSetting("KEY", 10f)))
+        val actual = ClockSettings.toJson(settings)
+        val expected = JSONObject("""{"metadata":{},"axes":[{"key":"KEY","value":10}]}""")
+        assertEquals(expected.toString(), actual.toString())
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index 7e86ff3..aa8b4f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -89,7 +89,7 @@
         // All providers need to provide clocks & thumbnails for exposed clocks
         for (metadata in provider.getClocks()) {
             assertNotNull(provider.createClock(metadata.clockId))
-            assertNotNull(provider.getClockPickerConfig(metadata.clockId))
+            assertNotNull(provider.getClockPickerConfig(ClockSettings(metadata.clockId)))
         }
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
index 2a196c6..1b3f29a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/CommandQueueInitializerTest.kt
@@ -32,7 +32,7 @@
 import org.junit.runner.RunWith
 import org.mockito.kotlin.verify
 
-@EnableFlags(StatusBarSimpleFragment.FLAG_NAME)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class CommandQueueInitializerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index 8dcc444..5be5fb4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.statusbar.core
 
 import android.platform.test.annotations.EnableFlags
+import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.display.data.repository.displayRepository
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.fakePrivacyDotWindowControllerStore
 import com.android.systemui.testKosmos
 import com.google.common.truth.Expect
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,6 +32,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.never
 import org.mockito.kotlin.verify
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -39,16 +42,12 @@
 class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
     @get:Rule val expect: Expect = Expect.create()
 
-    private val kosmos =
-        testKosmos().also {
-            it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory
-            it.statusBarInitializerStore = it.fakeStatusBarInitializerStore
-        }
+    private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val fakeDisplayRepository = kosmos.displayRepository
     private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory
     private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
-
+    private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
     // Lazy, so that @EnableFlags is set before initializer is instantiated.
     private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
 
@@ -83,6 +82,31 @@
         }
 
     @Test
+    fun start_startsPrivacyDotForCurrentDisplays() =
+        testScope.runTest {
+            fakeDisplayRepository.addDisplay(displayId = 1)
+            fakeDisplayRepository.addDisplay(displayId = 2)
+
+            underTest.start()
+            runCurrent()
+
+            verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start()
+            verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start()
+        }
+
+    @Test
+    fun start_doesNotStartPrivacyDotForDefaultDisplay() =
+        testScope.runTest {
+            fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
+
+            underTest.start()
+            runCurrent()
+
+            verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never())
+                .start()
+        }
+
+    @Test
     fun displayAdded_orchestratorForNewDisplayIsStarted() =
         testScope.runTest {
             underTest.start()
@@ -109,6 +133,18 @@
         }
 
     @Test
+    fun displayAdded_privacyDotForNewDisplayIsStarted() =
+        testScope.runTest {
+            underTest.start()
+            runCurrent()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+        }
+
+    @Test
     fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
         testScope.runTest {
             underTest.start()
@@ -129,8 +165,17 @@
             fakeDisplayRepository.addDisplay(displayId = 3)
             runCurrent()
 
-            expect
-                .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
-                .isTrue()
+            verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+        }
+
+    @Test
+    fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() =
+        testScope.runTest {
+            underTest.start()
+
+            fakeDisplayRepository.addDisplay(displayId = 3)
+            runCurrent()
+
+            verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
         }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
new file mode 100644
index 0000000..18eef33
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.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.statusbar.data.repository
+
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LightBarControllerStoreImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+
+    private val underTest = kosmos.lightBarControllerStoreImpl
+
+    @Before
+    fun start() {
+        underTest.start()
+    }
+
+    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+    @Test
+    fun forDisplay_startsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).start()
+        }
+
+    @Test
+    fun beforeDisplayRemoved_doesNotStopInstances() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance, never()).stop()
+        }
+
+    @Test
+    fun displayRemoved_stopsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).stop()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
new file mode 100644
index 0000000..ae734b3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl }
+
+    @Before
+    fun installDisplays() = runBlocking {
+        kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
+        kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun forDisplay_defaultDisplay_throws() {
+        underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY)
+    }
+
+    @Test
+    fun forDisplay_nonDefaultDisplay_doesNotThrow() {
+        underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
index f7a8858..3b720ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/RemoteInputRepositoryImplTest.kt
@@ -50,7 +50,7 @@
         MockitoAnnotations.initMocks(this)
 
         testScope = TestScope()
-        underTest = RemoteInputRepositoryImpl(remoteInputManager)
+        underTest = RemoteInputRepositoryImpl(testScope.backgroundScope, remoteInputManager)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
new file mode 100644
index 0000000..e65c04c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class SystemEventChipAnimationControllerStoreImplTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+    private val testScope = kosmos.testScope
+    private val fakeDisplayRepository = kosmos.displayRepository
+
+    // Lazy so that @EnableFlags has time to run before underTest is instantiated.
+    private val underTest by lazy { kosmos.systemEventChipAnimationControllerStoreImpl }
+
+    @Before
+    fun start() {
+        underTest.start()
+    }
+
+    @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+    @Test
+    fun beforeDisplayRemoved_doesNotStopInstances() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            verify(instance, never()).stop()
+        }
+
+    @Test
+    fun displayRemoved_stopsInstance() =
+        testScope.runTest {
+            val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+            fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+            verify(instance).stop()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
new file mode 100644
index 0000000..d4007d7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.statusbar.events
+
+import android.platform.test.annotations.EnableFlags
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ValueAnimator
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplaySystemEventChipAnimationControllerTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val displayRepository = kosmos.displayRepository
+    private val store = kosmos.systemEventChipAnimationControllerStore
+
+    // Lazy so that @EnableFlags has time to switch the flags before the instance is created.
+    private val underTest by lazy { kosmos.multiDisplaySystemEventChipAnimationController }
+
+    @Before
+    fun installDisplays() = runBlocking {
+        INSTALLED_DISPLAY_IDS.forEach { displayRepository.addDisplay(displayId = it) }
+    }
+
+    @Test
+    fun init_forwardsToAllControllers() {
+        underTest.init()
+
+        INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).init() }
+    }
+
+    @Test
+    fun stop_forwardsToAllControllers() {
+        underTest.stop()
+
+        INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).stop() }
+    }
+
+    @Test
+    fun announceForAccessibility_forwardsToAllControllers() {
+        val contentDescription = "test content description"
+        underTest.announceForAccessibility(contentDescription)
+
+        INSTALLED_DISPLAY_IDS.forEach {
+            verify(store.forDisplay(it)).announceForAccessibility(contentDescription)
+        }
+    }
+
+    @Test
+    fun onSystemEventAnimationBegin_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+        INSTALLED_DISPLAY_IDS.forEach {
+            val controller = store.forDisplay(it)
+            whenever(controller.onSystemEventAnimationBegin()).thenReturn(ValueAnimator.ofInt(0, 1))
+        }
+        val animator = underTest.onSystemEventAnimationBegin() as AnimatorSet
+
+        assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+    }
+
+    @Test
+    fun onSystemEventAnimationFinish_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+        INSTALLED_DISPLAY_IDS.forEach {
+            val controller = store.forDisplay(it)
+            whenever(controller.onSystemEventAnimationFinish(any()))
+                .thenReturn(ValueAnimator.ofInt(0, 1))
+        }
+        val animator =
+            underTest.onSystemEventAnimationFinish(hasPersistentDot = true) as AnimatorSet
+
+        assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+    }
+
+    companion object {
+        private const val DISPLAY_ID_1 = 123
+        private const val DISPLAY_ID_2 = 456
+        private val INSTALLED_DISPLAY_IDS = listOf(DISPLAY_ID_1, DISPLAY_ID_2)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
new file mode 100644
index 0000000..6bcd735
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.statusbar.events
+
+import android.view.Gravity.BOTTOM
+import android.view.Gravity.LEFT
+import android.view.Gravity.RIGHT
+import android.view.Gravity.TOP
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import android.view.fakeWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrivacyDotWindowControllerTest : SysuiTestCase() {
+
+    @get:Rule val expect: Expect = Expect.create()
+
+    private val kosmos = testKosmos()
+    private val underTest = kosmos.privacyDotWindowController
+    private val viewController = kosmos.privacyDotViewController
+    private val windowManager = kosmos.fakeWindowManager
+    private val executor = kosmos.fakeExecutor
+
+    @After
+    fun cleanUpCustomDisplay() {
+        context.display = null
+    }
+
+    @Test
+    fun start_beforeUiThreadExecutes_doesNotAddWindows() {
+        underTest.start()
+
+        assertThat(windowManager.addedViews).isEmpty()
+    }
+
+    @Test
+    fun start_beforeUiThreadExecutes_doesNotInitializeViewController() {
+        underTest.start()
+
+        assertThat(viewController.isInitialized).isFalse()
+    }
+
+    @Test
+    fun start_afterUiThreadExecutes_addsWindowsOnUiThread() {
+        underTest.start()
+
+        executor.runAllReady()
+
+        assertThat(windowManager.addedViews).hasSize(4)
+    }
+
+    @Test
+    fun start_afterUiThreadExecutes_initializesViewController() {
+        underTest.start()
+
+        executor.runAllReady()
+
+        assertThat(viewController.isInitialized).isTrue()
+    }
+
+    @Test
+    fun start_initializesTopLeft() {
+        underTest.start()
+        executor.runAllReady()
+
+        assertThat(viewController.topLeft?.id).isEqualTo(R.id.privacy_dot_top_left_container)
+    }
+
+    @Test
+    fun start_initializesTopRight() {
+        underTest.start()
+        executor.runAllReady()
+
+        assertThat(viewController.topRight?.id).isEqualTo(R.id.privacy_dot_top_right_container)
+    }
+
+    @Test
+    fun start_initializesTopBottomLeft() {
+        underTest.start()
+        executor.runAllReady()
+
+        assertThat(viewController.bottomLeft?.id).isEqualTo(R.id.privacy_dot_bottom_left_container)
+    }
+
+    @Test
+    fun start_initializesBottomRight() {
+        underTest.start()
+        executor.runAllReady()
+
+        assertThat(viewController.bottomRight?.id)
+            .isEqualTo(R.id.privacy_dot_bottom_right_container)
+    }
+
+    @Test
+    fun start_viewsAddedInRespectiveCorners() {
+        context.display = mock { on { rotation } doReturn Surface.ROTATION_0 }
+
+        underTest.start()
+        executor.runAllReady()
+
+        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or LEFT)
+        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or RIGHT)
+        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or LEFT)
+        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or RIGHT)
+    }
+
+    @Test
+    fun start_rotation90_viewsPositionIsShifted90degrees() {
+        context.display = mock { on { rotation } doReturn Surface.ROTATION_90 }
+
+        underTest.start()
+        executor.runAllReady()
+
+        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or LEFT)
+        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or LEFT)
+        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or RIGHT)
+        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or RIGHT)
+    }
+
+    @Test
+    fun start_rotation180_viewsPositionIsShifted180degrees() {
+        context.display = mock { on { rotation } doReturn Surface.ROTATION_180 }
+
+        underTest.start()
+        executor.runAllReady()
+
+        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or RIGHT)
+        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or LEFT)
+        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or RIGHT)
+        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or LEFT)
+    }
+
+    @Test
+    fun start_rotation270_viewsPositionIsShifted270degrees() {
+        context.display = mock { on { rotation } doReturn Surface.ROTATION_270 }
+
+        underTest.start()
+        executor.runAllReady()
+
+        expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or RIGHT)
+        expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or RIGHT)
+        expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or LEFT)
+        expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or LEFT)
+    }
+
+    private fun paramsForView(view: View): WindowManager.LayoutParams {
+        return windowManager.addedViews.entries
+            .first { it.key == view || it.key.findViewById<View>(view.id) != null }
+            .value
+    }
+
+    private fun gravityForView(view: View): Int {
+        return paramsForView(view).gravity
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
index 75479ad..60b95ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.notification
 
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -35,6 +37,13 @@
     private var scrimOffset = 0f
     private var contentHeight = 0f
     private var isCurrentGestureOverscroll = false
+    private val customFlingBehavior =
+        object : FlingBehavior {
+            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+                scrollBy(initialVelocity)
+                return initialVelocity / 2f
+            }
+        }
 
     private val scrollConnection =
         NotificationScrimNestedScrollConnection(
@@ -51,6 +60,7 @@
                 wasStarted = true
                 isStarted = false
             },
+            flingBehavior = customFlingBehavior,
         )
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index c005743..657e9df 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -58,6 +58,7 @@
 import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.wmshell.BubblesManager
+import com.google.android.msdl.domain.MSDLPlayer
 import java.util.Optional
 import junit.framework.Assert
 import org.junit.After
@@ -110,6 +111,7 @@
     private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
     private val statusBarService: IStatusBarService = mock()
     private val uiEventLogger: UiEventLogger = mock()
+    private val msdlPlayer: MSDLPlayer = mock()
     private lateinit var controller: ExpandableNotificationRowController
 
     @Before
@@ -150,7 +152,8 @@
                 dragController,
                 dismissibilityProvider,
                 statusBarService,
-                uiEventLogger
+                uiEventLogger,
+                msdlPlayer,
             )
         whenever(view.childrenContainer).thenReturn(childrenContainer)
 
@@ -268,14 +271,14 @@
         controller.mSettingsListener.onSettingChanged(
             BUBBLES_SETTING_URI,
             view.entry.sbn.userId,
-            "1"
+            "1",
         )
         verify(childView).setBubblesEnabledForUser(true)
 
         controller.mSettingsListener.onSettingChanged(
             BUBBLES_SETTING_URI,
             view.entry.sbn.userId,
-            "9"
+            "9",
         )
         verify(childView).setBubblesEnabledForUser(false)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 1ef4007..b2a485c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -4,8 +4,8 @@
 import android.content.pm.PackageManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.FlagsParameterization
 import android.widget.FrameLayout
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.systemui.Flags
@@ -16,7 +16,9 @@
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.andSceneContainer
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarState
@@ -47,10 +49,12 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(AndroidJUnit4::class)
-class StackScrollAlgorithmTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
 
     @JvmField @Rule var expect: Expect = Expect.create()
 
@@ -99,6 +103,18 @@
     private val bigGap = notifSectionDividerGap
     private val smallGap = px(R.dimen.notification_section_divider_height_lockscreen)
 
+    companion object {
+        @JvmStatic
+        @Parameters(name = "{0}")
+        fun getParams(): List<FlagsParameterization> {
+            return FlagsParameterization.allCombinationsOf().andSceneContainer()
+        }
+    }
+
+    init {
+        mSetFlagsRule.setFlagsParameterization(flags)
+    }
+
     @Before
     fun setUp() {
         Assume.assumeFalse(isTv())
@@ -614,7 +630,7 @@
 
     @Test
     fun resetViewStates_isOnKeyguard_viewBecomesTransparent() {
-        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
+        ambientState.fakeShowingStackOnLockscreen()
         ambientState.hideAmount = 0.25f
         whenever(notificationRow.isHeadsUpState).thenReturn(true)
 
@@ -676,7 +692,7 @@
         whenever(row1.isHeadsUpState).thenReturn(true)
         whenever(row2.isHeadsUpState).thenReturn(false)
 
-        ambientState.setStatusBarState(StatusBarState.KEYGUARD)
+        ambientState.fakeShowingStackOnLockscreen()
         ambientState.hideAmount = 0.25f
         ambientState.dozeAmount = 0.33f
         notificationShelf.viewState.hidden = true
@@ -729,6 +745,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     fun resetViewStates_noSpaceForFooter_footerHidden() {
         ambientState.isShadeExpanded = true
         ambientState.stackEndHeight = 0f // no space for the footer in the stack
@@ -740,6 +757,20 @@
     }
 
     @Test
+    @EnableSceneContainer
+    fun resetViewStates_noSpaceForFooter_footerHidden_withSceneContainer() {
+        ambientState.isShadeExpanded = true
+        ambientState.stackTop = 0f
+        ambientState.stackCutoff = 100f
+        val footerView = mockFooterView(height = 200) // no space for the footer in the stack
+        hostView.addView(footerView)
+
+        stackScrollAlgorithm.resetViewStates(ambientState, 0)
+
+        assertThat((footerView.viewState as FooterViewState).hideContent).isTrue()
+    }
+
+    @Test
     fun resetViewStates_clearAllInProgress_hasNonClearableRow_footerVisible() {
         whenever(notificationRow.canViewBeCleared()).thenReturn(false)
         ambientState.isClearAllInProgress = true
@@ -1552,3 +1583,19 @@
         whenever(viewState).thenReturn(ExpandableViewState())
     }
 }
+
+private fun mockFooterView(height: Int): FooterView {
+    return mock(FooterView::class.java).apply {
+        whenever(viewState).thenReturn(FooterViewState())
+        whenever(intrinsicHeight).thenReturn(height)
+    }
+}
+
+private fun AmbientState.fakeShowingStackOnLockscreen() {
+    if (SceneContainerFlag.isEnabled) {
+        isShowingStackOnLockscreen = true
+        lockscreenStackFadeInProgress = 1f // stack is fully opaque
+    } else {
+        setStatusBarState(StatusBarState.KEYGUARD)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 5052a00..198821f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -44,6 +44,8 @@
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -59,6 +61,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
@@ -88,6 +91,8 @@
     @Mock private ConfigurationController mConfigurationController;
     @Mock private UserTracker mUserTracker;
     @Mock private DozeInteractor mDozeInteractor;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Captor private ArgumentCaptor<BatteryStateChangeCallback> mBatteryStateChangeCallback;
 
     /**
@@ -134,6 +139,7 @@
             mStatusBarStateController,
             mUserTracker,
             mDozeInteractor,
+            mKeyguardTransitionInteractor,
             secureSettings
         );
 
@@ -286,6 +292,18 @@
         assertTrue(mDozeParameters.shouldControlScreenOff());
     }
 
+    @Test
+    public void shouldDelayDisplayDozeTransition_True_WhenTransitioningToAod() {
+        setShouldControlUnlockedScreenOffForTest(false);
+        when(mScreenOffAnimationController.shouldDelayDisplayDozeTransition()).thenReturn(false);
+        when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
+                .thenReturn(KeyguardState.LOCKSCREEN);
+        assertFalse(mDozeParameters.shouldDelayDisplayDozeTransition());
+
+        when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
+                .thenReturn(KeyguardState.AOD);
+        assertTrue(mDozeParameters.shouldDelayDisplayDozeTransition());
+    }
 
     @Test
     public void keyguardVisibility_changesControlScreenOffAnimation() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 88ec18d..12f6825 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -48,12 +48,10 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.statusbar.data.model.StatusBarAppearance;
 import com.android.systemui.statusbar.data.model.StatusBarMode;
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.kotlin.JavaAdapter;
 
 import kotlinx.coroutines.test.TestScope;
 
@@ -81,8 +79,8 @@
     private SysuiDarkIconDispatcher mStatusBarIconController;
     private LightBarController mLightBarController;
     private final TestScope mTestScope = TestScopeProvider.getTestScope();
-    private final FakeStatusBarModeRepository mStatusBarModeRepository =
-            new FakeStatusBarModeRepository();
+    private final FakeStatusBarModePerDisplayRepository mStatusBarModeRepository =
+            new FakeStatusBarModePerDisplayRepository();
 
     @Before
     public void setup() {
@@ -92,15 +90,15 @@
         mLightBarTransitionsController = mock(LightBarTransitionsController.class);
         when(mStatusBarIconController.getTransitionsController()).thenReturn(
                 mLightBarTransitionsController);
-        mLightBarController = new LightBarController(
-                mContext,
-                new JavaAdapter(mTestScope),
+        mLightBarController = new LightBarControllerImpl(
+                mContext.getDisplayId(),
+                mTestScope,
                 mStatusBarIconController,
                 mock(BatteryController.class),
                 mock(NavigationModeController.class),
                 mStatusBarModeRepository,
                 mock(DumpManager.class),
-                new FakeDisplayTracker(mContext));
+                mTestScope.getCoroutineContext());
         mLightBarController.start();
     }
 
@@ -121,7 +119,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -142,7 +140,7 @@
                 new AppearanceRegion(0 /* appearance */, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -165,7 +163,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -190,7 +188,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -214,7 +212,7 @@
                 new AppearanceRegion(0 /* appearance */, secondBounds)
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -231,7 +229,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -249,7 +247,7 @@
                 new AppearanceRegion(0, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -266,7 +264,7 @@
                 new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
         );
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -276,7 +274,7 @@
         reset(mStatusBarIconController);
 
         // WHEN the same appearance regions but different status bar mode is sent
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.LIGHTS_OUT_TRANSPARENT,
                         STATUS_BAR_BOUNDS,
@@ -298,7 +296,7 @@
                 /* start= */ new Rect(0, 0, 10, 10),
                 /* end= */ new Rect(0, 0, 20, 20));
 
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         startingBounds,
@@ -311,7 +309,7 @@
         BoundsPair newBounds = new BoundsPair(
                 /* start= */ new Rect(0, 0, 30, 30),
                 /* end= */ new Rect(0, 0, 40, 40));
-        mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+        mStatusBarModeRepository.getStatusBarAppearance().setValue(
                 new StatusBarAppearance(
                         StatusBarMode.TRANSPARENT,
                         newBounds,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index b2794d8..0eb6203 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -804,6 +804,13 @@
     }
 
     @Test
+    public void onBackPressedResetsLeaveOnKeyguardHide() {
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onBackPressed();
+        verify(mStatusBarStateController).setLeaveOpenOnKeyguardHide(false);
+    }
+
+    @Test
     public void testResetHideBouncerWhenShowingIsFalse_alternateBouncerHides() {
         // GIVEN the keyguard is showing
         reset(mAlternateBouncerInteractor);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
index 48c2cc7..a008588 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerTest.kt
@@ -20,6 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.flags.DisableSceneContainer
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
@@ -113,4 +115,21 @@
 
             assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
         }
+
+    @Test
+    @DisableSceneContainer
+    fun entireScreenTouchable_communalVisible() =
+        testScope.runTest {
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+            runCurrent()
+
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isTrue()
+
+            kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
+            runCurrent()
+
+            assertThat(underTest.shouldMakeEntireScreenTouchable()).isFalse()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index e0d9fac..110dec6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -36,7 +36,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
@@ -58,7 +57,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
-import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -144,6 +142,7 @@
         controller.start()
         controller.addCallback(mockOngoingCallListener)
         controller.setChipView(chipView)
+        onTeardown { controller.tearDownChipView() }
 
         val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
         verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
@@ -153,11 +152,6 @@
             .thenReturn(PROC_STATE_INVISIBLE)
     }
 
-    @After
-    fun tearDown() {
-        controller.tearDownChipView()
-    }
-
     @Test
     fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
         val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
@@ -224,7 +218,7 @@
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -241,7 +235,7 @@
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -257,7 +251,7 @@
         notifCollectionListener.onEntryUpdated(notification.build())
         chipView.measure(
             View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
-            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+            View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
         )
 
         assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -668,7 +662,7 @@
 
     private fun createCallNotifEntry(
         callStyle: Notification.CallStyle,
-        nullContentIntent: Boolean = false
+        nullContentIntent: Boolean = false,
     ): NotificationEntry {
         val notificationEntryBuilder = NotificationEntryBuilder()
         notificationEntryBuilder.modifyNotification(context).style = callStyle
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
index fc1ea22..19d5a16 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
@@ -66,6 +66,7 @@
             logBuffer = FakeLogBuffer.Factory.create(),
             verboseLogBuffer = FakeLogBuffer.Factory.create(),
             systemClock,
+            context.resources,
         )
     private val demoDataSource =
         mock<DemoDeviceBasedSatelliteDataSource>().also {
@@ -80,7 +81,11 @@
                 )
         }
     private val demoImpl =
-        DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope)
+        DemoDeviceBasedSatelliteRepository(
+            demoDataSource,
+            testScope.backgroundScope,
+            context.resources,
+        )
 
     private val underTest =
         DeviceBasedSatelliteRepositorySwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
index 8769389..a70881a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
@@ -58,7 +58,12 @@
                 whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents)
             }
 
-        underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope)
+        underTest =
+            DemoDeviceBasedSatelliteRepository(
+                dataSource,
+                testScope.backgroundScope,
+                context.resources,
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
index 55460bd..41fa9e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
@@ -28,4 +28,6 @@
     override val signalStrength = MutableStateFlow(0)
 
     override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
+
+    override var isOpportunisticSatelliteIconEnabled: Boolean = true
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index e7e4969..509aa7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -172,6 +172,26 @@
         }
 
     @Test
+    fun icon_null_allOosAndConfigIsFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.icon)
+
+            // GIVEN config for opportunistic icon is false
+            repo.isOpportunisticSatelliteIconEnabled = false
+
+            // GIVEN all icons are OOS
+            val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+            i1.isInService.value = false
+            i1.isEmergencyOnly.value = false
+
+            // GIVEN apm is disabled
+            airplaneModeRepository.setIsAirplaneMode(false)
+
+            // THEN icon is null because it is not allowed
+            assertThat(latest).isNull()
+        }
+
+    @Test
     fun icon_null_isEmergencyOnly() =
         testScope.runTest {
             val latest by collectLastValue(underTest.icon)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 53e033e..e7ca1dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -51,6 +51,8 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.provider.Settings;
 
 import androidx.annotation.VisibleForTesting;
@@ -561,6 +563,113 @@
     }
 
     @Test
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    public void onWallpaperColorsChanged_homeWallpaper_shouldUpdateTheme() {
+        // Should ask for a new theme when the colors of the last applied wallpaper change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(1);
+        // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+        // latest wallpaper
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(2);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings).putStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+                anyInt());
+
+        verify(mThemeOverlayApplier)
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+    }
+
+
+
+    @Test
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    public void onWallpaperColorsChanged_homeWallpaperWithSameColor_shouldKeepThemeAndReapply() {
+        // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
+        // with the same specified system palette one.
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(0xffa16b00), null);
+
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(1);
+        // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+        // latest wallpaper
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(2);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings, never()).putString(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+        // Apply overlay by existing theme from secure setting
+        verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    public void onWallpaperColorsChanged_lockWallpaper_shouldKeepTheme() {
+        // Should ask for a new theme when the colors of the last applied wallpaper change
+        WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+                Color.valueOf(Color.BLUE), null);
+
+        String jsonString =
+                "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+                        + "\"android.theme.customization.system_palette\":\"A16B00\","
+                        + "\"android.theme.customization.accent_color\":\"A16B00\","
+                        + "\"android.theme.customization.color_index\":\"2\"}";
+
+        when(mSecureSettings.getStringForUser(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+                .thenReturn(jsonString);
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+                .thenReturn(1);
+        // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+        // latest wallpaper
+        when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+                .thenReturn(2);
+
+        mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK,
+                USER_SYSTEM);
+
+        ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+        verify(mSecureSettings, never()).putString(
+                eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+        verify(mThemeOverlayApplier, never())
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+    }
+
+    @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() {
         // Should ask for a new theme when the colors of the last applied wallpaper change
         WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -594,6 +703,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_keepThemeWhenFromLatestWallpaperAndSpecifiedColor() {
         // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
         // with the same specified system palette one.
@@ -627,6 +737,7 @@
     }
 
     @Test
+    @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
     public void onWallpaperColorsChanged_keepThemeIfNotLatestWallpaper() {
         // Shouldn't ask for a new theme when the colors of the wallpaper that is not the last
         // applied one change
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 5792175..7d55169 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -28,6 +28,7 @@
 import java.io.PrintWriter
 import java.util.Locale
 import java.util.TimeZone
+import org.json.JSONArray
 import org.json.JSONObject
 
 /** Identifies a clock design */
@@ -61,7 +62,7 @@
 
     @ProtectedReturn("return new ClockPickerConfig(\"\", \"\", \"\", null);")
     /** Settings configuration parameters for the clock */
-    fun getClockPickerConfig(id: ClockId): ClockPickerConfig
+    fun getClockPickerConfig(settings: ClockSettings): ClockPickerConfig
 }
 
 /** Interface for controlling an active clock */
@@ -213,7 +214,53 @@
 
     /** Value to set this axis to */
     val value: Float,
-)
+) {
+    companion object {
+        private val KEY_AXIS_KEY = "key"
+        private val KEY_AXIS_VALUE = "value"
+
+        fun toJson(setting: ClockFontAxisSetting): JSONObject {
+            return JSONObject().apply {
+                put(KEY_AXIS_KEY, setting.key)
+                put(KEY_AXIS_VALUE, setting.value)
+            }
+        }
+
+        fun toJson(settings: List<ClockFontAxisSetting>): JSONArray {
+            return JSONArray().apply {
+                for (axis in settings) {
+                    put(toJson(axis))
+                }
+            }
+        }
+
+        fun fromJson(jsonObj: JSONObject): ClockFontAxisSetting {
+            return ClockFontAxisSetting(
+                key = jsonObj.getString(KEY_AXIS_KEY),
+                value = jsonObj.getDouble(KEY_AXIS_VALUE).toFloat(),
+            )
+        }
+
+        fun fromJson(jsonArray: JSONArray): List<ClockFontAxisSetting> {
+            val result = mutableListOf<ClockFontAxisSetting>()
+            for (i in 0..jsonArray.length() - 1) {
+                val obj = jsonArray.getJSONObject(i)
+                if (obj == null) continue
+                result.add(fromJson(obj))
+            }
+            return result
+        }
+
+        fun toFVar(settings: List<ClockFontAxisSetting>): String {
+            val sb = StringBuilder()
+            for (axis in settings) {
+                if (sb.length > 0) sb.append(", ")
+                sb.append("'${axis.key}' ${axis.value.toInt()}")
+            }
+            return sb.toString()
+        }
+    }
+}
 
 /** Methods which trigger various clock animations */
 @ProtectedInterface
@@ -350,6 +397,21 @@
     val description: String,
 ) {
     fun toSetting() = ClockFontAxisSetting(key, currentValue)
+
+    companion object {
+        fun merge(
+            fontAxes: List<ClockFontAxis>,
+            axisSettings: List<ClockFontAxisSetting>,
+        ): List<ClockFontAxis> {
+            val result = mutableListOf<ClockFontAxis>()
+            for (axis in fontAxes) {
+                val setting = axisSettings.firstOrNull { axis.key == it.key }
+                val output = setting?.let { axis.copy(currentValue = it.value) } ?: axis
+                result.add(output)
+            }
+            return result
+        }
+    }
 }
 
 /** Axis user interaction modes */
@@ -406,7 +468,7 @@
 data class ClockSettings(
     val clockId: ClockId? = null,
     val seedColor: Int? = null,
-    val axes: List<ClockFontAxisSetting>? = null,
+    val axes: List<ClockFontAxisSetting> = listOf(),
 ) {
     // Exclude metadata from equality checks
     var metadata: JSONObject = JSONObject()
@@ -415,38 +477,24 @@
         private val KEY_CLOCK_ID = "clockId"
         private val KEY_SEED_COLOR = "seedColor"
         private val KEY_METADATA = "metadata"
+        private val KEY_AXIS_LIST = "axes"
 
-        fun serialize(setting: ClockSettings?): String {
-            if (setting == null) {
-                return ""
+        fun toJson(setting: ClockSettings): JSONObject {
+            return JSONObject().apply {
+                put(KEY_CLOCK_ID, setting.clockId)
+                put(KEY_SEED_COLOR, setting.seedColor)
+                put(KEY_METADATA, setting.metadata)
+                put(KEY_AXIS_LIST, ClockFontAxisSetting.toJson(setting.axes))
             }
-
-            // TODO(b/364673977): Serialize axes
-
-            return JSONObject()
-                .put(KEY_CLOCK_ID, setting.clockId)
-                .put(KEY_SEED_COLOR, setting.seedColor)
-                .put(KEY_METADATA, setting.metadata)
-                .toString()
         }
 
-        fun deserialize(jsonStr: String?): ClockSettings? {
-            if (jsonStr.isNullOrEmpty()) {
-                return null
+        fun fromJson(json: JSONObject): ClockSettings {
+            val clockId = if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null
+            val seedColor = if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null
+            val axisList = json.optJSONArray(KEY_AXIS_LIST)?.let(ClockFontAxisSetting::fromJson)
+            return ClockSettings(clockId, seedColor, axisList ?: listOf()).apply {
+                metadata = json.optJSONObject(KEY_METADATA) ?: JSONObject()
             }
-
-            // TODO(b/364673977): Deserialize axes
-
-            val json = JSONObject(jsonStr)
-            val result =
-                ClockSettings(
-                    if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null,
-                    if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null,
-                )
-            if (!json.isNull(KEY_METADATA)) {
-                result.metadata = json.getJSONObject(KEY_METADATA)
-            }
-            return result
         }
     }
 }
diff --git a/packages/SystemUI/res/color/slider_active_track_color.xml b/packages/SystemUI/res/color/slider_active_track_color.xml
new file mode 100644
index 0000000..a5aa58d
--- /dev/null
+++ b/packages/SystemUI/res/color/slider_active_track_color.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/materialColorPrimary" android:state_enabled="true" />
+    <item android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/slider_inactive_track_color.xml b/packages/SystemUI/res/color/slider_inactive_track_color.xml
new file mode 100644
index 0000000..89aef77
--- /dev/null
+++ b/packages/SystemUI/res/color/slider_inactive_track_color.xml
@@ -0,0 +1,19 @@
+<?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.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/materialColorSurfaceContainerHighest" android:state_enabled="true" />
+    <item android:color="?androidprv:attr/materialColorPrimary" />
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/slider_thumb_color.xml b/packages/SystemUI/res/color/slider_thumb_color.xml
new file mode 100644
index 0000000..5206049
--- /dev/null
+++ b/packages/SystemUI/res/color/slider_thumb_color.xml
@@ -0,0 +1,20 @@
+<?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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    <item android:color="?androidprv:attr/materialColorSurfaceContainerHighest" android:state_enabled="false" />
+    <item android:color="?androidprv:attr/materialColorPrimary" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/volume_dialog_background_small_radius.xml b/packages/SystemUI/res/drawable/volume_dialog_background_small_radius.xml
new file mode 100644
index 0000000..7d79496
--- /dev/null
+++ b/packages/SystemUI/res/drawable/volume_dialog_background_small_radius.xml
@@ -0,0 +1,21 @@
+<?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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/volume_dialog_background_square_corner_radius" />
+    <solid android:color="?androidprv:attr/materialColorSurface" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
index df8521a..131201e 100644
--- a/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
+++ b/packages/SystemUI/res/drawable/volume_drawer_selection_bg.xml
@@ -17,8 +17,8 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:paddingMode="stack" >
     <size
-        android:height="@dimen/volume_ringer_item_size"
-        android:width="@dimen/volume_ringer_item_size" />
+        android:height="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:width="@dimen/volume_dialog_ringer_drawer_button_size" />
     <solid android:color="?androidprv:attr/materialColorPrimary" />
-    <corners android:radius="360dp" />
+    <corners android:radius="@dimen/volume_dialog_ringer_selected_button_background_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml b/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
index 7ddd57b7..a8c9818 100644
--- a/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
+++ b/packages/SystemUI/res/drawable/volume_ringer_item_bg.xml
@@ -16,7 +16,7 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:shape="rectangle" >
-    <size android:width="@dimen/volume_ringer_item_size" android:height="@dimen/volume_ringer_item_size"/>
+    <size android:width="@dimen/volume_dialog_ringer_drawer_button_size" android:height="@dimen/volume_dialog_ringer_drawer_button_size"/>
     <solid android:color="?androidprv:attr/materialColorSurfaceContainerHighest" />
-    <corners android:radius="@dimen/volume_ringer_item_radius" />
+    <corners android:radius="@dimen/volume_dialog_background_square_corner_radius" />
 </shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 8b39e5e..e90f055f 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -40,9 +40,9 @@
         android:layout_height="wrap_content"
         android:background="@drawable/volume_dialog_background"
         android:divider="@drawable/volume_dialog_spacer"
+        android:paddingVertical="@dimen/volume_dialog_vertical_padding"
         android:gravity="center_horizontal"
         android:orientation="vertical"
-        android:paddingVertical="@dimen/volume_dialog_vertical_padding"
         android:showDividers="middle">
 
         <include layout="@layout/volume_ringer_drawer" />
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index 8acdd39..c1852b1 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -18,10 +18,11 @@
     android:layout_height="@dimen/volume_dialog_slider_height">
 
     <com.google.android.material.slider.Slider
+        style="@style/SystemUI.Material3.Slider.Volume"
         android:id="@+id/volume_dialog_slider"
         android:layout_width="@dimen/volume_dialog_slider_height"
         android:layout_height="match_parent"
         android:layout_gravity="center"
         android:rotation="270"
-        android:theme="@style/Theme.MaterialComponents.DayNight" />
+        android:theme="@style/Theme.Material3.Light" />
 </FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_ringer_button.xml b/packages/SystemUI/res/layout/volume_ringer_button.xml
new file mode 100644
index 0000000..dc6780a
--- /dev/null
+++ b/packages/SystemUI/res/layout/volume_ringer_button.xml
@@ -0,0 +1,31 @@
+<?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.
+  -->
+<FrameLayout  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content" >
+
+    <ImageButton
+        android:id="@+id/volume_drawer_button"
+        android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:padding="@dimen/volume_dialog_ringer_drawer_button_icon_radius"
+        android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+        android:contentDescription="@string/volume_ringer_mode"
+        android:gravity="center"
+        android:src="@drawable/volume_ringer_item_bg"
+        android:background="@drawable/volume_ringer_item_bg"/>
+
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 94b55b1..9b3af52 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -20,9 +20,8 @@
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:gravity="center"
-    android:paddingLeft="@dimen/volume_dialog_ringer_container_padding"
-    android:paddingTop="@dimen/volume_dialog_ringer_container_padding"
-    android:paddingRight="@dimen/volume_dialog_ringer_container_padding"
+    android:paddingLeft="@dimen/volume_dialog_ringer_horizontal_padding"
+    android:paddingRight="@dimen/volume_dialog_ringer_horizontal_padding"
     android:layoutDirection="ltr"
     android:clipToPadding="false"
     android:clipChildren="false"
@@ -31,7 +30,6 @@
     <!-- Drawer view, invisible by default. -->
     <FrameLayout
         android:id="@+id/volume_drawer_container"
-        android:alpha="0.0"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical">
@@ -40,9 +38,8 @@
         <FrameLayout
             android:id="@+id/volume_drawer_selection_background"
             android:alpha="0.0"
-            android:layout_width="@dimen/volume_ringer_item_size"
-            android:layout_marginBottom="8dp"
-            android:layout_height="@dimen/volume_ringer_item_size"
+            android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
+            android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
             android:layout_gravity="bottom|right"
             android:background="@drawable/volume_drawer_selection_bg" />
 
@@ -52,64 +49,7 @@
             android:layout_height="wrap_content"
             android:orientation="vertical">
 
-            <FrameLayout
-                android:id="@+id/volume_drawer_vibrate"
-                android:layout_width="@dimen/volume_ringer_item_size"
-                android:layout_height="@dimen/volume_ringer_item_size"
-                android:layout_marginBottom="8dp"
-                android:contentDescription="@string/volume_ringer_hint_vibrate"
-                android:gravity="center"
-                android:background="@drawable/volume_ringer_item_bg">
-
-                <ImageView
-                    android:id="@+id/volume_drawer_vibrate_icon"
-                    android:layout_width="@dimen/volume_ringer_icon_size"
-                    android:layout_height="@dimen/volume_ringer_icon_size"
-                    android:layout_gravity="center"
-                    android:src="@drawable/ic_volume_ringer_vibrate"
-                    android:tint="?androidprv:attr/materialColorOnSurface" />
-
-            </FrameLayout>
-
-            <FrameLayout
-                android:id="@+id/volume_drawer_mute"
-                android:layout_width="@dimen/volume_ringer_item_size"
-                android:layout_height="@dimen/volume_ringer_item_size"
-                android:layout_marginBottom="8dp"
-                android:accessibilityTraversalAfter="@id/volume_drawer_vibrate"
-                android:contentDescription="@string/volume_ringer_hint_mute"
-                android:gravity="center"
-                android:background="@drawable/volume_ringer_item_bg">
-
-                <ImageView
-                    android:id="@+id/volume_drawer_mute_icon"
-                    android:layout_width="@dimen/volume_ringer_icon_size"
-                    android:layout_height="@dimen/volume_ringer_icon_size"
-                    android:layout_gravity="center"
-                    android:src="@drawable/ic_speaker_mute"
-                    android:tint="?androidprv:attr/materialColorOnSurface" />
-
-            </FrameLayout>
-
-            <FrameLayout
-                android:id="@+id/volume_drawer_normal"
-                android:layout_width="@dimen/volume_ringer_item_size"
-                android:layout_height="@dimen/volume_ringer_item_size"
-                android:layout_marginBottom="8dp"
-                android:accessibilityTraversalAfter="@id/volume_drawer_mute"
-                android:contentDescription="@string/volume_ringer_hint_unmute"
-                android:gravity="center"
-                android:background="@drawable/volume_ringer_item_bg">
-
-                <ImageView
-                    android:id="@+id/volume_drawer_normal_icon"
-                    android:layout_width="@dimen/volume_ringer_icon_size"
-                    android:layout_height="@dimen/volume_ringer_icon_size"
-                    android:layout_gravity="center"
-                    android:src="@drawable/ic_speaker_on"
-                    android:tint="?androidprv:attr/materialColorOnSurface" />
-
-            </FrameLayout>
+            <!-- add ringer buttons here -->
 
         </LinearLayout>
 
@@ -117,23 +57,16 @@
 
     <!-- The current ringer selection. When the drawer is opened, this animates to the corresponding
          position in the drawer. When the drawer is closed, it animates back. -->
-    <FrameLayout
-        android:id="@+id/volume_new_ringer_active_icon_container"
-        android:layout_width="@dimen/volume_ringer_item_size"
-        android:layout_height="@dimen/volume_ringer_item_size"
-        android:layout_marginBottom="8dp"
-        android:layout_gravity="bottom|right"
+    <ImageButton
+        android:id="@+id/volume_new_ringer_active_button"
+        android:layout_width="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:layout_height="@dimen/volume_dialog_ringer_drawer_button_size"
+        android:layout_marginBottom="@dimen/volume_dialog_components_spacing"
+        android:background="@drawable/volume_drawer_selection_bg"
         android:contentDescription="@string/volume_ringer_change"
-        android:background="@drawable/volume_drawer_selection_bg">
-
-        <ImageView
-            android:id="@+id/volume_new_ringer_active_icon"
-            android:layout_width="@dimen/volume_ringer_icon_size"
-            android:layout_height="@dimen/volume_ringer_icon_size"
-            android:layout_gravity="center"
-            android:tint="?androidprv:attr/materialColorOnPrimary"
-            android:src="@drawable/ic_volume_media" />
-
-    </FrameLayout>
+        android:gravity="center"
+        android:padding="10dp"
+        android:src="@drawable/ic_volume_media"
+        android:tint="?androidprv:attr/materialColorOnPrimary" />
 
 </FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 9ec6106..c091cbf 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Neem jou skerm op?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Neem een app op"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Neem hele skerm op"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Wanneer jy jou hele skerm opneem, word enigiets wat op jou skerm wys, opgeneem. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Wanneer jy ’n app opneem, word enigiets wat in daardie app gewys of gespeel word, opgeneem. Wees dus versigtig met dinge soos wagwoorde, betalingbesonderhede, boodskappe, foto’s, en oudio en video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Neem skerm op"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Huidige app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Toeganklikheid"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Kortpadsleutels"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Soekkortpaaie"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Geen soekresultate nie"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Vou ikoon in"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vou ikoon uit"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sleephandvatsel"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 598e224..af2971b 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ማያ ገፅዎን ይቀዳሉ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"አንድ መተግበሪያ ቅዳ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"መላው ማያ ገፅን ቅረጽ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"መላው ማያ ገፅዎን በሚቀዱበት ጊዜ፣ በማያ ገፅዎ ላይ የሚታየው ማንኛውም ነገር ይቀዳል። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"መተግበሪያን ሲቀዱ በዚያ መተግበሪያ ውስጥ የሚታይ ወይም የሚጫወት ማንኛውም ነገር ይቀዳል። ስለዚህ እንደ የይለፍ ቃላት፣ የክፍያ ዝርዝሮች፣ መልዕክቶች፣ ፎቶዎች እና ኦዲዮ እና ቪድዮ ላሉ ነገሮች ጥንቃቄ ያድርጉ።"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ማያ ገፅን ቅረጽ"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"የአሁን መተግበሪያ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ተደራሽነት"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"የቁልፍ ሰሌዳ አቋራጮች"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"የፍለጋ አቋራጮች"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ምንም የፍለጋ ውጤቶች የሉም"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"መሰብሰቢያ አዶ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"መዘርጊያ አዶ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ወይም"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"መያዣ ይጎትቱ"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 342c8c2..4ebac5a 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"هل تريد تسجيل محتوى الشاشة؟"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"تسجيل محتوى تطبيق واحد"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"تسجيل محتوى الشاشة بالكامل"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"أثناء تسجيل محتوى الشاشة بالكامل، يتم تسجيل كل المحتوى المعروض على شاشتك. لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"أثناء تسجيل محتوى تطبيق، يتم تسجيل أي محتوى يتم عرضه أو تشغيله في ذلك التطبيق. لذا يُرجى توخي الحذر بشأن المعلومات، مثل كلمات المرور وتفاصيل الدفع والرسائل والصور وملفات الصوت والفيديو."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"تسجيل محتوى الشاشة"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"التطبيق الحالي"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"تسهيل الاستخدام"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"اختصارات لوحة المفاتيح"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"اختصارات طلبات البحث"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ما مِن نتائج بحث"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"رمز التصغير"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"رمز التوسيع"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"أو"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"مقبض السحب"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index b71db2e..85517f3 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"আপোনাৰ স্ক্ৰীনখন ৰেকৰ্ড কৰিবনে?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"এটা এপ্ ৰেকৰ্ড কৰক"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"গোটেই স্ক্ৰীনখন ৰেকৰ্ড কৰক"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"আপুনি গোটেই স্ক্ৰীনখন ৰেকৰ্ডিং কৰিলে, আপোনাৰ স্ক্ৰীনখনত দেখুওৱা যিকোনো বস্তু ৰেকৰ্ড কৰা হয়। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"আপুনি কোনো এপ্ ৰেকৰ্ড কৰিলে, সেই এপত দেখুওৱা বা প্লে’ কৰা যিকোনো বস্তু ৰেকৰ্ড কৰা হয়। গতিকে, পাছৱৰ্ড, পৰিশোধৰ সবিশেষ, বাৰ্তা, ফট’ আৰু অডিঅ’ আৰু ভিডিঅ’ৰ ক্ষেত্ৰত সাৱধান হওক।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"স্ক্ৰীনখন ৰেকৰ্ড কৰক"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"আটাইবোৰ মচক"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"পৰিচালনা কৰক"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"জাননীৰ ছেটিং"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"জাননীৰ ইতিহাস"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"নতুন"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"নীৰৱ"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"জাননীসমূহ"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"বৰ্তমানৰ এপ্‌"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"সাধ্য সুবিধা"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"কীব’ৰ্ডৰ শ্বৰ্টকাট"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"সন্ধানৰ শ্বৰ্টকাট"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"সন্ধানৰ কোনো ফলাফল নাই"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"সংকোচন কৰাৰ চিহ্ন"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"বিস্তাৰ কৰাৰ চিহ্ন"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ড্ৰেগ হেণ্ডেল"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index d51c9aa..f08724a 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ekran qeydə alınsın?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Bir tətbiqi qeydə alın"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Bütün ekranı qeydə alın"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Bütün ekranı qeydə alarkən ekranda göstərilən bütün kontent qeydə alınır. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Tətbiq qeydə aldıqda həmin tətbiqdə göstərilən və ya işə salınan bütün kontent qeydə alınır. Parol, ödəniş detalları, mesaj, foto, habelə audio və video kimi məlumatlarla bağlı diqqətli olun."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekranı qeydə alın"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Cari tətbiq"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Xüsusi imkanlar"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klaviatura qısayolları"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Axtarış qısayolları"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Axtarış nəticəsi yoxdur"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"İkonanı yığcamlaşdırın"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"İkonanı genişləndirin"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"və ya"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dəstəyi çəkin"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 881495f..f509691 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Želite da snimite ekran?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Snimi jednu aplikaciju"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Snimi ceo ekran"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kada snimate ceo ekran, snima se sve što je na njemu. Zato pazite na lozinke, informacije o plaćanju, poruke, slike, audio i video sadržaj."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kada snimate aplikaciju, snima se sav sadržaj koji se prikazuje ili pušta u njoj. Zato pazite na lozinke, informacije o plaćanju, poruke, slike, audio i video sadržaj."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Snimi ekran"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljaj"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Istorija"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Podešavanja obaveštenja"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Istorija obaveštenja"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Novo"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Nečujno"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Obaveštenja"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuelna aplikacija"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tasterske prečice"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečice pretrage"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretrage"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za skupljanje"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za prevlačenje"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 16bb70c..bc0e2d1 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Запісаць экран?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Запісаць адну праграму"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Запісаць змесціва ўсяго экрана"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Пры запісе ўсяго экрана запісваецца ўсё, што паказваецца на экране. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Пры запісе праграмы запісваецца ўсё, што паказваецца або прайграецца ў гэтай праграме. Таму прадухіліце паказ пароляў, плацежных рэквізітаў, паведамленняў, фота, відэа і аўдыя."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Запісаць экран"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Бягучая праграма"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Спецыяльныя магчымасці"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Спалучэнні клавіш"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пошук спалучэнняў клавіш"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Няма вынікаў пошуку"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Згарнуць\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Разгарнуць\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перацягвання"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 2b3a0b9..2ee1f5f 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Да се записва ли екранът?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Записване на едно приложение"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Записване на целия екран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Когато записвате целия си екран, се записва всичко, което се показва на него. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Когато записвате приложение, се записва всичко, което се показва или възпроизвежда в него. Затова бъдете внимателни с неща като пароли, подробности за начини на плащане, съобщения, снимки, аудио и видео."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Записване на екрана"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Текущо приложение"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Достъпност"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Клавишни комбинации"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Търсете клавишни комбинации"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Няма резултати от търсенето"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за свиване"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за разгъване"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Манипулатор за преместване с плъзгане"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index f39a644..c593210 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"আপনার স্ক্রিন রেকর্ড করবেন?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"একটি অ্যাপ রেকর্ড করুন"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"সম্পূর্ণ স্ক্রিন রেকর্ড করুন"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"আপনার সম্পূর্ণ স্ক্রিন রেকর্ড করার সময়, আপনার স্ক্রিনে দেখানো সব কিছু রেকর্ড করা হয়। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ের ক্ষেত্রে সতর্ক থাকুন।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"আপনি কোনও অ্যাপ রেকর্ড করার সময়, সেই অ্যাপে দেখানো বা চালানো সব কিছু রেকর্ড করা হয়। তাই পাসওয়ার্ড, পেমেন্টের বিবরণ, মেসেজ, ফটো এবং অডিও ও ভিডিওর মতো বিষয়ের ক্ষেত্রে সতর্ক থাকুন।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"স্ক্রিন রেকর্ড করুন"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"সব মুছে দিন"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ম্যানেজ করুন"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ইতিহাস"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"বিজ্ঞপ্তি সেটিংস"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"বিজ্ঞপ্তির ইতিহাস"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"নতুন"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"আওয়াজ করবে না"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"বিজ্ঞপ্তি"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"বর্তমান অ্যাপ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"অ্যাক্সেসিবিলিটি"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"কীবোর্ড শর্টকাট"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"সার্চ শর্টকাট"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"কোনও সার্চ ফলাফল নেই"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"আইকন আড়াল করুন"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"আইকন বড় করুন"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"অথবা"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"টেনে আনার হ্যান্ডেল"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index ec57c6a..38f4265 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Snimati ekran?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Snimaj jednu aplikaciju"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Snimaj cijeli ekran"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kada snimate cijeli ekran, snimat će se sve što se prikazuje na ekranu. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kada snimate aplikaciju, snimat će se sve što se prikazuje ili reproducira u toj aplikaciji. Stoga budite oprezni s informacijama kao što su lozinke, podaci o plaćanju, poruke, fotografije, zvukovi i videozapisi."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Snimaj ekran"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Obriši sve"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Upravljajte"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historija"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Postavke obavještenja"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Historija obavještenja"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Novo"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Nečujno"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Obavještenja"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Trenutna aplikacija"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Prečice tastature"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečica pretraživanja"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretraživanja"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sužavanja"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona proširivanja"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ručica za prevlačenje"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index 9c9ba90..bcaca5a 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vols gravar la pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grava una aplicació"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grava tota la pantalla"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quan graves tota la pantalla, es grava tot el que es mostra en pantalla. Per aquest motiu, ves amb compte amb elements com les contrasenyes, les dades de pagament, els missatges, les fotos, i l\'àudio i el vídeo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quan graves una aplicació, es grava tot el que es mostra o es reprodueix en aquesta aplicació. Per aquest motiu, ves amb compte amb les contrasenyes, les dades de pagament, els missatges, les fotos, i l\'àudio i el vídeo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grava la pantalla"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicació actual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilitat"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tecles de drecera"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Dreceres de cerca"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No hi ha cap resultat de la cerca"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Replega la icona"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Desplega la icona"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ansa per arrossegar"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 95f3699..76ae86d 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Pořídit nahrávku obrazovky?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Nahrát jednu aplikaci"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Nahrát celou obrazovku"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Při nahrávání celé obrazovky se zaznamenává veškerý obsah na obrazovce. Buďte proto opatrní, když jde o hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Při nahrávání aplikace se zaznamenává všechno, co se v dané obrazovce zobrazuje nebo přehrává. Buďte proto opatrní, když jde o hesla, platební údaje, zprávy, fotografie, zvuk a video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Nahrát obrazovku"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuální aplikace"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Přístupnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klávesové zkratky"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Vyhledat zkratky"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Žádné výsledky hledání"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona sbalení"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalení"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"nebo"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Úchyt pro přetažení"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 8266799..abcd4b8 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vil du optage din skærm?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Optag én app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Optag hele skærmen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Når du optager hele skærmen, bliver alt det, der vises på skærmen, optaget. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Når du optager en app, optages alt det, der vises eller afspilles i den pågældende app. Vær derfor forsigtig med ting såsom adgangskoder, betalingsoplysninger, beskeder, billeder, lyd og video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Optag skærm"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Ryd alle"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrer"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historik"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Indstillinger for notifikationer"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Notifikationshistorik"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nye"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Lydløs"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notifikationer"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuel app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Hjælpefunktioner"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tastaturgenveje"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Genveje til søgning"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Der er ingen søgeresultater"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon for Skjul"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon for Udvid"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtag"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index a4bc364..9799a932 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Bildschirm aufnehmen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Einzelne App aufnehmen"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gesamten Bildschirm aufnehmen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Wenn du den gesamten Bildschirm aufnimmst, ist in der Aufnahme alles zu sehen, was auf dem Bildschirm angezeigt wird. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Wenn du eine App aufnimmst, ist in der Aufnahme alles zu sehen, was in dieser App angezeigt oder abgespielt wird. Sei also vorsichtig mit Informationen wie Passwörtern, Zahlungsdetails, Nachrichten, Fotos sowie Audio- und Videoinhalten."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Bildschirm aufnehmen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuelle App"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Bedienungshilfen"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tastenkürzel"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tastenkürzel suchen"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Keine Suchergebnisse"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Symbol „Minimieren“"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Symbol „Maximieren“"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oder"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ziehpunkt"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 278a8ea..7200a91 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Να γίνει εγγραφή της οθόνης σας;"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Εγγραφή μίας εφαρμογής"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Εγγραφή ολόκληρης της οθόνης"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Όταν κάνετε εγγραφή ολόκληρης της οθόνη σας, καταγράφεται οτιδήποτε εμφανίζεται σε αυτήν. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Όταν κάνετε εγγραφή μιας εφαρμογής, καταγράφεται οτιδήποτε εμφανίζεται ή αναπαράγεται στη συγκεκριμένη εφαρμογή. Επομένως, να είστε προσεκτικοί με τους κωδικούς πρόσβασης, τα στοιχεία πληρωμής, τα μηνύματα, τις φωτογραφίες, τον ήχο και το βίντεο."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Εγγραφή οθόνης"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Τρέχουσα εφαρμογή"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Προσβασιμότητα"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Συντομεύσεις πληκτρολογίου"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Συντομεύσεις αναζήτησης"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Κανένα αποτέλεσμα αναζήτησης"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Εικονίδιο σύμπτυξης"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Εικονίδιο ανάπτυξης"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ή"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Λαβή μεταφοράς"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 2fbd60a..1ecf4f1 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Record your screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Record one app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Record entire screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"When you\'re recording your entire screen, anything displayed on your screen is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"When you\'re recording an app, anything displayed or played in that app is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Record screen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index 23475fa..f775513 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Record your screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Record one app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Record entire screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"When you’re recording your entire screen, anything shown on your screen is recorded. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"When you’re recording an app, anything shown or played in that app is recorded. So be careful with things like passwords, payment details, messages, photos, and audio and video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Record screen"</string>
@@ -136,17 +138,14 @@
     <string name="screenrecord_stop_dialog_message_specific_app" msgid="5995770227684523244">"You\'re currently recording <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="screenrecord_stop_dialog_button" msgid="2883812564938194350">"Stop recording"</string>
     <string name="share_to_app_chip_accessibility_label" msgid="4210256229976947065">"Sharing screen"</string>
-    <!-- no translation found for share_to_app_chip_accessibility_label_generic (5517431657924536133) -->
-    <skip />
+    <string name="share_to_app_chip_accessibility_label_generic" msgid="5517431657924536133">"Sharing content"</string>
     <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"Stop sharing screen?"</string>
-    <!-- no translation found for share_to_app_stop_dialog_title_generic (9079161538135843648) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_title_generic" msgid="9079161538135843648">"Stop sharing?"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app" msgid="522823522115375414">"You\'re currently sharing your entire screen with <xliff:g id="HOST_APP_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen" msgid="5090115386271179270">"You\'re currently sharing your entire screen with an app"</string>
     <string name="share_to_app_stop_dialog_message_single_app_specific" msgid="5923772039347985172">"You\'re currently sharing <xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_single_app_generic" msgid="6681016774654578261">"You\'re currently sharing an app"</string>
-    <!-- no translation found for share_to_app_stop_dialog_message_generic (7622174291691249392) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_message_generic" msgid="7622174291691249392">"You\'re currently sharing with an app"</string>
     <string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Stop sharing"</string>
     <string name="cast_screen_to_other_device_chip_accessibility_label" msgid="4687917476203009885">"Casting screen"</string>
     <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Stop casting?"</string>
@@ -521,10 +520,8 @@
     <string name="communal_widget_picker_title" msgid="1953369090475731663">"Lock screen widgets"</string>
     <string name="communal_widget_picker_description" msgid="490515450110487871">"Anyone can view widgets on your lock screen, even if your tablet\'s locked."</string>
     <string name="accessibility_action_label_unselect_widget" msgid="1041811747619468698">"unselect widget"</string>
-    <!-- no translation found for accessibility_action_label_shrink_widget (8259511040536438771) -->
-    <skip />
-    <!-- no translation found for accessibility_action_label_expand_widget (9190524260912211759) -->
-    <skip />
+    <string name="accessibility_action_label_shrink_widget" msgid="8259511040536438771">"Decrease height"</string>
+    <string name="accessibility_action_label_expand_widget" msgid="9190524260912211759">"Increase height"</string>
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Lock screen widgets"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"To open an app using a widget, you’ll need to verify it’s you. Also, keep in mind that anyone can view them, even when your tablet’s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Got it"</string>
@@ -1411,9 +1408,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current App"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 2fbd60a..1ecf4f1 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Record your screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Record one app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Record entire screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"When you\'re recording your entire screen, anything displayed on your screen is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"When you\'re recording an app, anything displayed or played in that app is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Record screen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 2fbd60a..1ecf4f1 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Record your screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Record one app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Record entire screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"When you\'re recording your entire screen, anything displayed on your screen is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"When you\'re recording an app, anything displayed or played in that app is recorded. So, be careful with things like passwords, payment details, messages, photos, audio and video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Record screen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Current app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Keyboard shortcuts"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Search shortcuts"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No search results"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Collapse icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Expand icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"or"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Drag handle"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 9ea1540..77c2a63 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"¿Quieres grabar la pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grabar una app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grabar toda la pantalla"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Cuando grabes toda la pantalla, se grabará todo lo que se muestre en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Cuando grabes una app, se registrará todo lo que se muestre o reproduzca en ella. Por lo tanto, debes tener cuidado con contraseñas, detalles de pagos, mensajes, fotos, audios y videos."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grabar pantalla"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Cerrar todo"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Administrar"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Historial"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Configuración de notificaciones"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Historial de notificaciones"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nuevo"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Silenciadas"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Notificaciones"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App actual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidad"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Combinaciones de teclas"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Buscar combinaciones de teclas"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"La búsqueda no arrojó resultados"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícono de contraer"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícono de expandir"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 16ffd44..81b1562 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"¿Grabar la pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grabar una aplicación"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grabar toda la pantalla"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Cuando grabas toda la pantalla, se graba todo lo que se muestre en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Cuando grabas una aplicación, se graba todo lo que se muestre o reproduzca en ella. Debes tener cuidado con elementos como contraseñas, detalles de pagos, mensajes, fotos, audio y vídeo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grabar pantalla"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicación en uso"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidad"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Combinaciones de teclas"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atajos de búsqueda"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"No hay resultados de búsqueda"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icono de contraer"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icono de desplegar"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 5c9664b..56039da5 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Kas salvestada ekraanikuvast video?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Ühe rakenduse salvestamine"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Kogu ekraanikuva salvestamine"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kui salvestate kogu ekraani, salvestatakse kõik ekraanil kuvatud andmed. Seega olge ettevaatlik selliste andmetega nagu paroolid, makseteave, sõnumid, fotod ning heli ja video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kui salvestate rakendust, salvestatakse kõik, mida selles rakenduses näidatakse või esitatakse. Seega olge ettevaatlik selliste andmetega nagu paroolid, makseteave, sõnumid, fotod ning heli ja video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekraanikuva jäädvustamine"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Praegune rakendus"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Juurdepääsetavus"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klaviatuuri otseteed"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Otsingu otseteed"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Otsingutulemused puuduvad"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ahendamisikoon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laiendamisikoon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"või"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Lohistamispide"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 31348b6..426d1d7 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Pantaila grabatu nahi duzu?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Grabatu aplikazio bat"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Grabatu pantaila osoa"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Pantaila osoa grabatzen ari zarenean, pantailan agertzen den guztia grabatzen da. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Aplikazio bat grabatzen ari zarenean, aplikazio horretan agertzen den edo bertan erreproduzitzen ari den guztia grabatzen da. Beraz, kontuz ibili pasahitzekin, ordainketen xehetasunekin, mezuekin, argazkiekin, audioekin eta bideoekin, besteak beste."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Grabatu pantaila"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Oraingo aplikazioa"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Erabilerraztasuna"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Lasterbideak"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Bilatu lasterbideak"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ez dago bilaketa-emaitzarik"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tolesteko ikonoa"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Zabaltzeko ikonoa"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"edo"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Arrastatzeko kontrol-puntua"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index bc89200..b0d23aa 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"صفحه‌نمایش ضبط شود؟"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ضبط یک برنامه"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ضبط کل صفحه‌نمایش"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"وقتی کل صفحه‌نمایش را ضبط می‌کنید، هر چیزی که در صفحه‌نمایش نشان داده شود ضبط خواهد شد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"وقتی برنامه‌ای را ضبط می‌کنید، هر چیزی که در آن برنامه نشان داده شود یا پخش شود ضبط خواهد شد. درنتیجه مراقب چیزهایی مثل گذرواژه‌ها، جزئیات پرداخت، پیام‌ها، عکس‌ها، و صدا و تصویر باشید."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ضبط صفحه‌نمایش"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"پاک کردن همه موارد"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"مدیریت"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"سابقه"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"تنظیمات اعلان"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"سابقه اعلان"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"جدید"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"بی‌صدا"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"اعلان‌ها"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"برنامه فعلی"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"دسترس‌پذیری"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"میان‌برهای صفحه‌کلید"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"جستجوی میان‌برها"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"نتیجه‌ای برای جستجو پیدا نشد"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"نماد جمع کردن"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"نماد ازهم بازکردن"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"دستگیره کشاندن"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index a40cbae..690228f 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Tallennetaanko näytön toimintaa?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Tallenna yhdestä sovelluksesta"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Tallenna koko näyttö"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kun tallennat koko näyttöä, kaikki näytöllä näkyvä sisältö tallennetaan. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kun tallennat sovellusta, kaikki sovelluksessa näkyvä tai toistettu sisältö tallennetaan. Ole siis varovainen, kun lisäät salasanoja, maksutietoja, viestejä, kuvia, audiota tai videoita."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Tallenna näyttö"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Nykyinen sovellus"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Saavutettavuus"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Pikanäppäimet"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pikahaut"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ei hakutuloksia"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Tiivistyskuvake"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Laajennuskuvake"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"tai"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vetokahva"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 288be34..5ea58ce 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Enregistrer votre écran?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Enregistrer une appli"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Enregistrer l\'écran entier"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Lorsque vous enregistrez l\'intégralité de votre écran, tout ce qui s\'affiche sur votre écran est enregistré. Par conséquent, soyez prudent avec les mots de passe, les détails du mode de paiement, les messages, les photos et les contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Lorsque vous enregistrez une appli, tout ce qui est affiché ou lu dans cette appli est enregistré. Par conséquent, soyez prudent avec les mots de passe, les détails du mode de paiement, les messages, les photos et les contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Enregistrer l\'écran"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Appli actuelle"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilité"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Raccourcis-clavier"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Recherchez des raccourcis"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Aucun résultat de recherche"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 9d9f3f9..2be31b1 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Enregistrer l\'écran ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Enregistrer une appli"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Enregistrer tout l\'écran"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Lorsque vous enregistrez l\'intégralité de votre écran, tout ce qui s\'y affiche est enregistré. Faites donc attention aux éléments tels que les mots de passe, les détails du mode de paiement, les messages, les photos, et les contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Lorsque vous enregistrez une appli, tout ce qui est affiché ou lu dans celle-ci est enregistré. Faites donc attention aux éléments tels que les mots de passe, détails de mode de paiement, messages, photos et contenus audio et vidéo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Enregistrer l\'écran"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Appli actuelle"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilité"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Raccourcis clavier"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Raccourcis de recherche"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Aucun résultat de recherche"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icône Réduire"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icône Développer"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Poignée de déplacement"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 1430bc2..2bd0b30 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Queres gravar a túa pantalla?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Gravar unha aplicación"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gravar pantalla completa"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Cando gravas a pantalla completa, recóllese todo o que se mostra nela. Recomendámosche que teñas coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como co contido de audio e de vídeo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Cando gravas unha aplicación, recóllese todo o que se mostra ou reproduce nela. Recomendámosche que teñas coidado con determinada información, como os contrasinais, os detalles de pago, as mensaxes e as fotos, así como co contido de audio e de vídeo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Gravar pantalla"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicación actual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atallos de teclado"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atallos de busca"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Non hai resultados de busca"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona de contraer"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona de despregar"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Controlador de arrastre"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index bc2739f..becca8f 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"તમારી સ્ક્રીન રેકોર્ડ કરીએ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"એક ઍપ રેકોર્ડ કરો"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"પૂર્ણ સ્ક્રીન રેકોર્ડ કરો"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"જ્યારે તમે તમારી પૂર્ણ સ્ક્રીન રેકોર્ડ કરી રહ્યાં હો, ત્યારે તમારી સ્ક્રીન પર બતાવવામાં આવતી હોય તેવી બધી વસ્તુ રેકોર્ડ કરવામાં આવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"જ્યારે તમે કોઈ ઍપને રેકોર્ડ કરી રહ્યાં હો, ત્યારે એ ઍપમાં બતાવવામાં કે ચલાવવામાં આવતી હોય તેવી બધી વસ્તુ રેકોર્ડ કરવામાં આવે છે. તેથી પાસવર્ડ, ચુકવણીની વિગતો, મેસેજ, ફોટા અને ડિવાઇસ પર વાગી રહેલા ઑડિયો તથા વીડિયો જેવી બાબતોને લઈને સાવચેત રહો."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"સ્ક્રીન રેકોર્ડ કરો"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"બધુ સાફ કરો"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"મેનેજ કરો"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ઇતિહાસ"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"નોટિફિકેશનના સેટિંગ"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"નોટિફિકેશનનો ઇતિહાસ"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"નવા"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"સાઇલન્ટ"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"નોટિફિકેશન"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"હાલની ઍપ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ઍક્સેસિબિલિટી"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"કીબોર્ડ શૉર્ટકટ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"શૉર્ટકટ શોધો"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"કોઈ શોધ પરિણામો નથી"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\'નાનું કરો\'નું આઇકન"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\'મોટું કરો\'નું આઇકન"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"અથવા"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ઑબ્જેક્ટ ખેંચવાનું હૅન્ડલ"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index f9a1fa5..a6fc05e 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"क्या आपको स्क्रीन रिकॉर्ड करनी है?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"एक ऐप्लिकेशन की रिकॉर्डिंग करें"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"पूरी स्क्रीन रिकॉर्ड करें"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"पूरी स्क्रीन रिकॉर्ड करते समय, स्क्रीन पर दिखने वाली हर चीज़ रिकॉर्ड की जाती है. इसलिए पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज,  डिवाइस पर चल रहे ऑडियो और वीडियो, और फ़ोटो जैसी चीज़ों को लेकर सावधानी बरतें."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"किसी ऐप्लिकेशन को रिकॉर्ड करने के दौरान, उस पर दिख रहा कॉन्टेंट या चल रहा मीडिया दूसरी स्क्रीन पर भी रिकॉर्ड होता है. इसलिए, रिकॉर्ड करते समय पासवर्ड, पेमेंट के तरीके की जानकारी, मैसेज, फ़ोटो, ऑडियो, और वीडियो को लेकर सावधानी बरतें."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"स्क्रीन रिकॉर्ड करें"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"मौजूदा ऐप्लिकेशन"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"सुलभता"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"कीबोर्ड शॉर्टकट"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"सर्च शॉर्टकट"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"खोज का कोई नतीजा नहीं मिला"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"छोटा करने का आइकॉन"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"बड़ा करने का आइकॉन"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"या"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"खींचकर छोड़ने वाला हैंडल"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 4a17fdc..cb7a193 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Želite li snimati zaslon?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Snimanje jedne aplikacije"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Snimanje cijelog zaslona"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kad snimate cijeli zaslon, snima se sve što se prikazuje na zaslonu. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kad snimate aplikaciju, snima se sve što se prikazuje ili reproducira u toj aplikaciji. Stoga pazite na stvari kao što su zaporke, podaci o plaćanju, poruke, fotografije te audio i videozapisi."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Snimanje zaslona"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Trenutačna aplikacija"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pristupačnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tipkovni prečaci"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Prečaci za pretraživanje"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nema rezultata pretraživanja"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za sažimanje"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za proširivanje"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ili"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Marker za povlačenje"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index ff5339f..b09947f 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Rögzíti a képernyőt?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Egyetlen alkalmazás rögzítése"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Teljes képernyő rögzítése"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"A teljes képernyő rögzítése esetén a képernyőn megjelenő minden tartalom rögzítésre kerül. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Alkalmazás rögzítésekor az adott alkalmazásban megjelenített vagy lejátszott minden tartalom rögzítésre kerül. Ezért legyen elővigyázatos a jelszavakkal, a fizetési adatokkal, az üzenetekkel, a fotókkal, valamint a hang- és videófelvételekkel."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Képernyő rögzítése"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Jelenlegi alkalmazás"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Kisegítő lehetőségek"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Billentyűparancsok"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Billentyűparancsok keresése"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nincsenek keresési találatok"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Összecsukás ikon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Kibontás ikon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"vagy"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Fogópont"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 8fb1347..174b526 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Տեսագրե՞լ ձեր էկրանը"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Տեսագրել մեկ հավելված"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Տեսագրել ամբողջ էկրանը"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Երբ դուք տեսագրում եք ամբողջ էկրանը, էկրանին ցուցադրվող ամեն ինչ տեսագրվում է։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Երբ դուք որևէ հավելված եք տեսագրում, հավելվածում ցուցադրվող կամ նվագարկվող ամեն ինչ տեսագրվում է։ Ուստի ուշադիր եղեք այնպիսի բաների հետ, ինչպիսիք են գաղտնաբառերը, վճարային տվյալները, հաղորդագրությունները, լուսանկարները, աուդիո և վիդեո բովանդակությունը։"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Տեսագրել էկրանը"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Այս հավելվածը"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Հատուկ գործառույթներ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Ստեղնային դյուրանցումներ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Դյուրանցումների որոնում"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Որոնման արդյունքներ չկան"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ծալել պատկերակը"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ծավալել պատկերակը"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"կամ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Տեղափոխման նշիչ"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 0b316be..e706b27 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Rekam layar Anda?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Rekam satu aplikasi"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Rekam seluruh layar"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Saat Anda merekam seluruh layar, semua hal yang ditampilkan di layar akan direkam. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Jika Anda merekam aplikasi, semua hal yang ditampilkan atau diputar di aplikasi tersebut akan direkam. Jadi, berhati-hatilah saat memasukkan sandi, detail pembayaran, pesan, foto, audio, dan video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Rekam layar"</string>
@@ -409,7 +411,7 @@
     <string name="quick_settings_onehanded_label" msgid="2416537930246274991">"Mode satu tangan"</string>
     <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Alat bantu dengar"</string>
     <string name="quick_settings_hearing_devices_connected" msgid="6519069502397037781">"Aktif"</string>
-    <string name="quick_settings_hearing_devices_disconnected" msgid="8907061223998176187">"Terputus"</string>
+    <string name="quick_settings_hearing_devices_disconnected" msgid="8907061223998176187">"Tidak terhubung"</string>
     <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Alat bantu dengar"</string>
     <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Sambungkan perangkat baru"</string>
     <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Klik untuk menyambungkan perangkat baru"</string>
@@ -1190,7 +1192,7 @@
     <string name="media_output_dialog_group" msgid="5571251347877452212">"Grup"</string>
     <string name="media_output_dialog_single_device" msgid="3102758980643351058">"1 perangkat dipilih"</string>
     <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> perangkat dipilih"</string>
-    <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(terputus)"</string>
+    <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(tidak terhubung)"</string>
     <string name="media_output_dialog_connect_failed" msgid="3080972621975339387">"Tidak dapat beralih. Ketuk untuk mencoba lagi."</string>
     <string name="media_output_dialog_pairing_new" msgid="5098212763195577270">"Hubungkan perangkat"</string>
     <string name="media_output_dialog_launch_app_text" msgid="1527413319632586259">"Buka aplikasi untuk mentransmisikan sesi ini."</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplikasi Saat Ini"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Aksesibilitas"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Pintasan keyboard"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pintasan penelusuran"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Tidak ada hasil penelusuran"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikon ciutkan"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikon luaskan"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handel geser"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index 700ae58..26729106 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Viltu taka upp skjáinn?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Taka upp eitt forrit"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Taka upp allan skjáinn"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Þegar þú tekur upp allan skjáinn verður allt sem er sýnilegt á skjánum tekið upp. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og vídeó."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Þegar þú tekur upp forrit verður allt sem er sýnilegt eða spilað í forritinu tekið upp. Passaðu því upp á aðgangsorð, greiðsluupplýsingar, skilaboð, myndir, hljóð og vídeó."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Taka upp skjá"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Hreinsa allt"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Stjórna"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Ferill"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Tilkynningastillingar"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Tilkynningaferill"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nýtt"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Hljóðlaust"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Tilkynningar"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Núverandi forrit"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Aðgengi"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Flýtilyklar"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Leitarflýtileiðir"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Engar leitarniðurstöður"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Minnka tákn"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Stækka tákn"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eða"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Dragkló"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index e7734fa..fd56c5b 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Registrare lo schermo?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Registra un\'app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Registra l\'intero schermo"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quando registri l\'intero schermo, tutto ciò che viene mostrato sullo schermo viene registrato. Presta quindi attenzione a password, dati di pagamento, messaggi, foto, audio e video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quando registri un\'app, tutto ciò che viene mostrato o riprodotto al suo interno viene registrato. Presta quindi attenzione a password, dati di pagamento, messaggi, foto, audio e video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Registra lo schermo"</string>
@@ -136,17 +138,14 @@
     <string name="screenrecord_stop_dialog_message_specific_app" msgid="5995770227684523244">"Al momento stai registrando <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="screenrecord_stop_dialog_button" msgid="2883812564938194350">"Interrompi registrazione"</string>
     <string name="share_to_app_chip_accessibility_label" msgid="4210256229976947065">"Condivisione dello schermo in corso"</string>
-    <!-- no translation found for share_to_app_chip_accessibility_label_generic (5517431657924536133) -->
-    <skip />
+    <string name="share_to_app_chip_accessibility_label_generic" msgid="5517431657924536133">"Condivisione di contenuti in corso…"</string>
     <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"Vuoi interrompere la condivisione dello schermo?"</string>
-    <!-- no translation found for share_to_app_stop_dialog_title_generic (9079161538135843648) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_title_generic" msgid="9079161538135843648">"Interrompere la condivisione?"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app" msgid="522823522115375414">"Al momento stai condividendo l\'intero schermo con <xliff:g id="HOST_APP_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen" msgid="5090115386271179270">"Al momento stai condividendo l\'intero schermo con un\'app"</string>
     <string name="share_to_app_stop_dialog_message_single_app_specific" msgid="5923772039347985172">"Al momento stai condividendo <xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_single_app_generic" msgid="6681016774654578261">"Al momento stai condividendo un\'app"</string>
-    <!-- no translation found for share_to_app_stop_dialog_message_generic (7622174291691249392) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_message_generic" msgid="7622174291691249392">"Al momento stai condividendo contenuti con un\'app"</string>
     <string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Interrompi condivisione"</string>
     <string name="cast_screen_to_other_device_chip_accessibility_label" msgid="4687917476203009885">"Trasmissione dello schermo in corso…"</string>
     <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Vuoi interrompere la trasmissione?"</string>
@@ -521,10 +520,8 @@
     <string name="communal_widget_picker_title" msgid="1953369090475731663">"Widget della schermata di blocco"</string>
     <string name="communal_widget_picker_description" msgid="490515450110487871">"Chiunque può visualizzare i widget sulla tua schermata di blocco, anche se il tablet è bloccato."</string>
     <string name="accessibility_action_label_unselect_widget" msgid="1041811747619468698">"deseleziona widget"</string>
-    <!-- no translation found for accessibility_action_label_shrink_widget (8259511040536438771) -->
-    <skip />
-    <!-- no translation found for accessibility_action_label_expand_widget (9190524260912211759) -->
-    <skip />
+    <string name="accessibility_action_label_shrink_widget" msgid="8259511040536438771">"Riduci altezza"</string>
+    <string name="accessibility_action_label_expand_widget" msgid="9190524260912211759">"Aumenta altezza"</string>
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widget della schermata di blocco"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Per aprire un\'app utilizzando un widget, dovrai verificare la tua identità. Inoltre tieni presente che chiunque può vederlo, anche quando il tablet è bloccato. Alcuni widget potrebbero non essere stati progettati per la schermata di blocco e potrebbe non essere sicuro aggiungerli qui."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"Ok"</string>
@@ -1413,9 +1410,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App corrente"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibilità"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Scorciatoie da tastiera"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Scorciatoie per la ricerca"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nessun risultato di ricerca"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icona Comprimi"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icona Espandi"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"oppure"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Punto di trascinamento"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 553eaae..384c680 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"להקליט את המסך?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"הקלטה של אפליקציה אחת"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"הקלטה של כל המסך"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"כשמקליטים את כל המסך, כל מה שמופיע במסך מוקלט. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"כשמקליטים אפליקציה, כל מה שרואים או מפעילים בה מוקלט. מומלץ להיזהר עם סיסמאות, פרטי תשלום, הודעות, תמונות, אודיו וסרטונים."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"הקלטת המסך"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ניקוי הכול"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ניהול"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"היסטוריה"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"הגדרות של התראות"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"היסטוריית ההתראות"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"התראות חדשות"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"מצב שקט"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"התראות"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"האפליקציה הנוכחית"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"נגישות"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"מקשי קיצור"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"קיצורי דרך לחיפוש"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"אין תוצאות חיפוש"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"סמל הכיווץ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"סמל ההרחבה"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"או"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"נקודת האחיזה לגרירה"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index a6fffee..a0a3b2b 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"画面を録画しますか?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"1 つのアプリを録画"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"画面全体を録画"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"画面全体を録画すると、画面に表示されるものがすべて録画されます。パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"アプリを録画すると、そのアプリで表示または再生される内容がすべて録画されます。パスワード、お支払いの詳細、メッセージ、写真、音声、動画などの情報にご注意ください。"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"画面を録画"</string>
@@ -1411,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"現在のアプリ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ユーザー補助"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"キーボード ショートカット"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"検索ショートカット"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"検索結果がありません"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"閉じるアイコン"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"開くアイコン"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"または"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ドラッグ ハンドル"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 9608253..471957e 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"გსურთ თქვენი ეკრანის ჩაწერა?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ერთი აპის ჩაწერა"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"მთლიანი ეკრანის ჩაწერა"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"მთლიანი ეკრანის ჩაწერისას ჩაიწერება ყველაფერი, რაც თქვენს ეკრანზე გამოჩნდება. ამიტომ სიფრთხილე გამოიჩინეთ ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"აპის ჩაწერისას ჩაიწერება ყველაფერი, რაც ამ აპში გამოჩნდება ან დაიკვრება. ამიტომ სიფრთხილე გამოიჩინეთ ისეთ ინფორმაციასთან, როგორიცაა პაროლები, გადახდის დეტალები, შეტყობინებები, ფოტოები, აუდიო და ვიდეო."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ეკრანის ჩაწერა"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"მიმდინარე აპი"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"მისაწვდომობა"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"კლავიატურის მალსახმობები"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ძიების მალსახმობები"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ძიების შედეგები არ არის"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ხატულის ჩაკეცვა"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ხატულის გაფართოება"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ან"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"სახელური ჩავლებისთვის"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index 0456fa8..3dbfd86 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Қолданба экранын жазасыз ба?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Бір қолданба экранын жазу"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Бүкіл экранды жазу"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Бүкіл экранды жазған кезде, онда көрінетін барлық нәрсе жазылады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Қолданбаны жазған кезде, онда көрінетін не ойнатылатын барлық нәрсе жазылады. Сондықтан құпия сөздерді, төлем туралы мәліметті, хабарларды немесе басқа құпия ақпаратты енгізген кезде сақ болыңыз."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Экранды жазу"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Қолданыстағы қолданба"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Арнайы мүмкіндіктер"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Перне тіркесімдері"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Іздеу жылдам пәрмендері"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Іздеу нәтижелері жоқ."</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жию белгішесі"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жаю белгішесі"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"немесе"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Сүйрейтін тетік"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index 3c33531..b2c8977 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ថត​អេក្រង់​របស់អ្នកឬ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ថត​កម្មវិធី​ទោល"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ថតអេក្រង់ទាំងមូល"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"នៅពេល​អ្នកកំពុង​ថតអេក្រង់​ទាំងមូល​របស់អ្នក អ្វីគ្រប់យ៉ាង​ដែលបង្ហាញ​នៅលើ​អេក្រង់​របស់អ្នក​ត្រូវបាន​ថត។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"នៅពេលអ្នក​កំពុង​ថតកម្មវិធី​ណាមួយ អ្វីគ្រប់យ៉ាង​ដែលបង្ហាញ ឬចាក់​នៅក្នុង​កម្មវិធីនោះ​ត្រូវបាន​ថត។ ដូច្នេះ សូមប្រុងប្រយ័ត្នចំពោះអ្វីៗដូចជា ពាក្យសម្ងាត់ ព័ត៌មានលម្អិតអំពីការទូទាត់ប្រាក់ សារ រូបថត ព្រមទាំងសំឡេង និងវីដេអូ។"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ថត​អេក្រង់"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"កម្មវិធីបច្ចុប្បន្ន"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ភាពងាយស្រួល"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ផ្លូវកាត់​ក្ដារ​ចុច"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ផ្លូវ​កាត់ការស្វែងរក"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"គ្មាន​លទ្ធផល​ស្វែងរក​ទេ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"រូបតំណាង \"បង្រួម\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"រូបតំណាង \"ពង្រីក\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ឬ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ដង​អូស"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index dca2e4b..20c3676 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಬೇಕೇ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ಒಂದು ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ನಿಮ್ಮ ಸಂಪೂರ್ಣ ಸ್ಕ್ರೀನ್‌ ಅನ್ನು ನೀವು ರೆಕಾರ್ಡ್ ಮಾಡುತ್ತಿರುವಾಗ, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ ಮೇಲೆ ಗೋಚರಿಸುವ ಎಲ್ಲವನ್ನೂ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಮತ್ತು ಆಡಿಯೋ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಬಗ್ಗೆ ಜಾಗರೂಕರಾಗಿರಿ."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ನೀವು ಆ್ಯಪ್ ಅನ್ನು ರೆಕಾರ್ಡ್ ಮಾಡುವಾಗ, ಆ ಆ್ಯಪ್‌ನಲ್ಲಿ ತೋರಿಸಿರುವ ಅಥವಾ ಪ್ಲೇ ಮಾಡಿದ ಎಲ್ಲವನ್ನೂ ರೆಕಾರ್ಡ್ ಮಾಡಲಾಗುತ್ತದೆ. ಆದ್ದರಿಂದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, ಪಾವತಿ ವಿವರಗಳು, ಸಂದೇಶಗಳು, ಫೋಟೋಗಳು ಮತ್ತು ಆಡಿಯೋ ಮತ್ತು ವೀಡಿಯೊದಂತಹ ವಿಷಯಗಳ ಬಗ್ಗೆ ಜಾಗರೂಕರಾಗಿರಿ."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ಸ್ಕ್ರೀನ್ ರೆಕಾರ್ಡ್ ಮಾಡಿ"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ನಿರ್ವಹಿಸಿ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ಇತಿಹಾಸ"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"ನೋಟಿಫಿಕೇಶನ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"ನೋಟಿಫಿಕೇಶನ್ ಇತಿಹಾಸ"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"ಹೊಸತು"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"ನಿಶ್ಶಬ್ದ"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"ಅಧಿಸೂಚನೆಗಳು"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ಪ್ರಸ್ತುತ ಆ್ಯಪ್"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ಆ್ಯಕ್ಸೆಸಿಬಿಲಿಟಿ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ಕೀಬೋರ್ಡ್ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ಹುಡುಕಾಟದ ಶಾರ್ಟ್‌ಕಟ್‌ಗಳು"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ಯಾವುದೇ ಹುಡುಕಾಟ ಫಲಿತಾಂಶಗಳಿಲ್ಲ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ಕುಗ್ಗಿಸುವ ಐಕಾನ್"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ವಿಸ್ತೃತಗೊಳಿಸುವ ಐಕಾನ್"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ಅಥವಾ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ಡ್ರ್ಯಾಗ್‌ ಹ್ಯಾಂಡಲ್‌"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 8674dc3..35dc245 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"화면을 녹화하시겠습니까?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"단일 앱 녹화"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"전체 화면 녹화"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"전체 화면을 녹화하면 화면에 표시되는 모든 항목이 녹화됩니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"앱을 녹화하면 앱에 표시되거나 앱에서 재생되는 모든 항목이 녹화됩니다. 따라서 비밀번호, 결제 세부정보, 메시지, 사진, 오디오 및 동영상 등이 노출되지 않도록 주의하세요."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"화면 녹화"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"모두 지우기"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"관리"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"기록"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"알림 설정"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"알림 기록"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"새 알림"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"무음"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"알림"</string>
@@ -1413,15 +1413,21 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"현재 앱"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"접근성"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"단축키"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"검색 바로가기"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"검색 결과 없음"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"접기 아이콘"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"확장 아이콘"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"또는"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"드래그 핸들"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"키보드 설정"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"키보드를 사용하여 이동"</string>
-    <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"단축키 알아보기"</string>
+    <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"단축키에 관해 알아보세요."</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"터치패드를 사용하여 이동"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"터치패드 동작 알아보기"</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"키보드와 터치패드를 사용하여 이동"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 363342a..72e86cc 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Экранды жаздырасызбы?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Бир колдонмону жаздыруу"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Бүтүндөй экранды жаздыруу"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Бүтүндөй экранды жаздырганда, андагы нерселердин баары видеого түшүп калат. Андыктан этият болуп, сырсөздөр, төлөм ыкмалары, билдирүүлөр, сүрөттөр, аудио жана видео материалдар сыяктуу купуя нерселерди көрсөтүп албаңыз."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Колдонмону жаздырганда ал колдонмодо көрсөтүлүп же ойнотулуп жаткан нерселер жаздырылат. Андыктан сырсөздөрдү, төлөмдүн чоо-жайын, билдирүүлөрдү, сүрөттөрдү, аудио жана видеону көрсөтүп албаңыз."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Экранды жаздыруу"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Учурдагы колдонмо"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Атайын мүмкүнчүлүктөр"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Ыкчам баскычтар"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Ыкчам баскычтарды издөө"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Эч нерсе табылган жок"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Жыйыштыруу сүрөтчөсү"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Жайып көрсөтүү сүрөтчөсү"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"же"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Cүйрөө маркери"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 54c2c6b..f6fe3cc 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ບັນທຶກໜ້າຈໍຂອງທ່ານບໍ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ບັນທຶກແອັບດຽວ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ບັນທຶກໝົດໜ້າຈໍ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ເມື່ອທ່ານບັນທຶກໝົດໜ້າຈໍຂອງທ່ານ, ລະບົບຈະບັນທຶກທຸກສິ່ງທີ່ສະແດງຢູ່ໜ້າຈໍຂອງທ່ານ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ, ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ເມື່ອທ່ານບັນທຶກແອັບ, ລະບົບຈະບັນທຶກທຸກສິ່ງທີ່ສະແດງ ຫຼື ຫຼິ້ນຢູ່ໃນແອັບນັ້ນ. ດັ່ງນັ້ນ, ໃຫ້ລະມັດລະວັງສິ່ງຕ່າງໆ ເຊັ່ນ: ລະຫັດຜ່ານ, ລາຍລະອຽດການຈ່າຍເງິນ, ຂໍ້ຄວາມ, ຮູບພາບ, ພ້ອມທັງສຽງ ແລະ ວິດີໂອ."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ບັນທຶກໜ້າຈໍ"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ແອັບປັດຈຸບັນ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ການຊ່ວຍເຂົ້າເຖິງ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ຄີລັດ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ທາງລັດການຊອກຫາ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ບໍ່ມີຜົນການຊອກຫາ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ໄອຄອນຫຍໍ້ລົງ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ໄອຄອນຂະຫຍາຍ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ຫຼື"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ບ່ອນຈັບລາກ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index dca26a4..70a0063 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Įrašyti ekraną?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Įrašyti vieną programą"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Įrašyti visą ekraną"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kai įrašote visą ekraną, įrašomas visas ekrane rodomas turinys. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kai įrašote programą, įrašomas visas toje programoje rodomas ar leidžiamas turinys. Todėl būkite atsargūs naudodami slaptažodžius, išsamią mokėjimo metodo informaciją, pranešimus, nuotraukas ir garso bei vaizdo įrašus."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Įrašyti ekraną"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Esama programa"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pritaikomumas"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Spartieji klavišai"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Paieškos šaukiniai"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nėra jokių paieškos rezultatų"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sutraukimo piktograma"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Išskleidimo piktograma"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"arba"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkimo rankenėlė"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index c5ba0ad..360afad 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vai ierakstīt ekrānu?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Ierakstīt vienu lietotni"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Ierakstīt visu ekrānu"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Ierakstot visu ekrānu, viss, kas redzams ekrānā, tiek ierakstīts. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Ierakstot lietotni, tiek ierakstīts viss attiecīgajā lietotnē rādītais vai atskaņotais. Tāpēc piesardzīgi apejieties ar parolēm, maksājumu informāciju, ziņojumiem, fotoattēliem un audio un video saturu."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ierakstīt ekrānu"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Pašreizējā lietotne"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Pieejamība"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Īsinājumtaustiņi"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Meklēšanas saīsnes"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nav meklēšanas rezultātu"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Sakļaušanas ikona"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Izvēršanas ikona"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"vai"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Vilkšanas turis"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 65e958e..ebd62a9 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Да се снима екранот?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Снимање на една апликација"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Снимање на целиот екран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Додека го снимате целиот екран, сѐ што е прикажано на екранот се снима. Затоа, бидете внимателни со лозинките, деталите за плаќање, пораките, фотографиите и аудиото и видеото."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Додека снимате апликација, може да се сними сѐ што се прикажува или пушта во таа апликација. Затоа, бидете внимателни со лозинките, деталите за плаќање, пораките, фотографиите и аудиото и видеото."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Снимај го екранот"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Избриши сѐ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управувајте"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Поставки за известувања"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Историја на известувања"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Нов"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Безгласно"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Известувања"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Тековна апликација"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Пристапност"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Кратенки од тастатура"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Кратенки за пребарување"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нема резултати од пребарување"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за собирање"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширување"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Рачка за влечење"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index e10ecff..6152fce 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"നിങ്ങളുടെ സ്ക്രീൻ റെക്കോർഡ് ചെയ്യണോ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ഒരു ആപ്പ് റെക്കോർഡ് ചെയ്യുക"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"സ്ക്രീൻ പൂർണ്ണമായി റെക്കോർഡ് ചെയ്യുക"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"നിങ്ങളുടെ സ്ക്രീൻ പൂർണ്ണമായി റെക്കോർഡ് ചെയ്യുമ്പോൾ, സ്ക്രീനിൽ ദൃശ്യമാകുന്ന എല്ലാം റെക്കോർഡ് ചെയ്യപ്പെടും. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"നിങ്ങളുടെ ആപ്പ് റെക്കോർഡ് ചെയ്യുമ്പോൾ, ആ ആപ്പിൽ കാണിക്കുന്നതോ പ്ലേ ചെയ്യുന്നതോ ആയ എല്ലാ കാര്യങ്ങളും റെക്കോർഡ് ചെയ്യപ്പെടും. അതിനാൽ പാസ്‍വേഡുകൾ, പേയ്‌മെന്റ് വിശദാംശങ്ങൾ, സന്ദേശങ്ങൾ, ഫോട്ടോകൾ, ഓഡിയോ, വീഡിയോ എന്നിവ പോലുള്ള കാര്യങ്ങൾ നൽകുമ്പോൾ സൂക്ഷിക്കുക."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"സ്ക്രീൻ റെക്കോർഡ് ചെയ്യുക"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"എല്ലാം മായ്‌ക്കുക"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"മാനേജ് ചെയ്യുക"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ചരിത്രം"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"അറിയിപ്പ് ക്രമീകരണം"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"അറിയിപ്പ് ചരിത്രം"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"പുതിയത്"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"നിശബ്‌ദം"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"അറിയിപ്പുകൾ"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"നിലവിലെ ആപ്പ്"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ഉപയോഗസഹായി"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"കീബോഡ് കുറുക്കുവഴികൾ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"തിരയൽ കുറുക്കുവഴികൾ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"തിരയൽ ഫലങ്ങളൊന്നുമില്ല"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ചുരുക്കൽ ഐക്കൺ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"വികസിപ്പിക്കൽ ഐക്കൺ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"അല്ലെങ്കിൽ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"വലിച്ചിടുന്നതിനുള്ള ഹാൻഡിൽ"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index cb5f054..aafc8c61 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Дэлгэцээ бичих үү?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Нэг аппыг бичих"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Бүтэн дэлгэцийг бичих"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Таныг бүтэн дэлгэцээ бичиж байхад дэлгэц дээр тань харуулж буй аливаа зүйлийг бичдэг. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио, видео зэрэг зүйлд болгоомжтой хандаарай."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Таныг апп бичиж байхад тухайн аппад харуулж эсвэл тоглуулж буй аливаа зүйлийг бичдэг. Тиймээс нууц үг, төлбөрийн дэлгэрэнгүй, мессеж, зураг, аудио, видео зэрэг зүйлд болгоомжтой хандаарай."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Дэлгэцийг бичих"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Бүгдийг арилгах"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Удирдах"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Түүх"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Мэдэгдлийн тохиргоо"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Мэдэгдлийн түүх"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Шинэ"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Чимээгүй"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Мэдэгдлүүд"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Одоогийн апп"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Хандалт"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Товчлуурын шууд холбоос"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Товчлолууд хайх"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ямар ч хайлтын илэрц байхгүй"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Хураах дүрс тэмдэг"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Дэлгэх дүрс тэмдэг"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"эсвэл"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Чирэх бариул"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 8aa0afa..fbbb167 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"तुमची स्क्रीन रेकॉर्ड करायची आहे का?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"एक अ‍ॅप रेकॉर्ड करा"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"पूर्ण स्क्रीन रेकॉर्ड करा"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"तुम्ही तुमची पूर्ण स्क्रीन रेकॉर्ड करता, तेव्हा तुमच्या स्क्रीनवर दाखवलेली कोणतीही गोष्टी रेकॉर्ड केली जाते. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"तुम्ही अ‍ॅप रेकॉर्ड करता, तेव्हा त्या अ‍ॅपमध्ये दाखवलेली किंवा प्ले केलेली कोणतीही गोष्ट रेकॉर्ड केली जाते. त्यामुळे पासवर्ड, पेमेंट तपशील, मेसेज, फोटो आणि ऑडिओ व व्हिडिओ यांसारख्या गोष्टींबाबत सावधगिरी बाळगा."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"स्क्रीन रेकॉर्ड करा"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"सर्व साफ करा"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"व्यवस्थापित करा"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"इतिहास"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"नोटिफिकेशन सेटिंग्ज"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"नोटिफिकेशन इतिहास"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"नवीन"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"सायलंट"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"सूचना"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"सध्याचे अ‍ॅप"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"अ‍ॅक्सेसिबिलिटी"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"कीबोर्ड शॉर्टकट"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"शोधण्यासाठी शॉर्टकट"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"कोणतेही शोध परिणाम नाहीत"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"कोलॅप्स करा आयकन"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"विस्तार करा आयकन"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"किंवा"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्रॅग हॅंडल"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index d0071f4d..087c8a6 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Rakam skrin anda?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Rakam satu apl"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Rakam seluruh skrin"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Apabila anda merakam seluruh skrin anda, apa-apa sahaja yang dipaparkan pada skrin anda akan dirakam. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Apabila anda merakam apl, apa-apa sahaja yang dipaparkan atau dimainkan dalam apl tersebut akan dirakam. Oleh hal yang demikian, berhati-hati dengan perkara seperti kata laluan, butiran pembayaran, mesej, foto dan audio serta video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Rakam skrin"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Apl Semasa"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Kebolehaksesan"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Pintasan papan kekunci"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Pintasan carian"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Tiada hasil carian"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kuncupkan ikon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Kembangkan ikon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"atau"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Pemegang seret"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index fd4c147..d456bca 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ဖန်သားပြင်ကို ရိုက်ကူးမလား။"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"အက်ပ်တစ်ခုကို ရိုက်ကူးရန်"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ဖန်သားပြင်တစ်ခုလုံးကို ရိုက်ကူးရန်"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"သင့်ဖန်သားပြင်တစ်ခုလုံး ရိုက်ကူးနေချိန်တွင် ဖန်သားပြင်တွင် ပြထားသည့် အရာအားလုံးကို ရိုက်ကူးသည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"အက်ပ်ကို ရိုက်ကူးနေချိန်တွင် ယင်းအက်ပ်တွင် ပြထားသော (သို့) ဖွင့်ထားသော အရာအားလုံးကို ရိုက်ကူးသည်။ စကားဝှက်၊ ငွေပေးချေမှု အချက်အလက်၊ မက်ဆေ့ဂျ်၊ ဓာတ်ပုံ၊ အသံနှင့် ဗီဒီယိုကဲ့သို့ အရာများကို ဂရုစိုက်ပါ။"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ဖန်သားပြင်ကို ရိုက်ကူးရန်"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"လက်ရှိအက်ပ်"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"အများသုံးနိုင်မှု"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"လက်ကွက်ဖြတ်လမ်းများ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ရှာဖွေစာလုံး ဖြတ်လမ်း"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ရှာဖွေမှုရလဒ် မရှိပါ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"လျှော့ပြရန် သင်္ကေတ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ပိုပြရန် သင်္ကေတ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"သို့မဟုတ်"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ဖိဆွဲအထိန်း"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 1c30ca7..1fbb35b0 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vil du ta opp skjermen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Ta opp én app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Ta opp hele skjermen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Når du tar opp hele skjermen, blir alt som vises på skjermen, tatt opp. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Når du tar opp en app, blir alt som vises eller spilles av i appen, tatt opp. Derfor bør du være forsiktig med for eksempel passord, betalingsopplysninger, meldinger, bilder, lyd og video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ta opp skjermen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktiv app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Tilgjengelighet"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Hurtigtaster"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Snarveier til søk"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ingen søkeresultater"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Skjul-ikon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Vis-ikon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Håndtak"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index f7ac880..381118a 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"तपाईंको स्क्रिन रेकर्ड गर्ने हो?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"एउटा एप रेकर्ड गर्नुहोस्"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"पूरै स्क्रिन रेकर्ड गर्नुहोस्"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"तपाईंले आफ्नो पूरै स्क्रिन रेकर्ड गरिरहेका बेला तपाईंको स्क्रिनमा देखाइने सबै सामग्री रेकर्ड गरिन्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"तपाईंले यो एप रेकर्ड गरिरहेका बेला यो एपमा देखाइने वा प्ले गरिने सबै सामग्री रेकर्ड गरिन्छ। त्यसैले पासवर्ड, भुक्तानीसम्बन्धी विवरण, म्यासेज, फोटो र अडियो तथा भिडियो जस्ता कुरा हेर्दा वा प्ले गर्दा सावधानी अपनाउनुहोला।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"स्क्रिन रेकर्ड गर्नुहोस्"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"हालको एप"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"सर्वसुलभता"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"किबोर्डका सर्टकटहरू"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"खोजका सर्टकटहरू"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"कुनै पनि खोज परिणाम भेटिएन"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"\"कोल्याप्स गर्नुहोस्\" आइकन"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"\"एक्स्पान्ड गर्नुहोस्\" आइकन"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"वा"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ड्र्याग ह्यान्डल"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 4cea605..8ff59ee 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Je scherm opnemen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Eén app opnemen"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Hele scherm opnemen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Als je je hele scherm opneemt, wordt alles opgenomen wat op je scherm wordt getoond. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Als je een app opneemt, wordt alles opgenomen wat wordt getoond of afgespeeld in die app. Wees daarom voorzichtig met bijvoorbeeld wachtwoorden, betalingsgegevens, berichten, foto\'s, en audio en video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Scherm opnemen"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Alles wissen"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Beheren"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Geschiedenis"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Instellingen voor meldingen"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Meldings­geschiedenis"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nieuw"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Stil"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Meldingen"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Huidige app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Toegankelijkheid"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Sneltoetsen"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Snelkoppelingen voor zoekopdrachten"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Geen zoekresultaten"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Icoon voor samenvouwen"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Icoon voor uitvouwen"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"of"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handgreep voor slepen"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index bbbea61..6086440 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ଆପଣଙ୍କ ସ୍କ୍ରିନକୁ ରେକର୍ଡ କରିବେ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ଗୋଟିଏ ଆପ ରେକର୍ଡ କରନ୍ତୁ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ ରେକର୍ଡ କରନ୍ତୁ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ଆପଣ ଆପଣଙ୍କର ସମ୍ପୂର୍ଣ୍ଣ ସ୍କ୍ରିନ ରେକର୍ଡ କରିବା ସମୟରେ, ଆପଣଙ୍କ ସ୍କ୍ରିନରେ ଦେଖାଯାଉଥିବା ସବୁକିଛି ରେକର୍ଡ ହୋଇଥାଏ। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ଆପଣ ଏକ ଆପ ରେକର୍ଡ କରିବା ସମୟରେ, ସେହି ଆପରେ ଦେଖାଯାଉଥିବା କିମ୍ବା ପ୍ଲେ ହେଉଥିବା ସବୁକିଛି ରେକର୍ଡ ହୋଇଥାଏ। ତେଣୁ ପାସୱାର୍ଡ, ପେମେଣ୍ଟ ବିବରଣୀ, ମେସେଜ, ଫଟୋ ଏବଂ ଅଡିଓ ଓ ଭିଡିଓ ପରି ବିଷୟଗୁଡ଼ିକ ପ୍ରତି ସତର୍କ ରୁହନ୍ତୁ।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ସ୍କ୍ରିନ ରେକର୍ଡ କରନ୍ତୁ"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ବର୍ତ୍ତମାନର ଆପ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ଆକ୍ସେସିବିଲିଟୀ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"କୀବୋର୍ଡ ସର୍ଟକଟ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ସର୍ଚ୍ଚ ସର୍ଟକଟ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"କୌଣସି ସର୍ଚ୍ଚ ଫଳାଫଳ ନାହିଁ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ଆଇକନକୁ ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ଆଇକନକୁ ବିସ୍ତାର କରନ୍ତୁ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"କିମ୍ବା"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ଡ୍ରାଗ ହେଣ୍ଡେଲ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 0170a71..b99986a 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ਕੀ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰਨਾ ਹੈ?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ਇੱਕ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰੋ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ਜਦੋਂ ਤੁਸੀਂ ਆਪਣੀ ਪੂਰੀ ਸਕ੍ਰੀਨ ਨੂੰ ਰਿਕਾਰਡ ਕਰ ਰਹੇ ਹੁੰਦੇ ਹੋ, ਤਾਂ ਤੁਹਾਡੀ ਸਕ੍ਰੀਨ \'ਤੇ ਦਿਖਾਈ ਜਾ ਰਹੀ ਹਰ ਚੀਜ਼ ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਨਾਲ ਹੀ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ਜਦੋਂ ਤੁਸੀਂ ਕਿਸੇ ਐਪ ਨੂੰ ਰਿਕਾਰਡ ਕਰ ਰਹੇ ਹੁੰਦੇ ਹੋ, ਤਾਂ ਉਸ ਐਪ ਵਿੱਚ ਦਿਖਾਈ ਜਾਂ ਚਲਾਈ ਜਾ ਰਹੀ ਹਰ ਚੀਜ਼ ਨੂੰ ਰਿਕਾਰਡ ਕੀਤਾ ਜਾਂਦਾ ਹੈ। ਇਸ ਲਈ ਪਾਸਵਰਡਾਂ, ਭੁਗਤਾਨ ਵੇਰਵਿਆਂ, ਸੁਨੇਹਿਆਂ, ਫ਼ੋਟੋਆਂ ਅਤੇ ਆਡੀਓ ਅਤੇ ਵੀਡੀਓ ਵਰਗੀਆਂ ਚੀਜ਼ਾਂ ਵਾਸਤੇ ਸਾਵਧਾਨ ਰਹੋ।"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"ਸਕ੍ਰੀਨ ਰਿਕਾਰਡ ਕਰੋ"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"ਇਤਿਹਾਸ"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"ਸੂਚਨਾ ਸੈਟਿੰਗਾਂ"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"ਸੂਚਨਾ ਇਤਿਹਾਸ"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"ਨਵੀਆਂ"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"ਸ਼ਾਂਤ"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"ਸੂਚਨਾਵਾਂ"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ਮੌਜੂਦਾ ਐਪ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ਪਹੁੰਚਯੋਗਤਾ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"ਕੀ-ਬੋਰਡ ਸ਼ਾਰਟਕੱਟ"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ਖੋਜ ਸੰਬੰਧੀ ਸ਼ਾਰਟਕੱਟ"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ਕੋਈ ਖੋਜ ਨਤੀਜਾ ਨਹੀਂ ਮਿਲਿਆ"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ਪ੍ਰਤੀਕ ਨੂੰ ਸਮੇਟੋ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ਪ੍ਰਤੀਕ ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ਜਾਂ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ਘਸੀਟਣ ਵਾਲਾ ਹੈਂਡਲ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 33a743f..88177ac 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Nagrywać ekran?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Nagrywaj jedną aplikację"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Nagrywaj cały ekran"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kiedy nagrywasz cały ekran, nagrane zostanie wszystko, co jest na nim widoczne. Dlatego uważaj na hasła, dane do płatności, wiadomości, zdjęcia, nagrania audio czy filmy."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kiedy nagrywasz aplikację, wszystko, co jest w niej wyświetlane lub odtwarzane, zostaje nagrane. Dlatego zachowaj ostrożność w zakresie haseł, danych do płatności, wiadomości, zdjęć, audio i filmów."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Nagrywaj ekran"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Bieżąca aplikacja"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ułatwienia dostępu"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Skróty klawiszowe"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Skróty do wyszukiwania"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Brak wyników wyszukiwania"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zwijania"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozwijania"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"lub"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Uchwyt do przeciągania"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 8041760..e67c168 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Gravar a tela?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Gravar um app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gravar a tela toda"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quando você grava a tela toda, tudo o que aparece nela é registrado. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quando você grava um app, todas as informações visíveis ou abertas nele ficam registradas. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Gravar a tela"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App atual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos do teclado"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atalhos de pesquisa"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado de pesquisa"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index f000998..30e16f0 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Gravar o ecrã?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Gravar uma app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gravar o ecrã inteiro"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quando está a gravar o ecrã inteiro, tudo o que é apresentado no ecrã é gravado. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quando está a gravar uma app, tudo o que é apresentado ou reproduzido nessa app é gravado. Por isso, tenha cuidado com, por exemplo, palavras-passe, detalhes de pagamento, mensagens, fotos, áudio e vídeo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Gravar ecrã"</string>
@@ -136,17 +138,14 @@
     <string name="screenrecord_stop_dialog_message_specific_app" msgid="5995770227684523244">"Neste momento, está a gravar a app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string>
     <string name="screenrecord_stop_dialog_button" msgid="2883812564938194350">"Parar gravação"</string>
     <string name="share_to_app_chip_accessibility_label" msgid="4210256229976947065">"A partilhar o ecrã"</string>
-    <!-- no translation found for share_to_app_chip_accessibility_label_generic (5517431657924536133) -->
-    <skip />
+    <string name="share_to_app_chip_accessibility_label_generic" msgid="5517431657924536133">"A partilhar conteúdo"</string>
     <string name="share_to_app_stop_dialog_title" msgid="9212915050910250438">"Parar a partilha do ecrã?"</string>
-    <!-- no translation found for share_to_app_stop_dialog_title_generic (9079161538135843648) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_title_generic" msgid="9079161538135843648">"Parar a partilha?"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen_with_host_app" msgid="522823522115375414">"Neste momento, está a partilhar todo o seu ecrã com a app <xliff:g id="HOST_APP_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_entire_screen" msgid="5090115386271179270">"Neste momento, está a partilhar todo o seu ecrã com uma app"</string>
     <string name="share_to_app_stop_dialog_message_single_app_specific" msgid="5923772039347985172">"Neste momento, está a partilhar a app <xliff:g id="APP_BEING_SHARED_NAME">%1$s</xliff:g>"</string>
     <string name="share_to_app_stop_dialog_message_single_app_generic" msgid="6681016774654578261">"Neste momento, está a partilhar uma app"</string>
-    <!-- no translation found for share_to_app_stop_dialog_message_generic (7622174291691249392) -->
-    <skip />
+    <string name="share_to_app_stop_dialog_message_generic" msgid="7622174291691249392">"Neste momento, está a partilhar com uma app"</string>
     <string name="share_to_app_stop_dialog_button" msgid="6334056916284230217">"Parar partilha"</string>
     <string name="cast_screen_to_other_device_chip_accessibility_label" msgid="4687917476203009885">"A transmitir o ecrã"</string>
     <string name="cast_to_other_device_stop_dialog_title" msgid="7836517190930357326">"Parar a transmissão?"</string>
@@ -521,10 +520,8 @@
     <string name="communal_widget_picker_title" msgid="1953369090475731663">"Widgets do ecrã de bloqueio"</string>
     <string name="communal_widget_picker_description" msgid="490515450110487871">"Todos podem pode ver widgets no ecrã de bloqueio, mesmo com o tablet bloqueado."</string>
     <string name="accessibility_action_label_unselect_widget" msgid="1041811747619468698">"desmarcar widget"</string>
-    <!-- no translation found for accessibility_action_label_shrink_widget (8259511040536438771) -->
-    <skip />
-    <!-- no translation found for accessibility_action_label_expand_widget (9190524260912211759) -->
-    <skip />
+    <string name="accessibility_action_label_shrink_widget" msgid="8259511040536438771">"Diminuir altura"</string>
+    <string name="accessibility_action_label_expand_widget" msgid="9190524260912211759">"Aumentar altura"</string>
     <string name="communal_widgets_disclaimer_title" msgid="1150954395585308868">"Widgets do ecrã de bloqueio"</string>
     <string name="communal_widgets_disclaimer_text" msgid="1423545475160506349">"Para abrir uma app através de um widget, vai ter de validar a sua identidade. Além disso, tenha em atenção que qualquer pessoa pode ver os widgets, mesmo quando o tablet estiver bloqueado. Alguns widgets podem não se destinar ao ecrã de bloqueio e pode ser inseguro adicioná-los aqui."</string>
     <string name="communal_widgets_disclaimer_button" msgid="4423059765740780753">"OK"</string>
@@ -1413,9 +1410,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App atual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos de teclado"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atalhos de pesquisa"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado da pesquisa"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone de reduzir"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone de expandir"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Indicador para arrastar"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 8041760..e67c168 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Gravar a tela?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Gravar um app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Gravar a tela toda"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Quando você grava a tela toda, tudo o que aparece nela é registrado. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Quando você grava um app, todas as informações visíveis ou abertas nele ficam registradas. Portanto, tenha cuidado com senhas, detalhes de pagamento, mensagens, fotos, áudios e vídeos."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Gravar a tela"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"App atual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Acessibilidade"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Atalhos do teclado"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Atalhos de pesquisa"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Nenhum resultado de pesquisa"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ícone \"Fechar\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ícone \"Abrir\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ou"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Alça de arrastar"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 57a2a0c..de7f8da 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Înregistrezi ecranul?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Înregistrează o aplicație"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Înregistrează tot ecranul"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Când înregistrezi întregul ecran, se înregistrează tot ce apare pe ecran. Prin urmare, ai grijă cu parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Când înregistrezi o aplicație, se înregistrează tot ce se afișează sau se redă în aplicație. Prin urmare, ai grijă cu parolele, detaliile de plată, mesajele, fotografiile și conținutul audio și video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Înregistrează ecranul"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplicația actuală"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accesibilitate"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Comenzi rapide de la tastatură"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Comenzi directe de căutare"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Niciun rezultat al căutării"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Pictograma de restrângere"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Pictograma de extindere"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"sau"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ghidaj de tragere"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 507f709..a007685 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Начать запись экрана?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Записывать одно приложение"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Записывать весь экран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Во время записи всего экрана все данные и действия, которые на нем показываются, попадают на видео. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Во время записи приложения все данные и действия, которые показываются в его окне, попадают на видео. Поэтому будьте осторожны с паролями, сведениями о способах оплаты, сообщениями, фотографиями, аудио- и видеозаписями."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Запись экрана"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Очистить все"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Настроить"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"История"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Настройки уведомлений"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"История уведомлений"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Новое"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Без звука"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Уведомления"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Это приложение"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Специальные возможности"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Быстрые клавиши"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Найти быстрые клавиши"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ничего не найдено"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок \"Свернуть\""</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок \"Развернуть\""</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер перемещения"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index fb9be30..ae9c4d0 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"ඔබේ තිරය පටිගත කරන්න ද?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"එක් යෙදුමක් පටිගත කරන්න"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"සම්පූර්ණ තිරය පටිගත කරන්න"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ඔබ ඔබේ සම්පූර්ණ තිරය පටිගත කරන විට, ඔබේ තිරයේ පෙන්වන ඕනෑම දෙයක් වාර්තා වේ. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ඔබ යෙදුමක් පටිගත කරන විට, එම යෙදුමේ පෙන්වන හෝ වාදනය කරන ඕනෑම දෙයක් වාර්තා වේ. ඒ නිසා මුරපද, ගෙවීම් විස්තර, පණිවුඩ, ඡායාරූප, සහ ශ්‍රව්‍ය සහ දෘශ්‍ය වැනි දේවල් පිළිබඳ ප්‍රවේශම් වන්න."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"තිරය පටිගත කරන්න"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"වත්මන් යෙදුම"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ප්‍රවේශ්‍යතාව"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"යතුරු පුවරු කෙටි මං"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"කෙටි මං සොයන්න"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"සෙවීම් ප්‍රතිඵල නැත"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"හැකුළුම් නිරූපකය"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"දිගහැරීම් නිරූපකය"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"හෝ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"ඇදීම් හැඬලය"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 42a2613..c223566 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Chcete nahrávať obrazovku?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Nahrávať jednu aplikáciu"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Nahrávať celú obrazovku"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Pri nahrávaní celej obrazovky sa zaznamená všetko, čo sa na nej zobrazuje. Preto venujte pozornosť položkám, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Pri nahrávaní aplikácie sa zaznamená všetko, čo sa v nej zobrazuje alebo prehráva. Preto venujte pozornosť položkám, ako sú heslá, platobné údaje, správy, fotky a zvuk či video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Nahrávať obrazovku"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Vymazať všetko"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Spravovať"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"História"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Nastavenia upozornení"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"História upozornení"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Nové"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Tichý"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Upozornenia"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuálna aplikácia"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Dostupnosť"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klávesové skratky"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Vyhľadávacie odkazy"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Žiadne výsledky vyhľadávania"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona zbalenia"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona rozbalenia"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"alebo"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Presúvadlo"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 4ca7b24..15f1b3c 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Želite posneti zaslon?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Snemanje ene aplikacije"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Snemanje celotnega zaslona"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Pri snemanju celotnega zaslona se posname vse, kar je prikazano na zaslonu. Zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Pri snemanju aplikacije se posname vse, kar je prikazano ali predvajano v tej aplikaciji. Zato bodite previdni z gesli, podatki za plačilo, sporočili, fotografijami ter z zvokom in videom."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Snemanje zaslona"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Trenutna aplikacija"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Dostopnost"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Bližnjične tipke"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Bližnjice za iskanje"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ni rezultatov iskanja"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona za strnitev"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona za razširitev"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ali"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Ročica za vlečenje"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index ddea921..7f74487 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Të regjistrohet ekrani?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Regjistro një aplikacion"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Regjistro të gjithë ekranin"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kur regjistron të gjithë ekranin, regjistrohet çdo gjë e shfaqur në ekranin tënd. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kur regjistron një aplikacion, regjistrohet çdo gjë që shfaqet ose luhet në atë aplikacion. Prandaj, ki kujdes me gjërat si fjalëkalimet, detajet e pagesave, mesazhet, fotografitë, si dhe audion dhe videon."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Regjistro ekranin"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aplikacioni aktual"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qasshmëria"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Shkurtoret e tastierës"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Kërko për shkurtoret"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Asnjë rezultat kërkimi"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikona e palosjes"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikona e zgjerimit"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"ose"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Doreza e zvarritjes"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 5936cd7..a1952d4 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Желите да снимите екран?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Сними једну апликацију"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Сними цео екран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Када снимате цео екран, снима се све што је на њему. Зато пазите на лозинке, информације о плаћању, поруке, слике, аудио и видео садржај."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Када снимате апликацију, снима се сав садржај који се приказује или пушта у њој. Зато пазите на лозинке, информације о плаћању, поруке, слике, аудио и видео садржај."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Сними екран"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Обриши све"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Управљај"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Историја"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Подешавања обавештења"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Историја обавештења"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Ново"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Нечујно"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Обавештења"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Актуелна апликација"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Приступачност"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Тастерске пречице"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Пречице претраге"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нема резултата претраге"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Икона за скупљање"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Икона за проширивање"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"или"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер за превлачење"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 45e150a..b898eaf 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Vill du spela in det som visas på skärmen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Spela in en app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Spela in hela skärmen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"När du spelar in hela skärmen spelas allt som visas på skärmen in. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton, ljud och video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"När du spelar in en app spelas allt som visas eller spelas upp i appen in. Var försiktig med sådant som lösenord, betalningsuppgifter, meddelanden, foton, ljud och video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Spela in skärmen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Aktuell app"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Tillgänglighet"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Kortkommandon"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sökgenvägar"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Inga sökresultat"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Ikonen Komprimera"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Ikonen Utöka"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"eller"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handtag"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 86ea7e4..8731337 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ungependa kurekodi skrini yako?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Rekodi programu moja"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Rekodi skrini nzima"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Unaporekodi skrini yako nzima, chochote kinachoonyeshwa kwenye skrini yako kitarekodiwa. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha, sauti na video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Unaporekodi programu, chochote kinachoonyeshwa au kuchezwa kwenye programu hiyo kitarekodiwa. Kwa hivyo kuwa mwangalifu na vitu kama vile manenosiri, maelezo ya malipo, ujumbe, picha, sauti na video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Rekodi skrini"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Programu Inayotumika Sasa"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ufikivu"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Mikato ya kibodi"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Njia mkato za kutafutia"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Hamna matokeo ya utafutaji"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Kunja aikoni"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Panua aikoni"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"au"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Aikoni ya buruta"</string>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 4007bb1..393631e 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -76,6 +76,14 @@
     <dimen name="large_dialog_width">472dp</dimen>
 
     <dimen name="large_screen_shade_header_height">42dp</dimen>
+
+    <!--
+    The horizontal distance between the shade overlay panel (both notifications and quick settings)
+    and the edge of the screen. On Compact screens in portrait orientation (< w600dp) this is
+    ignored in the shade layout, which takes up the full screen width without margins.
+    -->
+    <dimen name="shade_panel_margin_horizontal">24dp</dimen>
+
     <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
     <dimen name="hover_system_icons_container_padding_start">3dp</dimen>
     <dimen name="hover_system_icons_container_padding_end">4dp</dimen>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 93b1864..b75a218 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"உங்கள் திரையை ரெக்கார்டு செய்யவா?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ஓர் ஆப்ஸை ரெக்கார்டு செய்தல்"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"முழுத் திரையை ரெக்கார்டு செய்தல்"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"முழுத் திரையை நீங்கள் ரெக்கார்டு செய்யும்போது அதில் காட்டப்படும் அனைத்தும் ரெக்கார்டு செய்யப்படும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ஓர் ஆப்ஸை ரெக்கார்டு செய்யும்போது அதில் காட்டப்படும் அல்லது பிளே செய்யப்படும் அனைத்தும் ரெக்கார்டு செய்யப்படும். எனவே கடவுச்சொற்கள், பேமெண்ட் விவரங்கள், மெசேஜ்கள், படங்கள், ஆடியோ, வீடியோ போன்றவை குறித்துக் கவனத்துடன் இருங்கள்."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"திரையை ரெக்கார்டு செய்"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"தற்போதைய ஆப்ஸ்"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"மாற்றுத்திறன் வசதி"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"கீபோர்டு ஷார்ட்கட்கள்"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"தேடல் ஷார்ட்கட்கள்"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"தேடல் முடிவுகள் இல்லை"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"சுருக்குவதற்கான ஐகான்"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"விரிவாக்குவதற்கான ஐகான்"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"அல்லது"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"இழுப்பதற்கான ஹேண்டில்"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 4a97651..d66c656 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"మీ స్క్రీన్‌ను రికార్డ్ చేయాలా?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ఒక యాప్‌ను రికార్డ్ చేయండి"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"ఫుల్ స్క్రీన్‌ను రికార్డ్ చేయండి"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"మీ ఫుల్ స్క్రీన్‌ను మీరు రికార్డ్ చేసేటప్పుడు, మీ స్క్రీన్‌పై కనిపించేవన్నీ రికార్డ్ అవుతాయి. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"మీరు యాప్‌ను రికార్డ్ చేసేటప్పుడు, సంబంధిత యాప్‌లో కనిపించేవన్నీ లేదా ప్లే అయ్యేవన్నీ రికార్డ్ అవుతాయి. కాబట్టి పాస్‌వర్డ్‌లు, పేమెంట్ వివరాలు, మెసేజ్‌లు, ఫోటోలు, ఆడియో, ఇంకా వీడియో వంటి విషయాల్లో జాగ్రత్త వహించండి."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"స్క్రీన్‌ను రికార్డ్ చేయండి"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"అన్నీ క్లియర్ చేయండి"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"మేనేజ్ చేయండి"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"హిస్టరీ"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"నోటిఫికేషన్ సెట్టింగ్‌లు"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"నోటిఫికేషన్ హిస్టరీ"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"కొత్తవి"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"నిశ్శబ్దం"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"నోటిఫికేషన్‌లు"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"ప్రస్తుత యాప్"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"యాక్సెసిబిలిటీ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"కీబోర్డ్ షార్ట్‌కట్‌లు"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"సెర్చ్ షార్ట్‌కట్‌లు"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"సెర్చ్ ఫలితాలు ఏవీ లేవు"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"కుదించండి చిహ్నం"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"విస్తరించండి చిహ్నం"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"లేదా"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"లాగే హ్యాండిల్"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index ac027c7..b7d1a32 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"บันทึกหน้าจอไหม"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"บันทึกแอปเดียว"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"บันทึกทั้งหน้าจอ"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"ขณะบันทึกทั้งหน้าจอ ระบบจะบันทึกทุกสิ่งที่แสดงอยู่บนหน้าจอ ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"ขณะบันทึกแอป ระบบจะบันทึกทุกสิ่งที่แสดงหรือเล่นอยู่ในแอปดังกล่าว ดังนั้นโปรดระวังสิ่งต่างๆ อย่างเช่นรหัสผ่าน รายละเอียดการชำระเงิน ข้อความ รูปภาพ รวมถึงเสียงและวิดีโอ"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"บันทึกหน้าจอ"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"แอปปัจจุบัน"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"การช่วยเหลือพิเศษ"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"แป้นพิมพ์ลัด"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"ค้นหาแป้นพิมพ์ลัด"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"ไม่พบผลการค้นหา"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"ไอคอนยุบ"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"ไอคอนขยาย"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"หรือ"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"แฮนเดิลการลาก"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 09a0e040..a30a2f2 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"I-record ang iyong screen?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Mag-record ng isang app"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"I-record ang buong screen"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Kapag nire-record mo ang iyong buong screen, nire-record ang anumang ipinapakita sa screen mo. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Kapag nagre-record ka ng app, nire-record ang anumang ipinapakita o pine-play sa app na iyon. Kaya mag-ingat sa mga bagay-bagay tulad ng mga password, detalye ng pagbabayad, mensahe, larawan, at audio at video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"I-record ang screen"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Kasalukuyang App"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Accessibility"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Mga keyboard shortcut"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Mga shortcut ng paghahanap"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Walang resulta ng paghahanap"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"I-collapse ang icon"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"I-expand ang icon"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"o"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Handle sa pag-drag"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index ae9cd18..c79dfcf 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ekranınız kaydedilsin mi?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Bir uygulamayı kaydet"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Tüm ekranı kaydedin"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Tüm ekranınızı kaydettiğinizde ekranınızda gösterilen her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Bir uygulamayı kaydettiğinizde o uygulamada gösterilen veya oynatılan her şey kaydedilir. Bu nedenle şifre, ödeme ayrıntıları, mesaj, fotoğraf, ses ve video gibi öğeler konusunda dikkatli olun."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekranı kaydet"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Mevcut Uygulama"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Erişilebilirlik"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Klavye kısayolları"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Arama kısayolları"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Arama sonucu yok"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Daralt simgesi"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Genişlet simgesi"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"veya"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Sürükleme tutamacı"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 820d072..2808922 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Записати відео з екрана?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Записувати один додаток"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Записувати весь екран"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Коли ви записуєте вміст усього екрана, на відео потрапляє все, що на ньому відображається. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Коли ви записуєте додаток, на відео потрапляє все, що відображається або відтворюється в ньому. Тому будьте уважні з паролями, повідомленнями, фотографіями, аудіо, відео, платіжною інформацією тощо."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Записувати вміст екрана"</string>
@@ -1413,15 +1415,21 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Поточний додаток"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Доступність"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Комбінації клавіш"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Комбінації клавіш для пошуку"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Нічого не знайдено"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Значок згортання"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Значок розгортання"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"або"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Маркер переміщення"</string>
     <string name="shortcut_helper_keyboard_settings_buttons_label" msgid="6720967595915985259">"Налаштування клавіатури"</string>
     <string name="launch_keyboard_tutorial_notification_title" msgid="8849933155160522519">"Навігація за допомогою клавіатури"</string>
-    <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Комбінації клавіш: докладніше"</string>
+    <string name="launch_keyboard_tutorial_notification_content" msgid="2880339951512757918">"Дізнайтеся більше про комбінації клавіш"</string>
     <string name="launch_touchpad_tutorial_notification_title" msgid="2243780062772196901">"Навігація за допомогою сенсорної панелі"</string>
     <string name="launch_touchpad_tutorial_notification_content" msgid="7931085031240753226">"Жести для сенсорної панелі: докладніше"</string>
     <string name="launch_keyboard_touchpad_tutorial_notification_title" msgid="1940023776496198762">"Навігація за допомогою клавіатури й сенсорної панелі"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index b98abbd..b72464ca 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"آپ کی اسکرین ریکارڈ کریں؟"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"ایک ایپ ریکارڈ کریں"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"پوری اسکرین کو ریکارڈ کریں"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"جب آپ اپنی پوری اسکرین کو ریکارڈ کر رہے ہوتے ہیں تو آپ کی اسکرین پر دکھائی گئی ہر چیز ریکارڈ کی جاتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"جب آپ کسی ایپ کو ریکارڈ کر رہے ہوتے ہیں تو اس ایپ میں دکھائی گئی یا چلائی گئی ہر چیز ریکارڈ کی جاتی ہے۔ لہذا، پاس ورڈز، ادائیگی کی تفصیلات، پیغامات، تصاویر، ساتھ ہی آڈیو اور ویڈیو جیسی چیزوں کے سلسلے میں محتاط رہیں۔"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"اسکرین ریکارڈ کریں"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"موجودہ ایپ"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"ایکسیسبیلٹی"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"کی بورڈ شارٹ کٹس"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"تلاش کے شارٹ کٹس"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"تلاش کا کوئی نتیجہ نہیں ہے"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"آئیکن سکیڑیں"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"آئیکن پھیلائیں"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"یا"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"گھسیٹنے کا ہینڈل"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index 3309772..7f6275c 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ekran yozib olinsinmi?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Bitta ilovani yozib olish"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Butun ekranni yozib olish"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Butun ekranni yozib olishda ekranda koʻrsatilgan barcha axborotlar yozib olinadi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Ilovani yozib olishda ilova koʻrsatilgan yoki ijro etilgan barcha axborotlar yozib olinadi. Shu sababli parollar, toʻlov tafsilotlari, xabarlar, suratlar, audio va video chiqmasligi uchun ehtiyot boʻling."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ekranni yozib olish"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Joriy ilova"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Qulayliklar"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Tezkor tugmalar"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Tezkor tugmalar qidiruvi"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Hech narsa topilmadi"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Yigʻish belgisi"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Yoyish belgisi"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"yoki"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Surish dastagi"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index f404a2b..388ebb8 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Ghi màn hình?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Ghi một ứng dụng"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Ghi toàn màn hình"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Khi bạn ghi toàn màn hình, mọi nội dung trên màn hình của bạn đều được ghi. Vì vậy, hãy thận trọng để không làm lộ thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Khi bạn ghi một ứng dụng, mọi nội dung xuất hiện hoặc phát trong ứng dụng đó sẽ đều được ghi. Vì vậy, hãy thận trọng để không làm lộ thông tin như mật khẩu, thông tin thanh toán, tin nhắn, ảnh, âm thanh và video."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Ghi màn hình"</string>
@@ -581,10 +583,8 @@
     <string name="clear_all_notifications_text" msgid="348312370303046130">"Xóa tất cả"</string>
     <string name="manage_notifications_text" msgid="6885645344647733116">"Quản lý"</string>
     <string name="manage_notifications_history_text" msgid="57055985396576230">"Lịch sử"</string>
-    <!-- no translation found for notification_settings_button_description (2441994740884163889) -->
-    <skip />
-    <!-- no translation found for notification_history_button_description (1578657591405033383) -->
-    <skip />
+    <string name="notification_settings_button_description" msgid="2441994740884163889">"Cài đặt thông báo"</string>
+    <string name="notification_history_button_description" msgid="1578657591405033383">"Nhật ký thông báo"</string>
     <string name="notification_section_header_incoming" msgid="850925217908095197">"Mới"</string>
     <string name="notification_section_header_gentle" msgid="6804099527336337197">"Im lặng"</string>
     <string name="notification_section_header_alerting" msgid="5581175033680477651">"Thông báo"</string>
@@ -1413,9 +1413,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"Ứng dụng hiện tại"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Hỗ trợ tiếp cận"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Phím tắt"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Lối tắt tìm kiếm"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Không có kết quả tìm kiếm nào"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Biểu tượng Thu gọn"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Biểu tượng Mở rộng"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"hoặc"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Nút kéo"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 24c9f40..b463900 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"要录制屏幕吗?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"录制单个应用"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"录制整个屏幕"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"录制整个屏幕时,屏幕上显示的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款信息、消息、照片、音频、视频等。"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"录制单个应用时,该应用中显示或播放的所有内容均会被录制。因此,请务必小心操作,谨防泄露密码、付款信息、消息、照片、音频、视频等。"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"录制屏幕"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"当前应用"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"无障碍功能"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"键盘快捷键"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜索快捷键"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"无搜索结果"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收起图标"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展开图标"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖动手柄"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index e7ebe5c..9118591 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"要錄影螢幕畫面嗎?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"錄影一個應用程式"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"錄影整個螢幕畫面"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"當你錄影整個螢幕畫面時,系統會錄影螢幕畫面上顯示的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"當你錄影應用程式時,系統會錄影該應用程式中顯示或播放的任何內容。因此,請謹慎處理密碼、付款資料、訊息、相片、音訊和影片等。"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"錄影螢幕畫面"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"目前的應用程式"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"無障礙功能"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"鍵盤快速鍵"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"沒有相符的搜尋結果"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 0f298dc..2189cbf 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"要錄製畫面嗎?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"錄製單一應用程式"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"錄製整個畫面"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"錄製整個畫面時,系統會錄下畫面上的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"當你錄製應用程式畫面時,系統會錄下該應用程式顯示或播放的所有內容。因此,請謹慎處理密碼、付款資料、訊息、相片和影音內容等資訊。"</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"錄製畫面"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"目前的應用程式"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"無障礙"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"鍵盤快速鍵"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"搜尋快速鍵"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"找不到相符的搜尋結果"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"收合圖示"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"展開圖示"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"或"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"拖曳控點"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 444ca04..05b5040 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -112,6 +112,8 @@
     <string name="screenrecord_permission_dialog_title" msgid="7415261783188749730">"Rekhoda isikrini sakho?"</string>
     <string name="screenrecord_permission_dialog_option_text_single_app" msgid="1996450687814647583">"Rekhoda i-app eyodwa"</string>
     <string name="screenrecord_permission_dialog_option_text_entire_screen" msgid="2794896384693120020">"Rekhoda sonke isikrini"</string>
+    <!-- no translation found for screenrecord_permission_dialog_option_text_entire_screen_for_display (3754611651558838691) -->
+    <skip />
     <string name="screenrecord_permission_dialog_warning_entire_screen" msgid="1321758636709366068">"Uma urekhoda sonke isikrini sakho, noma yini evela esikrinini iyarekhodwa. Ngakho-ke qaphela ngezinto ezifana namaphasiwedi, imininingwane yenkokhelo, imilayezo, izithombe, nomsindo nevidiyo."</string>
     <string name="screenrecord_permission_dialog_warning_single_app" msgid="3738199712880063924">"Uma urekhoda i-app, noma yini evezwa noma edlala kuleyo app iyarekhodwa. Ngakho-ke qaphela ngezinto ezifana namaphasiwedi, imininingwane yenkokhelo, imilayezo, izithombe, nomsindo nevidiyo."</string>
     <string name="screenrecord_permission_dialog_continue_entire_screen" msgid="5557974446773486600">"Rekhoda isikrini"</string>
@@ -1413,9 +1415,15 @@
     <string name="shortcut_helper_category_current_app_shortcuts" msgid="4017840565974573628">"I-App yamanje"</string>
     <string name="shortcut_helper_category_a11y" msgid="6314444792641773464">"Ukufinyeleleka"</string>
     <string name="shortcut_helper_title" msgid="8567500639300970049">"Izinqamuleli zekhibhodi"</string>
+    <!-- no translation found for shortcut_helper_customize_mode_title (1467657117101096033) -->
+    <skip />
     <string name="shortcut_helper_search_placeholder" msgid="5488547526269871819">"Sesha izinqamuleli"</string>
     <string name="shortcut_helper_no_search_results" msgid="8554756497996692160">"Ayikho imiphumela yosesho"</string>
     <string name="shortcut_helper_content_description_collapse_icon" msgid="8028015738431664954">"Goqa isithonjana"</string>
+    <!-- no translation found for shortcut_helper_customize_button_text (3124983502748069338) -->
+    <skip />
+    <!-- no translation found for shortcut_helper_done_button_text (7249905942125386191) -->
+    <skip />
     <string name="shortcut_helper_content_description_expand_icon" msgid="1084435697860417390">"Nweba isithonjana"</string>
     <string name="shortcut_helper_key_combinations_or_separator" msgid="7082902112102125540">"noma"</string>
     <string name="shortcut_helper_content_description_drag_handle" msgid="5092426406009848110">"Hudula isibambi"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d4a52c3..c8ef093 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -383,6 +383,10 @@
     <!-- Whether to show activity indicators in the status bar -->
     <bool name="config_showActivity">false</bool>
 
+    <!-- Whether to show the opportunistic satellite icon. When true, an icon will show to indicate
+         satellite capabilities when all other connections are out of service. -->
+    <bool name="config_showOpportunisticSatelliteIcon">true</bool>
+
     <!-- Whether or not to show the notification shelf that houses the icons of notifications that
      have been scrolled off-screen. -->
     <bool name="config_showNotificationShelf">true</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index de547ad..7fa2879 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -584,6 +584,12 @@
     -->
     <dimen name="shade_panel_width">412dp</dimen>
 
+    <!--
+    The horizontal distance between the shade overlay panel (both notifications and quick settings)
+    and the edge of the screen. This is zero only on Compact screens (< sw600dp).
+    -->
+    <dimen name="shade_panel_margin_horizontal">0dp</dimen>
+
     <dimen name="brightness_mirror_height">48dp</dimen>
 
     <dimen name="volume_dialog_panel_transparent_padding_right">8dp</dimen>
@@ -2058,7 +2064,7 @@
     <!-- Volume start -->
     <dimen name="volume_dialog_background_corner_radius">30dp</dimen>
     <dimen name="volume_dialog_width">60dp</dimen>
-    <dimen name="volume_dialog_vertical_padding">6dp</dimen>
+    <dimen name="volume_dialog_vertical_padding">10dp</dimen>
     <dimen name="volume_dialog_components_spacing">8dp</dimen>
     <dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
     <dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
@@ -2072,10 +2078,10 @@
     <dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
     <dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
 
-    <dimen name="volume_dialog_ringer_container_padding">10dp</dimen>
-    <dimen name="volume_ringer_item_size">40dp</dimen>
-    <dimen name="volume_ringer_icon_size">20dp</dimen>
-    <dimen name="volume_ringer_item_radius">12dp</dimen>
-    <dimen name="volume_dialog_ringer_item_margin_bottom">8dp</dimen>
+    <dimen name="volume_dialog_ringer_horizontal_padding">10dp</dimen>
+    <dimen name="volume_dialog_ringer_drawer_button_size">40dp</dimen>
+    <dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
+    <dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
+    <dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen>
     <!-- Volume end -->
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ab64ae5..1766cdf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1797,6 +1797,7 @@
     <string name="volume_panel_spatial_audio_tracking">Head Tracking</string>
 
     <string name="volume_ringer_change">Tap to change ringer mode</string>
+    <string name="volume_ringer_mode">ringer mode</string>
 
     <!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
     <string name="volume_ringer_hint_mute">mute</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index ab45b9f..7d071cd 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -557,6 +557,20 @@
         <item name="android:showWhenLocked">true</item>
     </style>
 
+    <style name="SystemUI.Material3.Slider.Volume">
+        <item name="trackHeight">40dp</item>
+        <item name="thumbHeight">52dp</item>
+    </style>
+
+    <style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider">
+        <item name="labelStyle">@style/Widget.Material3.Slider.Label</item>
+        <item name="thumbColor">@color/slider_thumb_color</item>
+        <item name="tickColorActive">@color/slider_inactive_track_color</item>
+        <item name="tickColorInactive">@color/slider_active_track_color</item>
+        <item name="trackColorActive">@color/slider_active_track_color</item>
+        <item name="trackColorInactive">@color/slider_inactive_track_color</item>
+    </style>
+
     <style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
 
     <style name="Theme.SystemUI.Dialog" parent="@style/Theme.SystemUI.DayNightDialog">
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 46e45aa..66b3e189 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -905,7 +905,18 @@
         return lp;
     }
 
-    private WindowManager.LayoutParams getWindowLayoutBaseParams() {
+    public static WindowManager.LayoutParams getWindowLayoutBaseParams() {
+        return getWindowLayoutBaseParams(/* excludeFromScreenshots= */ true);
+    }
+
+    /**
+     * Creates the base {@link WindowManager.LayoutParams} that are used for all decoration windows.
+     *
+     * @param excludeFromScreenshots whether to set the {@link
+     *     WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY} flag.
+     */
+    public static WindowManager.LayoutParams getWindowLayoutBaseParams(
+            boolean excludeFromScreenshots) {
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -921,7 +932,7 @@
         // FLAG_SLIPPERY can only be set by trusted overlays
         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 
-        if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
+        if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS && excludeFromScreenshots) {
             lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index d1c728c..b0f5e5e 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -26,11 +26,6 @@
 @Module
 abstract class CommonDataLayerModule {
     @Binds
-    abstract fun bindConfigurationRepository(
-        impl: ConfigurationRepositoryImpl
-    ): ConfigurationRepository
-
-    @Binds
     abstract fun bindPackageChangeRepository(
         impl: PackageChangeRepositoryImpl
     ): PackageChangeRepository
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
index b36da3b..e0756fc 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.common.ui
 
 import android.content.Context
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -28,7 +29,7 @@
 /**
  * Annotates elements that provide information from the global configuration.
  *
- * The global configuration is the one associted with the main display. Secondary displays will
+ * The global configuration is the one associated with the main display. Secondary displays will
  * apply override to the global configuration. Elements annotated with this shouldn't be used for
  * secondary displays.
  */
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 2052c70..31a7fa4 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -23,13 +23,17 @@
 import androidx.annotation.DimenRes
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.wrapper.DisplayUtilsWrapper
 import dagger.Binds
 import dagger.Module
-import javax.inject.Inject
+import dagger.Provides
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
@@ -57,66 +61,62 @@
     fun getDimensionPixelSize(id: Int): Int
 }
 
-@SysUISingleton
 class ConfigurationRepositoryImpl
-@Inject
+@AssistedInject
 constructor(
-    private val configurationController: ConfigurationController,
-    private val context: Context,
+    @Assisted private val configurationController: ConfigurationController,
+    @Assisted private val context: Context,
     @Application private val scope: CoroutineScope,
     private val displayUtils: DisplayUtilsWrapper,
 ) : ConfigurationRepository {
     private val displayInfo = MutableStateFlow(DisplayInfo())
 
-    override val onAnyConfigurationChange: Flow<Unit> =
-        conflatedCallbackFlow {
-            val callback =
-                object : ConfigurationController.ConfigurationListener {
-                    override fun onUiModeChanged() {
-                        sendUpdate("ConfigurationRepository#onUiModeChanged")
-                    }
-
-                    override fun onThemeChanged() {
-                        sendUpdate("ConfigurationRepository#onThemeChanged")
-                    }
-
-                    override fun onConfigChanged(newConfig: Configuration) {
-                        sendUpdate("ConfigurationRepository#onConfigChanged")
-                    }
-
-                    fun sendUpdate(reason: String) {
-                        trySendWithFailureLogging(Unit, reason)
-                    }
+    override val onAnyConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+        val callback =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onUiModeChanged() {
+                    sendUpdate("ConfigurationRepository#onUiModeChanged")
                 }
-            configurationController.addCallback(callback)
-            awaitClose { configurationController.removeCallback(callback) }
-        }
 
-    override val onConfigurationChange: Flow<Unit> =
-        conflatedCallbackFlow {
-            val callback =
-                object : ConfigurationController.ConfigurationListener {
-                    override fun onConfigChanged(newConfig: Configuration) {
-                        trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
-                    }
+                override fun onThemeChanged() {
+                    sendUpdate("ConfigurationRepository#onThemeChanged")
                 }
-            configurationController.addCallback(callback)
-            awaitClose { configurationController.removeCallback(callback) }
-        }
 
-    override val configurationValues: Flow<Configuration> =
-            conflatedCallbackFlow {
-                val callback =
-                        object : ConfigurationController.ConfigurationListener {
-                            override fun onConfigChanged(newConfig: Configuration) {
-                                trySend(newConfig)
-                            }
-                        }
+                override fun onConfigChanged(newConfig: Configuration) {
+                    sendUpdate("ConfigurationRepository#onConfigChanged")
+                }
 
-                trySend(context.resources.configuration)
-                configurationController.addCallback(callback)
-                awaitClose { configurationController.removeCallback(callback) }
+                fun sendUpdate(reason: String) {
+                    trySendWithFailureLogging(Unit, reason)
+                }
             }
+        configurationController.addCallback(callback)
+        awaitClose { configurationController.removeCallback(callback) }
+    }
+
+    override val onConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+        val callback =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration) {
+                    trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
+                }
+            }
+        configurationController.addCallback(callback)
+        awaitClose { configurationController.removeCallback(callback) }
+    }
+
+    override val configurationValues: Flow<Configuration> = conflatedCallbackFlow {
+        val callback =
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration) {
+                    trySend(newConfig)
+                }
+            }
+
+        trySend(context.resources.configuration)
+        configurationController.addCallback(callback)
+        awaitClose { configurationController.removeCallback(callback) }
+    }
 
     override val scaleForResolution: StateFlow<Float> =
         onConfigurationChange
@@ -134,8 +134,7 @@
                     maxDisplayMode.physicalWidth,
                     maxDisplayMode.physicalHeight,
                     displayInfo.value.naturalWidth,
-                    displayInfo.value.naturalHeight
-                )
+                    displayInfo.value.naturalHeight)
             return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
         }
         return 1f
@@ -144,9 +143,40 @@
     override fun getDimensionPixelSize(@DimenRes id: Int): Int {
         return context.resources.getDimensionPixelSize(id)
     }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            context: Context,
+            configurationController: ConfigurationController
+        ): ConfigurationRepositoryImpl
+    }
 }
 
 @Module
-interface ConfigurationRepositoryModule {
-    @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+abstract class ConfigurationRepositoryModule {
+
+    /**
+     * For compatibility reasons. Ideally, only the an annotated [ConfigurationRepository] should be
+     * injected.
+     */
+    @Binds
+    @Deprecated("Use the ConfigurationRepository annotated with @GlobalConfig instead.")
+    @SysUISingleton
+    abstract fun provideDefaultConfigRepository(
+        @GlobalConfig configurationRepository: ConfigurationRepository
+    ): ConfigurationRepository
+
+    companion object {
+        @Provides
+        @GlobalConfig
+        @SysUISingleton
+        fun provideGlobalConfigRepository(
+            context: Context,
+            @GlobalConfig configurationController: ConfigurationController,
+            factory: ConfigurationRepositoryImpl.Factory
+        ): ConfigurationRepository {
+            return factory.create(context, configurationController)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index adb1ee2..eb423d6 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -32,6 +32,7 @@
 import kotlinx.coroutines.flow.onStart
 
 /** Business logic related to configuration changes. */
+// TODO: b/374267505 - Create a @ShadeDisplayWindow annotated version of this.
 @SysUISingleton
 class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index a33e0ac..44dd34a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -32,6 +32,8 @@
 import com.android.systemui.communal.shared.log.CommunalMetricsLogger
 import com.android.systemui.communal.shared.log.CommunalStatsLogProxyImpl
 import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl
 import com.android.systemui.communal.util.CommunalColors
 import com.android.systemui.communal.util.CommunalColorsImpl
 import com.android.systemui.communal.widgets.CommunalWidgetModule
@@ -65,6 +67,7 @@
             CommunalSettingsRepositoryModule::class,
             CommunalSmartspaceRepositoryModule::class,
             CommunalStartableModule::class,
+            GlanceableHubWidgetManagerModule::class,
         ]
 )
 interface CommunalModule {
@@ -91,6 +94,11 @@
         impl: CommunalSceneTransitionInteractor
     ): CoreStartable
 
+    @Binds
+    fun bindGlanceableHubMultiUserHelper(
+        impl: GlanceableHubMultiUserHelperImpl
+    ): GlanceableHubMultiUserHelper
+
     companion object {
         const val LOGGABLE_PREFIXES = "loggable_prefixes"
         const val LAUNCHER_PACKAGE = "launcher_package"
@@ -106,19 +114,14 @@
                     sceneKeys = listOf(CommunalScenes.Blank, CommunalScenes.Communal),
                     initialSceneKey = CommunalScenes.Blank,
                     navigationDistances =
-                        mapOf(
-                            CommunalScenes.Blank to 0,
-                            CommunalScenes.Communal to 1,
-                        ),
+                        mapOf(CommunalScenes.Blank to 0, CommunalScenes.Communal to 1),
                 )
             return SceneDataSourceDelegator(applicationScope, config)
         }
 
         @Provides
         @SysUISingleton
-        fun providesCommunalBackupUtils(
-            @Application context: Context,
-        ): CommunalBackupUtils {
+        fun providesCommunalBackupUtils(@Application context: Context): CommunalBackupUtils {
             return CommunalBackupUtils(context)
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
index 6cbf540..2d19b02 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.communal.CommunalSceneStartable
 import com.android.systemui.communal.log.CommunalLoggerStartable
 import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
+import com.android.systemui.dagger.qualifiers.PerUser
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -48,6 +49,7 @@
 
     @Binds
     @IntoMap
+    @PerUser
     @ClassKey(CommunalAppWidgetHostStartable::class)
     fun bindCommunalAppWidgetHostStartable(impl: CommunalAppWidgetHostStartable): CoreStartable
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/GlanceableHubWidgetManagerModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/GlanceableHubWidgetManagerModule.kt
new file mode 100644
index 0000000..8c07583
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/GlanceableHubWidgetManagerModule.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.communal.dagger
+
+import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManagerServiceInfo
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManagerServiceSupplier
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManagerServiceWatcherFactoryImpl
+import com.android.systemui.communal.widgets.ServiceWatcherFactory
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface GlanceableHubWidgetManagerModule {
+    @Binds
+    fun bindServiceSupplier(
+        impl: GlanceableHubWidgetManagerServiceSupplier
+    ): ServiceSupplier<GlanceableHubWidgetManagerServiceInfo?>
+
+    @Binds
+    fun bindServiceWatcherFactory(
+        impl: GlanceableHubWidgetManagerServiceWatcherFactoryImpl
+    ): ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
index 17f4f0c..e72088f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalDatabase.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.data.db
 
 import android.content.Context
+import android.os.Process
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import androidx.room.Database
@@ -24,6 +25,7 @@
 import androidx.room.RoomDatabase
 import androidx.room.migration.Migration
 import androidx.sqlite.db.SupportSQLiteDatabase
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelperImpl
 import com.android.systemui.res.R
 
 @Database(entities = [CommunalWidgetItem::class, CommunalItemRank::class], version = 4)
@@ -44,6 +46,11 @@
          *   new instance is created.
          */
         fun getInstance(context: Context, callback: Callback? = null): CommunalDatabase {
+            with(GlanceableHubMultiUserHelperImpl(Process.myUserHandle())) {
+                // Assert that the database is never accessed from a headless system user.
+                assertNotInHeadlessSystemUser()
+            }
+
             if (instance == null) {
                 instance =
                     Room.databaseBuilder(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
index 258480e..3d40aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/db/CommunalWidgetDao.kt
@@ -25,6 +25,7 @@
 import androidx.room.Transaction
 import androidx.room.Update
 import androidx.sqlite.db.SupportSQLiteDatabase
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.communal.nano.CommunalHubState
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.widgets.CommunalWidgetHost
@@ -39,7 +40,6 @@
 import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Callback that will be invoked when the Room database is created. Then the database will be
@@ -224,9 +224,9 @@
     ): Long {
         val widgets = getWidgetsNow()
 
-        // If rank is not specified, rank it last by finding the current maximum rank and increment
-        // by 1. If the new widget is the first widget, set the rank to 0.
-        val newRank = rank ?: widgets.keys.maxOfOrNull { it.rank + 1 } ?: 0
+        // If rank is not specified (null or less than 0), rank it last by finding the current
+        // maximum rank and increment by 1. If the new widget is the first widget, set rank to 0.
+        val newRank = rank?.takeIf { it >= 0 } ?: widgets.keys.maxOfOrNull { it.rank + 1 } ?: 0
 
         // Shift widgets after [rank], unless widget is added at the end.
         if (rank != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e164587..29569f8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -21,6 +21,7 @@
 import android.content.ComponentName
 import android.os.UserHandle
 import android.os.UserManager
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags.communalWidgetResizing
 import com.android.systemui.common.data.repository.PackageChangeRepository
 import com.android.systemui.common.shared.model.PackageInstallSession
@@ -51,11 +52,10 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Encapsulates the state of widgets for communal mode. */
 interface CommunalWidgetRepository {
-    /** A flow of information about active communal widgets stored in database. */
+    /** A flow of the list of Glanceable Hub widgets ordered by rank. */
     val communalWidgets: Flow<List<CommunalWidgetContentModel>>
 
     /**
@@ -106,8 +106,12 @@
     fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>)
 }
 
+/**
+ * The local implementation of the [CommunalWidgetRepository] that should be injected in a
+ * foreground user process.
+ */
 @SysUISingleton
-class CommunalWidgetRepositoryImpl
+class CommunalWidgetRepositoryLocalImpl
 @Inject
 constructor(
     private val appWidgetHost: CommunalAppWidgetHost,
@@ -123,7 +127,7 @@
     private val defaultWidgetPopulation: DefaultWidgetPopulation,
 ) : CommunalWidgetRepository {
     companion object {
-        const val TAG = "CommunalWidgetRepository"
+        const val TAG = "CommunalWidgetRepositoryLocalImpl"
     }
 
     private val logger = Logger(logBuffer, TAG)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index b502fb1..824edcb 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -17,11 +17,24 @@
 
 package com.android.systemui.communal.data.repository
 
-import dagger.Binds
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import dagger.Lazy
 import dagger.Module
+import dagger.Provides
 
 @Module
 interface CommunalWidgetRepositoryModule {
-    @Binds
-    fun communalWidgetRepository(impl: CommunalWidgetRepositoryImpl): CommunalWidgetRepository
+    companion object {
+        @Provides
+        fun provideCommunalWidgetRepository(
+            localImpl: Lazy<CommunalWidgetRepositoryLocalImpl>,
+            remoteImpl: Lazy<CommunalWidgetRepositoryRemoteImpl>,
+            helper: GlanceableHubMultiUserHelper,
+        ): CommunalWidgetRepository {
+            // Provide an implementation based on the current user.
+            return if (helper.glanceableHubHsumFlagEnabled && helper.isInHeadlessSystemUser())
+                remoteImpl.get()
+            else localImpl.get()
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryRemoteImpl.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryRemoteImpl.kt
new file mode 100644
index 0000000..6682186
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryRemoteImpl.kt
@@ -0,0 +1,85 @@
+/*
+ * 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.communal.data.repository
+
+import android.content.ComponentName
+import android.os.UserHandle
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManager
+import com.android.systemui.communal.widgets.WidgetConfigurator
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+
+/**
+ * The remote implementation of the [CommunalWidgetRepository] that should be injected in a headless
+ * system user process. This implementation receives widget data from and routes requests to the
+ * remote service in the foreground user.
+ */
+@SysUISingleton
+class CommunalWidgetRepositoryRemoteImpl
+@Inject
+constructor(
+    @Background private val bgScope: CoroutineScope,
+    private val glanceableHubWidgetManager: GlanceableHubWidgetManager,
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
+) : CommunalWidgetRepository {
+
+    init {
+        // This is the implementation for the headless system user. For the foreground user
+        // implementation see [CommunalWidgetRepositoryLocalImpl].
+        glanceableHubMultiUserHelper.assertInHeadlessSystemUser()
+    }
+
+    override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
+        glanceableHubWidgetManager.widgets
+
+    override fun addWidget(
+        provider: ComponentName,
+        user: UserHandle,
+        rank: Int?,
+        configurator: WidgetConfigurator?,
+    ) {
+        bgScope.launch { glanceableHubWidgetManager.addWidget(provider, user, rank, configurator) }
+    }
+
+    override fun deleteWidget(widgetId: Int) {
+        bgScope.launch { glanceableHubWidgetManager.deleteWidget(widgetId) }
+    }
+
+    override fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) {
+        bgScope.launch { glanceableHubWidgetManager.updateWidgetOrder(widgetIdToRankMap) }
+    }
+
+    override fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) {
+        bgScope.launch {
+            glanceableHubWidgetManager.resizeWidget(appWidgetId, spanY, widgetIdToRankMap)
+        }
+    }
+
+    override fun restoreWidgets(oldToNewWidgetIdMap: Map<Int, Int>) {
+        throw IllegalStateException("Restore widgets should be performed on a foreground user")
+    }
+
+    override fun abortRestoreWidgets() {
+        throw IllegalStateException("Restore widgets should be performed on a foreground user")
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index dc24805..602fe30 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.dagger.SysUISingleton
@@ -108,7 +107,6 @@
     keyguardInteractor: KeyguardInteractor,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     communalSettingsInteractor: CommunalSettingsInteractor,
-    private val appWidgetHost: CommunalAppWidgetHost,
     private val editWidgetsActivityStarter: EditWidgetsActivityStarter,
     private val userTracker: UserTracker,
     private val activityStarter: ActivityStarter,
@@ -451,7 +449,6 @@
                             appWidgetId = widget.appWidgetId,
                             rank = widget.rank,
                             providerInfo = widget.providerInfo,
-                            appWidgetHost = appWidgetHost,
                             inQuietMode = isQuietModeEnabled(widget.providerInfo.profile),
                             size = CommunalContentSize.toSize(widget.spanY),
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 4c2c094..30f580e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -23,7 +23,6 @@
 import android.graphics.Bitmap
 import android.widget.RemoteViews
 import com.android.systemui.communal.shared.model.CommunalContentSize
-import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import java.util.UUID
 
 /** Encapsulates data for a communal content. */
@@ -60,7 +59,6 @@
             override val appWidgetId: Int,
             override val rank: Int,
             val providerInfo: AppWidgetProviderInfo,
-            val appWidgetHost: CommunalAppWidgetHost,
             val inQuietMode: Boolean,
             override val size: CommunalContentSize,
         ) : WidgetContent {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.aidl b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.aidl
new file mode 100644
index 0000000..a215698e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.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 com.android.systemui.communal.shared.model;
+
+parcelable CommunalWidgetContentModel;
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
index 0c9ea78..f23347d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalWidgetContentModel.kt
@@ -19,21 +19,60 @@
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
 import android.graphics.Bitmap
+import android.os.Parcel
+import android.os.Parcelable
 import android.os.UserHandle
 
 /** Encapsulates data for a communal widget. */
-sealed interface CommunalWidgetContentModel {
+sealed interface CommunalWidgetContentModel : Parcelable {
     val appWidgetId: Int
     val rank: Int
     val spanY: Int
 
+    // Used for distinguishing subtypes when reading from a parcel.
+    val type: Int
+
     /** Widget is ready to display */
     data class Available(
         override val appWidgetId: Int,
         val providerInfo: AppWidgetProviderInfo,
         override val rank: Int,
         override val spanY: Int,
-    ) : CommunalWidgetContentModel
+    ) : CommunalWidgetContentModel {
+
+        override val type = TYPE_AVAILABLE
+
+        constructor(
+            parcel: Parcel
+        ) : this(
+            parcel.readInt(),
+            requireNotNull(parcel.readTypedObject(AppWidgetProviderInfo.CREATOR)),
+            parcel.readInt(),
+            parcel.readInt(),
+        )
+
+        override fun writeToParcel(parcel: Parcel, flags: Int) {
+            parcel.writeInt(type)
+            parcel.writeInt(appWidgetId)
+            parcel.writeTypedObject(providerInfo, flags)
+            parcel.writeInt(rank)
+            parcel.writeInt(spanY)
+        }
+
+        override fun describeContents(): Int {
+            return 0
+        }
+
+        companion object CREATOR : Parcelable.Creator<Available> {
+            override fun createFromParcel(parcel: Parcel): Available {
+                return Available(parcel)
+            }
+
+            override fun newArray(size: Int): Array<Available?> {
+                return arrayOfNulls(size)
+            }
+        }
+    }
 
     /** Widget is pending installation */
     data class Pending(
@@ -43,5 +82,61 @@
         val icon: Bitmap?,
         val user: UserHandle,
         override val spanY: Int,
-    ) : CommunalWidgetContentModel
+    ) : CommunalWidgetContentModel {
+
+        override val type = TYPE_PENDING
+
+        constructor(
+            parcel: Parcel
+        ) : this(
+            parcel.readInt(),
+            parcel.readInt(),
+            requireNotNull(parcel.readTypedObject(ComponentName.CREATOR)),
+            parcel.readTypedObject(Bitmap.CREATOR),
+            requireNotNull(parcel.readTypedObject(UserHandle.CREATOR)),
+            parcel.readInt(),
+        )
+
+        override fun writeToParcel(parcel: Parcel, flags: Int) {
+            parcel.writeInt(type)
+            parcel.writeInt(appWidgetId)
+            parcel.writeInt(rank)
+            parcel.writeTypedObject(componentName, flags)
+            parcel.writeTypedObject(icon, flags)
+            parcel.writeTypedObject(user, flags)
+            parcel.writeInt(spanY)
+        }
+
+        override fun describeContents(): Int {
+            return 0
+        }
+
+        companion object CREATOR : Parcelable.Creator<Pending> {
+            override fun createFromParcel(parcel: Parcel): Pending {
+                return Pending(parcel)
+            }
+
+            override fun newArray(size: Int): Array<Pending?> {
+                return arrayOfNulls(size)
+            }
+        }
+    }
+
+    // Used for distinguishing subtypes when reading from a parcel.
+    companion object CREATOR : Parcelable.Creator<CommunalWidgetContentModel> {
+        private const val TYPE_AVAILABLE = 0
+        private const val TYPE_PENDING = 1
+
+        override fun createFromParcel(parcel: Parcel): CommunalWidgetContentModel {
+            return when (val type = parcel.readInt()) {
+                TYPE_AVAILABLE -> Available(parcel)
+                TYPE_PENDING -> Pending(parcel)
+                else -> throw IllegalArgumentException("Unknown type: $type")
+            }
+        }
+
+        override fun newArray(size: Int): Array<CommunalWidgetContentModel?> {
+            return arrayOfNulls(size)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelper.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelper.kt
new file mode 100644
index 0000000..ef6a9ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelper.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.communal.shared.model
+
+import android.os.UserHandle
+import android.os.UserManager
+import com.android.systemui.Flags.secondaryUserWidgetHost
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/** Helper for multi-user / HSUM related functionality for the Glanceable Hub. */
+interface GlanceableHubMultiUserHelper {
+    /** Whether the Glanceable Hub in HSUM flag is enabled. */
+    val glanceableHubHsumFlagEnabled: Boolean
+
+    /** Whether the device is in headless system user mode. */
+    fun isHeadlessSystemUserMode(): Boolean
+
+    /** Whether the current process is running in the headless system user. */
+    fun isInHeadlessSystemUser(): Boolean
+
+    /**
+     * Asserts that the current process is running in the headless system user.
+     *
+     * Only throws an exception if [glanceableHubHsumFlagEnabled] is true.
+     */
+    @Throws(IllegalStateException::class) fun assertInHeadlessSystemUser()
+
+    /**
+     * Asserts that the current process is NOT running in the headless system user.
+     *
+     * Only throws an exception if [glanceableHubHsumFlagEnabled] is true.
+     */
+    @Throws(IllegalStateException::class) fun assertNotInHeadlessSystemUser()
+}
+
+@SysUISingleton
+class GlanceableHubMultiUserHelperImpl @Inject constructor(private val userHandle: UserHandle) :
+    GlanceableHubMultiUserHelper {
+
+    override val glanceableHubHsumFlagEnabled: Boolean = secondaryUserWidgetHost()
+
+    override fun isHeadlessSystemUserMode(): Boolean = UserManager.isHeadlessSystemUserMode()
+
+    override fun isInHeadlessSystemUser(): Boolean {
+        return isHeadlessSystemUserMode() && userHandle.isSystem
+    }
+
+    override fun assertInHeadlessSystemUser() {
+        if (glanceableHubHsumFlagEnabled) {
+            check(isInHeadlessSystemUser())
+        }
+    }
+
+    override fun assertNotInHeadlessSystemUser() {
+        if (glanceableHubHsumFlagEnabled) {
+            check(!isInHeadlessSystemUser())
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
index bde5d0f..113375f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt
@@ -76,10 +76,10 @@
             floor(spans.toDouble() / resizeMultiple).toInt() * resizeMultiple
 
         val maxSpans: Int
-            get() = roundDownToMultiple(getSpansForPx(maxHeightPx))
+            get() = roundDownToMultiple(getSpansForPx(maxHeightPx)).coerceAtLeast(currentSpan)
 
         val minSpans: Int
-            get() = roundDownToMultiple(getSpansForPx(minHeightPx))
+            get() = roundDownToMultiple(getSpansForPx(minHeightPx)).coerceAtMost(currentSpan)
     }
 
     /** Check if widget can expanded based on current drag states */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
index d5d3a92..cc6007b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt
@@ -21,11 +21,14 @@
 import android.util.SizeF
 import com.android.app.tracing.coroutines.withContextTraced as withContext
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate
 import com.android.systemui.communal.widgets.CommunalAppWidgetHost
 import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManager
 import com.android.systemui.communal.widgets.WidgetInteractionHandler
 import com.android.systemui.dagger.qualifiers.UiBackground
+import dagger.Lazy
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
@@ -36,9 +39,11 @@
 constructor(
     @UiBackground private val uiBgContext: CoroutineContext,
     @UiBackground private val uiBgExecutor: Executor,
-    private val appWidgetHost: CommunalAppWidgetHost,
+    private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
     private val interactionHandler: WidgetInteractionHandler,
     private val listenerFactory: AppWidgetHostListenerDelegate.Factory,
+    private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
+    private val multiUserHelper: GlanceableHubMultiUserHelper,
 ) {
     suspend fun createWidget(
         context: Context,
@@ -51,9 +56,25 @@
                     setExecutor(uiBgExecutor)
                     setAppWidget(model.appWidgetId, model.providerInfo)
                 }
-            // Instead of setting the view as the listener directly, we wrap the view in a delegate
-            // which ensures the callbacks always get called on the main thread.
-            appWidgetHost.setListener(model.appWidgetId, listenerFactory.create(view))
+
+            if (
+                multiUserHelper.glanceableHubHsumFlagEnabled &&
+                    multiUserHelper.isInHeadlessSystemUser()
+            ) {
+                // If the widget view is created in the headless system user, the widget host lives
+                // remotely in the foreground user, and therefore the host listener needs to be
+                // registered through the widget manager.
+                with(glanceableHubWidgetManagerLazy.get()) {
+                    setAppWidgetHostListener(model.appWidgetId, listenerFactory.create(view))
+                }
+            } else {
+                // Instead of setting the view as the listener directly, we wrap the view in a
+                // delegate which ensures the callbacks always get called on the main thread.
+                with(appWidgetHostLazy.get()) {
+                    setListener(model.appWidgetId, listenerFactory.create(view))
+                }
+            }
+
             if (size != null) {
                 view.updateAppWidgetSize(
                     /* newOptions = */ Bundle(),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
index 4f9ed2f..70e7947 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -18,6 +18,8 @@
 
 import android.appwidget.AppWidgetHost
 import android.content.Context
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import javax.annotation.concurrent.GuardedBy
@@ -25,7 +27,6 @@
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
 class CommunalAppWidgetHost(
@@ -33,7 +34,14 @@
     private val backgroundScope: CoroutineScope,
     hostId: Int,
     logBuffer: LogBuffer,
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
 ) : AppWidgetHost(context, hostId) {
+
+    init {
+        // The app widget host should never be accessed from a headless system user.
+        glanceableHubMultiUserHelper.assertNotInHeadlessSystemUser()
+    }
+
     private val logger = Logger(logBuffer, TAG)
 
     private val _appWidgetIdToRemove = MutableSharedFlow<Int>()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
index 301da51..dec7ba3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt
@@ -18,55 +18,118 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
 import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.pairwise
 import com.android.systemui.util.kotlin.sample
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.dropWhile
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.withContext
 
+// Started per user, so make sure injections are lazy to avoid instantiating unnecessary
+// dependencies.
 @SysUISingleton
 class CommunalAppWidgetHostStartable
 @Inject
 constructor(
-    private val appWidgetHost: CommunalAppWidgetHost,
-    private val communalWidgetHost: CommunalWidgetHost,
-    private val communalInteractor: CommunalInteractor,
-    private val userTracker: UserTracker,
+    private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
+    private val communalWidgetHostLazy: Lazy<CommunalWidgetHost>,
+    private val communalInteractorLazy: Lazy<CommunalInteractor>,
+    private val communalSettingsInteractorLazy: Lazy<CommunalSettingsInteractor>,
+    private val keyguardInteractorLazy: Lazy<KeyguardInteractor>,
+    private val userTrackerLazy: Lazy<UserTracker>,
     @Background private val bgScope: CoroutineScope,
-    @Main private val uiDispatcher: CoroutineDispatcher
+    @Main private val uiDispatcher: CoroutineDispatcher,
+    private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
+    private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
 ) : CoreStartable {
 
+    private val appWidgetHost by lazy { appWidgetHostLazy.get() }
+    private val communalWidgetHost by lazy { communalWidgetHostLazy.get() }
+    private val communalInteractor by lazy { communalInteractorLazy.get() }
+    private val communalSettingsInteractor by lazy { communalSettingsInteractorLazy.get() }
+    private val keyguardInteractor by lazy { keyguardInteractorLazy.get() }
+    private val userTracker by lazy { userTrackerLazy.get() }
+    private val glanceableHubWidgetManager by lazy { glanceableHubWidgetManagerLazy.get() }
+
     override fun start() {
-        anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
-            // Only trigger updates on state changes, ignoring the initial false value.
-            .pairwise(false)
-            .filter { (previous, new) -> previous != new }
-            .onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
-            .sample(communalInteractor.communalWidgets, ::Pair)
-            .onEach { (withPrev, widgets) ->
-                val (_, isActive) = withPrev
-                // The validation is performed once the hub becomes active.
-                if (isActive) {
-                    validateWidgetsAndDeleteOrphaned(widgets)
+        if (
+            glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled &&
+                glanceableHubMultiUserHelper.isInHeadlessSystemUser()
+        ) {
+            onStartInHeadlessSystemUser()
+        } else {
+            onStartInForegroundUser()
+        }
+    }
+
+    private fun onStartInForegroundUser() {
+        // Make the host active when communal becomes available, and delete widgets whose user has
+        // been removed from the system.
+        // Skipped in HSUM, because lifecycle of the host is controlled by the
+        // [GlanceableHubWidgetManagerService], and widgets are stored per user so no more orphaned
+        // widgets.
+        if (
+            !glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled ||
+                !glanceableHubMultiUserHelper.isHeadlessSystemUserMode()
+        ) {
+            anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen)
+                // Only trigger updates on state changes, ignoring the initial false value.
+                .pairwise(false)
+                .filter { (previous, new) -> previous != new }
+                .onEach { (_, shouldListen) -> updateAppWidgetHostActive(shouldListen) }
+                .sample(communalInteractor.communalWidgets, ::Pair)
+                .onEach { (withPrev, widgets) ->
+                    val (_, isActive) = withPrev
+                    // The validation is performed once the hub becomes active.
+                    if (isActive) {
+                        validateWidgetsAndDeleteOrphaned(widgets)
+                    }
                 }
-            }
-            .launchIn(bgScope)
+                .launchIn(bgScope)
+        }
 
         appWidgetHost.appWidgetIdToRemove
             .onEach { appWidgetId -> communalInteractor.deleteWidget(id = appWidgetId) }
             .launchIn(bgScope)
     }
 
+    private fun onStartInHeadlessSystemUser() {
+        // Connection to the widget manager service in the foreground user should stay open as long
+        // as the Glanceable Hub is available.
+        allOf(
+                communalSettingsInteractor.isCommunalEnabled,
+                not(keyguardInteractor.isEncryptedOrLockdown),
+            )
+            .distinctUntilChanged()
+            // Drop the initial false
+            .dropWhile { !it }
+            .onEach { shouldRegister ->
+                if (shouldRegister) {
+                    glanceableHubWidgetManager.register()
+                } else {
+                    glanceableHubWidgetManager.unregister()
+                }
+            }
+            .launchIn(bgScope)
+    }
+
     private suspend fun updateAppWidgetHostActive(active: Boolean) =
         // Always ensure this is called on the main/ui thread.
         withContext(uiDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
index 0b8d977..8c745f5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetHost.kt
@@ -26,6 +26,8 @@
 import android.os.UserHandle
 import android.widget.RemoteViews
 import androidx.annotation.WorkerThread
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
@@ -38,7 +40,6 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Widget host that interacts with AppWidget service and host to bind and provide info for widgets
@@ -52,7 +53,14 @@
     private val appWidgetHost: CommunalAppWidgetHost,
     private val selectedUserInteractor: SelectedUserInteractor,
     @CommunalLog logBuffer: LogBuffer,
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
 ) : CommunalAppWidgetHost.Observer {
+
+    init {
+        // The communal widget host should never be accessed from a headless system user.
+        glanceableHubMultiUserHelper.assertNotInHeadlessSystemUser()
+    }
+
     companion object {
         private const val TAG = "CommunalWidgetHost"
 
@@ -96,7 +104,7 @@
             bindWidget(
                 widgetId = id,
                 user = user ?: UserHandle(selectedUserInteractor.getSelectedUserId()),
-                provider = provider
+                provider = provider,
             )
         ) {
             logger.d("Successfully bound the widget $provider")
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
index f4962085..a235e7a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalWidgetModule.kt
@@ -20,6 +20,7 @@
 import android.appwidget.AppWidgetManager
 import android.content.Context
 import android.content.res.Resources
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -46,8 +47,15 @@
             @Application context: Context,
             @Background backgroundScope: CoroutineScope,
             @CommunalLog logBuffer: LogBuffer,
+            glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
         ): CommunalAppWidgetHost {
-            return CommunalAppWidgetHost(context, backgroundScope, APP_WIDGET_HOST_ID, logBuffer)
+            return CommunalAppWidgetHost(
+                context,
+                backgroundScope,
+                APP_WIDGET_HOST_ID,
+                logBuffer,
+                glanceableHubMultiUserHelper,
+            )
         }
 
         @SysUISingleton
@@ -58,6 +66,7 @@
             appWidgetHost: CommunalAppWidgetHost,
             selectedUserInteractor: SelectedUserInteractor,
             @CommunalLog logBuffer: LogBuffer,
+            glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
         ): CommunalWidgetHost {
             return CommunalWidgetHost(
                 applicationScope,
@@ -65,6 +74,7 @@
                 appWidgetHost,
                 selectedUserInteractor,
                 logBuffer,
+                glanceableHubMultiUserHelper,
             )
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
new file mode 100644
index 0000000..202edf7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.communal.widgets
+
+import android.appwidget.AppWidgetHost.AppWidgetHostListener
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.os.IBinder
+import android.os.UserHandle
+import android.widget.RemoteViews
+import com.android.server.servicewatcher.ServiceWatcher
+import com.android.server.servicewatcher.ServiceWatcher.ServiceListener
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+
+/**
+ * Manages updates to Glanceable Hub widgets and requests to edit them from the headless system
+ * user.
+ *
+ * It communicates with the remote [GlanceableHubWidgetManagerService] which runs in the foreground
+ * user, and abstracts the IPC details from the rest of the system.
+ */
+@SysUISingleton
+class GlanceableHubWidgetManager
+@Inject
+constructor(
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
+    @CommunalLog logBuffer: LogBuffer,
+    serviceWatcherFactory: ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?>,
+) : ServiceListener<GlanceableHubWidgetManagerServiceInfo?> {
+
+    init {
+        // The manager should only be used in the headless system user.
+        glanceableHubMultiUserHelper.assertInHeadlessSystemUser()
+    }
+
+    private val logger = Logger(logBuffer, TAG)
+
+    private val serviceWatcher by lazy { serviceWatcherFactory.create(this) }
+
+    val widgets = conflatedCallbackFlow {
+        val callback =
+            object : IGlanceableHubWidgetsListener.Stub() {
+                override fun onWidgetsUpdated(widgets: List<CommunalWidgetContentModel>?) {
+                    trySend(widgets ?: emptyList())
+                }
+            }
+        runOnService { service -> service.addWidgetsListener(callback) }
+        awaitClose { runOnService { service -> service.removeWidgetsListener(callback) } }
+    }
+
+    fun register() {
+        serviceWatcher.register()
+    }
+
+    fun unregister() {
+        serviceWatcher.unregister()
+    }
+
+    override fun onBind(binder: IBinder?, serviceInfo: GlanceableHubWidgetManagerServiceInfo?) {
+        logger.i("Service bound")
+    }
+
+    override fun onUnbind() {
+        logger.i("Service unbound")
+    }
+
+    /** Requests the foreground user to set a [AppWidgetHostListener] for the given app widget. */
+    fun setAppWidgetHostListener(appWidgetId: Int, listener: AppWidgetHostListener) =
+        runOnService { service ->
+            service.setAppWidgetHostListener(appWidgetId, createIAppWidgetHostListener(listener))
+        }
+
+    /** Requests the foreground user to add a widget. */
+    fun addWidget(
+        provider: ComponentName,
+        user: UserHandle,
+        rank: Int?,
+        configurator: WidgetConfigurator?,
+    ) = runOnService { service ->
+        // TODO(b/375036327): Add support for widget configuration
+        service.addWidget(provider, user, rank ?: -1)
+    }
+
+    /** Requests the foreground user to delete a widget. */
+    fun deleteWidget(appWidgetId: Int) = runOnService { service ->
+        service.deleteWidget(appWidgetId)
+    }
+
+    /** Requests the foreground user to update widget order. */
+    fun updateWidgetOrder(widgetIdToRankMap: Map<Int, Int>) = runOnService { service ->
+        service.updateWidgetOrder(
+            widgetIdToRankMap.keys.toIntArray(),
+            widgetIdToRankMap.values.toIntArray(),
+        )
+    }
+
+    /** Requests the foreground user to resize a widget. */
+    fun resizeWidget(appWidgetId: Int, spanY: Int, widgetIdToRankMap: Map<Int, Int>) =
+        runOnService { service ->
+            service.resizeWidget(
+                appWidgetId,
+                spanY,
+                widgetIdToRankMap.keys.toIntArray(),
+                widgetIdToRankMap.values.toIntArray(),
+            )
+        }
+
+    private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) {
+        serviceWatcher.runOnBinder(
+            object : ServiceWatcher.BinderOperation {
+                override fun run(binder: IBinder?) {
+                    block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder))
+                }
+
+                override fun onError(t: Throwable?) {
+                    // TODO(b/375236794): handle failure in case service is unbound
+                }
+            }
+        )
+    }
+
+    private fun createIAppWidgetHostListener(
+        listener: AppWidgetHostListener
+    ): IAppWidgetHostListener {
+        return object : IAppWidgetHostListener.Stub() {
+            override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
+                listener.onUpdateProviderInfo(appWidget)
+            }
+
+            override fun updateAppWidget(views: RemoteViews?) {
+                listener.updateAppWidget(views)
+            }
+
+            override fun updateAppWidgetDeferred(packageName: String?, appWidgetId: Int) {
+                listener.updateAppWidgetDeferred(packageName, appWidgetId)
+            }
+
+            override fun onViewDataChanged(viewId: Int) {
+                listener.onViewDataChanged(viewId)
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "GlanceableHubWidgetManager"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
new file mode 100644
index 0000000..4d042fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
@@ -0,0 +1,305 @@
+/*
+ * 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.communal.widgets
+
+import android.appwidget.AppWidgetHost.AppWidgetHostListener
+import android.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.Intent
+import android.os.IBinder
+import android.os.RemoteCallbackList
+import android.os.UserHandle
+import android.widget.RemoteViews
+import androidx.lifecycle.LifecycleService
+import androidx.lifecycle.lifecycleScope
+import com.android.systemui.communal.data.repository.CommunalWidgetRepository
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import javax.inject.Inject
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Service for the [GlanceableHubWidgetManager], which runs in a foreground user in Headless System
+ * User Mode (HSUM), manages widgets as the user who owns them, and communicates back to the
+ * headless system user, where these widgets are rendered.
+ */
+class GlanceableHubWidgetManagerService
+@Inject
+constructor(
+    private val widgetRepository: CommunalWidgetRepository,
+    private val appWidgetHost: CommunalAppWidgetHost,
+    private val communalWidgetHost: CommunalWidgetHost,
+    glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
+    @CommunalLog logBuffer: LogBuffer,
+) : LifecycleService() {
+
+    init {
+        // The service should only run in a foreground user.
+        glanceableHubMultiUserHelper.assertNotInHeadlessSystemUser()
+    }
+
+    private val logger = Logger(logBuffer, TAG)
+    private val widgetListenersRegistry = WidgetListenerRegistry()
+
+    override fun onCreate() {
+        super.onCreate()
+
+        logger.i("Service created")
+
+        communalWidgetHost.startObservingHost()
+        appWidgetHost.startListening()
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+        logger.i("Service destroyed")
+
+        appWidgetHost.stopListening()
+        communalWidgetHost.stopObservingHost()
+
+        // Cancel all widget listener jobs and unregister listeners
+        widgetListenersRegistry.kill()
+    }
+
+    override fun onBind(intent: Intent): IBinder? {
+        super.onBind(intent)
+        return WidgetManagerServiceBinder().asBinder()
+    }
+
+    private fun addWidgetsListenerInternal(listener: IGlanceableHubWidgetsListener?) {
+        if (listener == null) {
+            throw IllegalStateException("Listener cannot be null")
+        }
+
+        if (!listener.asBinder().isBinderAlive) {
+            throw IllegalStateException("Listener binder is dead")
+        }
+
+        val job =
+            widgetRepository.communalWidgets
+                .onEach { widgets -> listener.onWidgetsUpdated(widgets) }
+                .launchIn(lifecycleScope)
+        widgetListenersRegistry.register(listener, job)
+    }
+
+    private fun removeWidgetsListenerInternal(listener: IGlanceableHubWidgetsListener?) {
+        if (listener == null) {
+            throw IllegalStateException("Listener cannot be null")
+        }
+
+        widgetListenersRegistry.unregister(listener)
+    }
+
+    private fun setAppWidgetHostListenerInternal(
+        appWidgetId: Int,
+        listener: IAppWidgetHostListener?,
+    ) {
+        if (listener == null) {
+            throw IllegalStateException("Listener cannot be null")
+        }
+
+        appWidgetHost.setListener(appWidgetId, createListener(listener))
+    }
+
+    private fun addWidgetInternal(provider: ComponentName?, user: UserHandle?, rank: Int) {
+        if (provider == null) {
+            throw IllegalStateException("Provider cannot be null")
+        }
+
+        if (user == null) {
+            throw IllegalStateException("User cannot be null")
+        }
+
+        // TODO(b/375036327): Add support for widget configuration
+        widgetRepository.addWidget(provider, user, rank, configurator = null)
+    }
+
+    private fun deleteWidgetInternal(appWidgetId: Int) {
+        widgetRepository.deleteWidget(appWidgetId)
+    }
+
+    private fun updateWidgetOrderInternal(appWidgetIds: IntArray?, ranks: IntArray?) {
+        if (appWidgetIds == null || ranks == null) {
+            throw IllegalStateException("appWidgetIds and ranks cannot be null")
+        }
+
+        if (appWidgetIds.size != ranks.size) {
+            throw IllegalStateException("appWidgetIds and ranks must be the same size")
+        }
+
+        widgetRepository.updateWidgetOrder(appWidgetIds.zip(ranks).toMap())
+    }
+
+    private fun resizeWidgetInternal(
+        appWidgetId: Int,
+        spanY: Int,
+        appWidgetIds: IntArray?,
+        ranks: IntArray?,
+    ) {
+        if (appWidgetIds == null || ranks == null) {
+            throw IllegalStateException("appWidgetIds and ranks cannot be null")
+        }
+
+        if (appWidgetIds.size != ranks.size) {
+            throw IllegalStateException("appWidgetIds and ranks must be the same size")
+        }
+
+        widgetRepository.resizeWidget(appWidgetId, spanY, appWidgetIds.zip(ranks).toMap())
+    }
+
+    private fun createListener(listener: IAppWidgetHostListener): AppWidgetHostListener {
+        return object : AppWidgetHostListener {
+            override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
+                listener.onUpdateProviderInfo(appWidget)
+            }
+
+            override fun updateAppWidget(views: RemoteViews?) {
+                listener.updateAppWidget(views)
+            }
+
+            override fun updateAppWidgetDeferred(packageName: String?, appWidgetId: Int) {
+                listener.updateAppWidgetDeferred(packageName, appWidgetId)
+            }
+
+            override fun onViewDataChanged(viewId: Int) {
+                listener.onViewDataChanged(viewId)
+            }
+        }
+    }
+
+    private inner class WidgetManagerServiceBinder : IGlanceableHubWidgetManagerService.Stub() {
+        override fun addWidgetsListener(listener: IGlanceableHubWidgetsListener?) {
+            val iden = clearCallingIdentity()
+
+            try {
+                addWidgetsListenerInternal(listener)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun removeWidgetsListener(listener: IGlanceableHubWidgetsListener?) {
+            val iden = clearCallingIdentity()
+
+            try {
+                removeWidgetsListenerInternal(listener)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun setAppWidgetHostListener(appWidgetId: Int, listener: IAppWidgetHostListener?) {
+            val iden = clearCallingIdentity()
+
+            try {
+                setAppWidgetHostListenerInternal(appWidgetId, listener)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun addWidget(provider: ComponentName?, user: UserHandle?, rank: Int) {
+            val iden = clearCallingIdentity()
+
+            try {
+                addWidgetInternal(provider, user, rank)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun deleteWidget(appWidgetId: Int) {
+            val iden = clearCallingIdentity()
+
+            try {
+                deleteWidgetInternal(appWidgetId)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun updateWidgetOrder(appWidgetIds: IntArray?, ranks: IntArray?) {
+            val iden = clearCallingIdentity()
+
+            try {
+                updateWidgetOrderInternal(appWidgetIds, ranks)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+
+        override fun resizeWidget(
+            appWidgetId: Int,
+            spanY: Int,
+            appWidgetIds: IntArray?,
+            ranks: IntArray?,
+        ) {
+            val iden = clearCallingIdentity()
+
+            try {
+                resizeWidgetInternal(appWidgetId, spanY, appWidgetIds, ranks)
+            } finally {
+                restoreCallingIdentity(iden)
+            }
+        }
+    }
+
+    /**
+     * Registry of widget listener binders, which handles canceling the job associated with a
+     * listener when it is unregistered, or when the binder is dead.
+     */
+    private class WidgetListenerRegistry : RemoteCallbackList<IGlanceableHubWidgetsListener>() {
+        private val jobs = mutableMapOf<IGlanceableHubWidgetsListener, Job>()
+
+        fun register(listener: IGlanceableHubWidgetsListener, job: Job) {
+            if (register(listener)) {
+                synchronized(jobs) { jobs[listener] = job }
+            } else {
+                job.cancel()
+            }
+        }
+
+        override fun unregister(listener: IGlanceableHubWidgetsListener?): Boolean {
+            synchronized(jobs) { jobs.remove(listener)?.cancel() }
+            return super.unregister(listener)
+        }
+
+        override fun onCallbackDied(listener: IGlanceableHubWidgetsListener?) {
+            synchronized(jobs) { jobs.remove(listener)?.cancel() }
+            super.onCallbackDied(listener)
+        }
+
+        override fun kill() {
+            synchronized(jobs) {
+                jobs.values.forEach { it.cancel() }
+                jobs.clear()
+            }
+            super.kill()
+        }
+    }
+
+    companion object {
+        private const val TAG = "GlanceableHubWidgetManagerService"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceInfo.kt
new file mode 100644
index 0000000..d527611
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceInfo.kt
@@ -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.systemui.communal.widgets
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Context.BIND_AUTO_CREATE
+import android.os.UserHandle
+import com.android.server.servicewatcher.ServiceWatcher
+
+/**
+ * Information about the [GlanceableHubWidgetManagerServiceInfo] used for binding with the
+ * [ServiceWatcher].
+ */
+class GlanceableHubWidgetManagerServiceInfo(context: Context, userHandle: UserHandle) :
+    ServiceWatcher.BoundServiceInfo(
+        /* action= */ null,
+        UserHandle.getUid(userHandle.identifier, UserHandle.getCallingAppId()),
+        ComponentName(context.packageName, GlanceableHubWidgetManagerService::class.java.name),
+        BIND_AUTO_CREATE,
+    )
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceSupplier.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceSupplier.kt
new file mode 100644
index 0000000..ed77e6f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceSupplier.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.communal.widgets
+
+import android.content.Context
+import com.android.server.servicewatcher.ServiceWatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserTracker
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Supplies details about the [GlanceableHubWidgetManagerService] that the [ServiceWatcher] should
+ * bind to. Currently the service should only be bound if the current user is the main user.
+ */
+@SysUISingleton
+class GlanceableHubWidgetManagerServiceSupplier
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Background private val bgExecutor: Executor,
+    private val userTracker: UserTracker,
+) : ServiceWatcher.ServiceSupplier<GlanceableHubWidgetManagerServiceInfo?>, UserTracker.Callback {
+
+    private var userAboutToSwitch = false
+    private var listener: ServiceWatcher.ServiceChangedListener? = null
+
+    override fun getServiceInfo(): GlanceableHubWidgetManagerServiceInfo? {
+        return GlanceableHubWidgetManagerServiceInfo(context, userTracker.userHandle)
+    }
+
+    override fun hasMatchingService(): Boolean {
+        // The service becomes unavailable immediately before a user switching is about to happen
+        // so that it is disconnected before the user process is terminated.
+        // It is also only available if the current user is the main user.
+        return !userAboutToSwitch && userTracker.userInfo.isMain
+    }
+
+    override fun register(listener: ServiceWatcher.ServiceChangedListener?) {
+        this.listener = listener
+        userTracker.addCallback(this, bgExecutor)
+    }
+
+    override fun unregister() {
+        listener = null
+        userTracker.removeCallback(this)
+    }
+
+    override fun onBeforeUserSwitching(newUser: Int) {
+        userAboutToSwitch = true
+        listener?.onServiceChanged()
+    }
+
+    override fun onUserChanged(newUser: Int, userContext: Context) {
+        userAboutToSwitch = false
+        listener?.onServiceChanged()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
new file mode 100644
index 0000000..e556472
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
@@ -0,0 +1,51 @@
+package com.android.systemui.communal.widgets;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.os.UserHandle;
+import android.widget.RemoteViews;
+import com.android.systemui.communal.shared.model.CommunalWidgetContentModel;
+import java.util.List;
+
+// Interface for the [GlanceableHubWidgetManagerService], which runs in a foreground user in HSUM
+// and communicates with the headless system user.
+interface IGlanceableHubWidgetManagerService {
+
+    // Adds a listener for updates of Glanceable Hub widgets.
+    oneway void addWidgetsListener(in IGlanceableHubWidgetsListener listener);
+
+    // Removes a listener for updates of Glanceable Hub widgets.
+    oneway void removeWidgetsListener(in IGlanceableHubWidgetsListener listener);
+
+    // Sets a listener for updates on a specific widget.
+    oneway void setAppWidgetHostListener(int appWidgetId, in IAppWidgetHostListener listener);
+
+    // Requests to add a widget in the Glanceable Hub.
+    oneway void addWidget(in ComponentName provider, in UserHandle user, int rank);
+
+    // Requests to delete a widget from the Glanceable Hub.
+    oneway void deleteWidget(int appWidgetId);
+
+    // Requests to update the order of widgets in the Glanceable Hub.
+    oneway void updateWidgetOrder(in int[] appWidgetIds, in int[] ranks);
+
+    // Requests to resize a widget in the Glanceable Hub.
+    oneway void resizeWidget(int appWidgetId, int spanY, in int[] appWidgetIds, in int[] ranks);
+
+    // Listener for Glanceable Hub widget updates
+    oneway interface IGlanceableHubWidgetsListener {
+        // Called when widgets have updated.
+        void onWidgetsUpdated(in List<CommunalWidgetContentModel> widgets);
+    }
+
+    // Mirrors [AppWidgetHost#AppWidgetHostListener].
+    oneway interface IAppWidgetHostListener {
+        void onUpdateProviderInfo(in @nullable AppWidgetProviderInfo appWidget);
+
+        void updateAppWidget(in @nullable RemoteViews views);
+
+        void updateAppWidgetDeferred(in String packageName, int appWidgetId);
+
+        void onViewDataChanged(int viewId);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/ServiceWatcherFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/ServiceWatcherFactory.kt
new file mode 100644
index 0000000..f79cc87
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/ServiceWatcherFactory.kt
@@ -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 com.android.systemui.communal.widgets
+
+import android.content.Context
+import android.os.Handler
+import com.android.server.servicewatcher.ServiceWatcher
+import com.android.server.servicewatcher.ServiceWatcher.BoundServiceInfo
+import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+
+/** Factory for creating a [ServiceWatcher]. */
+interface ServiceWatcherFactory<TBoundService : BoundServiceInfo?> {
+    fun create(listener: ServiceWatcher.ServiceListener<TBoundService?>): ServiceWatcher
+}
+
+/** Implementation of the [ServiceWatcherFactory] for the [GlanceableHubWidgetManagerService]. */
+@SysUISingleton
+class GlanceableHubWidgetManagerServiceWatcherFactoryImpl
+@Inject
+constructor(
+    @Application private val context: Context,
+    @Background private val handler: Handler,
+    private val supplier: ServiceSupplier<GlanceableHubWidgetManagerServiceInfo?>,
+) : ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?> {
+
+    override fun create(
+        listener: ServiceWatcher.ServiceListener<GlanceableHubWidgetManagerServiceInfo?>
+    ): ServiceWatcher {
+        return ServiceWatcher.create(
+            context,
+            handler,
+            GlanceableHubWidgetManagerService::class.java.simpleName,
+            supplier,
+            listener,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
index 3e68479..d157cd7 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
@@ -21,8 +21,10 @@
 import android.content.ActivityNotFoundException
 import android.window.SplashScreen
 import androidx.activity.ComponentActivity
+import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.nullableAtomicReference
+import dagger.Lazy
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -38,8 +40,9 @@
 @AssistedInject
 constructor(
     @Assisted private val activity: ComponentActivity,
-    private val appWidgetHost: CommunalAppWidgetHost,
-    @Background private val bgDispatcher: CoroutineDispatcher
+    private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
 ) : WidgetConfigurator {
     @AssistedFactory
     fun interface Factory {
@@ -62,13 +65,21 @@
                 }
 
             try {
-                appWidgetHost.startAppWidgetConfigureActivityForResult(
-                    activity,
-                    appWidgetId,
-                    0,
-                    REQUEST_CODE,
-                    options.toBundle()
-                )
+                // TODO(b/375036327): Add support for widget configuration
+                if (
+                    !glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled ||
+                        !glanceableHubMultiUserHelper.isInHeadlessSystemUser()
+                ) {
+                    with(appWidgetHostLazy.get()) {
+                        startAppWidgetConfigureActivityForResult(
+                            activity,
+                            appWidgetId,
+                            0,
+                            REQUEST_CODE,
+                            options.toBundle(),
+                        )
+                    }
+                }
             } catch (e: ActivityNotFoundException) {
                 setConfigurationResult(Activity.RESULT_CANCELED)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index 904d5898..8f01775 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -19,6 +19,7 @@
 import android.app.Service;
 
 import com.android.systemui.SystemUIService;
+import com.android.systemui.communal.widgets.GlanceableHubWidgetManagerService;
 import com.android.systemui.doze.DozeService;
 import com.android.systemui.dreams.DreamOverlayService;
 import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
@@ -93,4 +94,10 @@
     @ClassKey(IssueRecordingService.class)
     public abstract Service bindIssueRecordingService(IssueRecordingService service);
 
+    /** Inject into GlanceableHubWidgetManagerService */
+    @Binds
+    @IntoMap
+    @ClassKey(GlanceableHubWidgetManagerService.class)
+    public abstract Service bindGlanceableHubWidgetManagerService(
+            GlanceableHubWidgetManagerService service);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cb649f2..9138243 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -49,6 +49,7 @@
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.common.data.CommonDataLayerModule;
 import com.android.systemui.common.ui.ConfigurationStateModule;
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule;
 import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule;
 import com.android.systemui.communal.dagger.CommunalModule;
 import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -212,6 +213,7 @@
         CommunalModule.class,
         CommonDataLayerModule.class,
         ConfigurationStateModule.class,
+        ConfigurationRepositoryModule.class,
         CommonUsageStatsDataLayerModule.class,
         ConfigurationControllerModule.class,
         ConnectivityModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
index 9aa7fd1..78d8d8f 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -81,7 +81,7 @@
     override val viewId: Int,
     @DisplayCutout.BoundsPosition override val alignedBound1: Int,
     @DisplayCutout.BoundsPosition override val alignedBound2: Int,
-    private val layoutId: Int,
+    val layoutId: Int,
 ) : CornerDecorProvider() {
 
     override fun onReloadResAndMeasure(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index a085887..12dd581 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -19,12 +19,14 @@
 import android.content.Context
 import android.graphics.drawable.Icon
 import android.hardware.input.InputManager
+import android.hardware.input.KeyGlyphMap
 import android.util.Log
 import android.view.InputDevice
 import android.view.KeyCharacterMap
 import android.view.KeyEvent
 import android.view.KeyboardShortcutGroup
 import android.view.KeyboardShortcutInfo
+import com.android.systemui.Flags.shortcutHelperKeyGlyph
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyboard.shortcut.data.source.KeyboardShortcutGroupsSource
@@ -142,7 +144,10 @@
         return if (type == null) {
             null
         } else {
+            val keyGlyphMap =
+                if (shortcutHelperKeyGlyph()) inputManager.getKeyGlyphMap(inputDevice.id) else null
             toShortcutCategory(
+                keyGlyphMap,
                 inputDevice.keyCharacterMap,
                 type,
                 groups,
@@ -163,6 +168,7 @@
     }
 
     private fun toShortcutCategory(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         type: ShortcutCategoryType,
         shortcutGroups: List<KeyboardShortcutGroup>,
@@ -175,6 +181,7 @@
                     ShortcutSubCategory(
                         shortcutGroup.label.toString(),
                         toShortcuts(
+                            keyGlyphMap,
                             keyCharacterMap,
                             shortcutGroup.items,
                             keepIcons,
@@ -192,6 +199,7 @@
     }
 
     private fun toShortcuts(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         infoList: List<KeyboardShortcutInfo>,
         keepIcons: Boolean,
@@ -203,14 +211,16 @@
                 // keycode, or they could have a baseCharacter instead of a keycode.
                 it.keycode == KeyEvent.KEYCODE_UNKNOWN || supportedKeyCodes.contains(it.keycode)
             }
-            .mapNotNull { toShortcut(keyCharacterMap, it, keepIcons) }
+            .mapNotNull { toShortcut(keyGlyphMap, keyCharacterMap, it, keepIcons) }
 
     private fun toShortcut(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         shortcutInfo: KeyboardShortcutInfo,
         keepIcon: Boolean,
     ): Shortcut? {
-        val shortcutCommand = toShortcutCommand(keyCharacterMap, shortcutInfo) ?: return null
+        val shortcutCommand =
+            toShortcutCommand(keyGlyphMap, keyCharacterMap, shortcutInfo) ?: return null
         return Shortcut(
             label = shortcutInfo.label!!.toString(),
             icon = toShortcutIcon(keepIcon, shortcutInfo),
@@ -235,6 +245,7 @@
     }
 
     private fun toShortcutCommand(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         info: KeyboardShortcutInfo,
     ): ShortcutCommand? {
@@ -242,7 +253,7 @@
         var remainingModifiers = info.modifiers
         SUPPORTED_MODIFIERS.forEach { supportedModifier ->
             if ((supportedModifier and remainingModifiers) != 0) {
-                keys += toShortcutModifierKey(supportedModifier) ?: return null
+                keys += toShortcutModifierKey(keyGlyphMap, supportedModifier) ?: return null
                 // "Remove" the modifier from the remaining modifiers
                 remainingModifiers = remainingModifiers and supportedModifier.inv()
             }
@@ -253,7 +264,9 @@
             return null
         }
         if (info.keycode != 0 || info.baseCharacter > Char.MIN_VALUE) {
-            keys += toShortcutKey(keyCharacterMap, info.keycode, info.baseCharacter) ?: return null
+            keys +=
+                toShortcutKey(keyGlyphMap, keyCharacterMap, info.keycode, info.baseCharacter)
+                    ?: return null
         }
         if (keys.isEmpty()) {
             Log.wtf(TAG, "No keys for $info")
@@ -262,10 +275,15 @@
         return ShortcutCommand(keys)
     }
 
-    private fun toShortcutModifierKey(modifierMask: Int): ShortcutKey? {
+    private fun toShortcutModifierKey(keyGlyphMap: KeyGlyphMap?, modifierMask: Int): ShortcutKey? {
+        val modifierDrawable = keyGlyphMap?.getDrawableForModifierState(context, modifierMask)
+        if (modifierDrawable != null) {
+            return ShortcutKey.Icon.DrawableIcon(drawable = modifierDrawable)
+        }
+
         val iconResId = ShortcutHelperKeys.keyIcons[modifierMask]
         if (iconResId != null) {
-            return ShortcutKey.Icon(iconResId)
+            return ShortcutKey.Icon.ResIdIcon(iconResId)
         }
 
         val modifierLabel = ShortcutHelperKeys.modifierLabels[modifierMask]
@@ -277,13 +295,19 @@
     }
 
     private fun toShortcutKey(
+        keyGlyphMap: KeyGlyphMap?,
         keyCharacterMap: KeyCharacterMap,
         keyCode: Int,
         baseCharacter: Char = Char.MIN_VALUE,
     ): ShortcutKey? {
+        val keycodeDrawable = keyGlyphMap?.getDrawableForKeycode(context, keyCode)
+        if (keycodeDrawable != null) {
+            return ShortcutKey.Icon.DrawableIcon(drawable = keycodeDrawable)
+        }
+
         val iconResId = ShortcutHelperKeys.keyIcons[keyCode]
         if (iconResId != null) {
-            return ShortcutKey.Icon(iconResId)
+            return ShortcutKey.Icon.ResIdIcon(iconResId)
         }
         if (baseCharacter > Char.MIN_VALUE) {
             return ShortcutKey.Text(baseCharacter.uppercase())
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
index eddac4d..05ff0cc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/source/MultitaskingShortcutsSource.kt
@@ -70,7 +70,7 @@
             },
             //  Change split screen focus to LHS:
             //   - Meta + Alt + Left arrow
-            shortcutInfo(resources.getString(R.string.system_multitasking_splitscreen_focus_rhs)) {
+            shortcutInfo(resources.getString(R.string.system_multitasking_splitscreen_focus_lhs)) {
                 command(META_META_ON or META_ALT_ON, KEYCODE_DPAD_LEFT)
             },
         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
index e5b8096..28451ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCommand.kt
@@ -28,7 +28,7 @@
     }
 
     fun key(@DrawableRes drawableResId: Int) {
-        keys += ShortcutKey.Icon(drawableResId)
+        keys += ShortcutKey.Icon.ResIdIcon(drawableResId)
     }
 
     fun build() = ShortcutCommand(keys)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt
index 1abb78c..1a609ea 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutKey.kt
@@ -16,10 +16,15 @@
 
 package com.android.systemui.keyboard.shortcut.shared.model
 
+import android.graphics.drawable.Drawable
 import androidx.annotation.DrawableRes
 
 sealed interface ShortcutKey {
     data class Text(val value: String) : ShortcutKey
 
-    data class Icon(@DrawableRes val drawableResId: Int) : ShortcutKey
+    sealed interface Icon : ShortcutKey {
+        data class ResIdIcon(@DrawableRes val drawableResId: Int) : Icon
+
+        data class DrawableIcon(val drawable: Drawable) : Icon
+    }
 }
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 d537056..abddc70 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
@@ -647,7 +647,11 @@
 @Composable
 private fun BoxScope.ShortcutIconKey(key: ShortcutKey.Icon) {
     Icon(
-        painter = painterResource(key.drawableResId),
+        painter =
+            when (key) {
+                is ShortcutKey.Icon.ResIdIcon -> painterResource(key.drawableResId)
+                is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable)
+            },
         contentDescription = null,
         modifier = Modifier.align(Alignment.Center).padding(6.dp),
     )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index f3eeed2..8c60371 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -19,7 +19,6 @@
 import android.animation.ValueAnimator
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
-import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.Flags.communalSceneKtfRefactor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.dagger.SysUISingleton
@@ -40,17 +39,19 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.sample
-import java.util.UUID
-import javax.inject.Inject
-import kotlin.time.Duration.Companion.milliseconds
-import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import java.util.UUID
+import javax.inject.Inject
+import com.android.app.tracing.coroutines.launchTraced as launch
 
 @SysUISingleton
 class FromLockscreenTransitionInteractor
@@ -258,6 +259,19 @@
                 }
             }
         }
+
+        // Ensure that transitionId is nulled out if external signals cause a PRIMARY_BOUNCER
+        // transition to be canceled.
+        scope.launch {
+            transitionInteractor.transitions
+                .filter {
+                    it.transitionState == TransitionState.CANCELED &&
+                        it.to == KeyguardState.PRIMARY_BOUNCER
+                }
+                .collect {
+                    transitionId = null
+                }
+        }
     }
 
     fun dismissKeyguard() {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index c70cd0a..47dacae 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -131,7 +131,9 @@
 
         // This activity is launched directly by an app, or system server. System server provides
         // the package name through the intent if so.
-        if (mPackageName == null) {
+        if (mPackageName == null || (
+                com.android.systemui.Flags.mediaProjectionRequestAttributionFix()
+                        && getCallingPackage() == null)) {
             if (launchingIntent.hasExtra(EXTRA_PACKAGE_REUSING_GRANTED_CONSENT)) {
                 mPackageName = launchingIntent.getStringExtra(
                         EXTRA_PACKAGE_REUSING_GRANTED_CONSENT);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 96c0cac..40613c0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -149,6 +149,7 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -258,8 +259,7 @@
     private boolean mTransientShownFromGestureOnSystemBar;
     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
     private LightBarController mLightBarController;
-    private final LightBarController mMainLightBarController;
-    private final LightBarController.Factory mLightBarControllerFactory;
+    private final LightBarControllerStore mLightBarControllerStore;
     private AutoHideController mAutoHideController;
     private final AutoHideController mMainAutoHideController;
     private final AutoHideController.Factory mAutoHideControllerFactory;
@@ -580,8 +580,7 @@
             @Background Executor bgExecutor,
             UiEventLogger uiEventLogger,
             NavBarHelper navBarHelper,
-            LightBarController mainLightBarController,
-            LightBarController.Factory lightBarControllerFactory,
+            LightBarControllerStore lightBarControllerStore,
             AutoHideController mainAutoHideController,
             AutoHideController.Factory autoHideControllerFactory,
             Optional<TelecomManager> telecomManagerOptional,
@@ -628,8 +627,7 @@
         mUiEventLogger = uiEventLogger;
         mNavBarHelper = navBarHelper;
         mNotificationShadeDepthController = notificationShadeDepthController;
-        mMainLightBarController = mainLightBarController;
-        mLightBarControllerFactory = lightBarControllerFactory;
+        mLightBarControllerStore = lightBarControllerStore;
         mMainAutoHideController = mainAutoHideController;
         mAutoHideControllerFactory = autoHideControllerFactory;
         mTelecomManagerOptional = telecomManagerOptional;
@@ -842,8 +840,7 @@
         // Unfortunately, we still need it because status bar needs LightBarController
         // before notifications creation. We cannot directly use getLightBarController()
         // from NavigationBarFragment directly.
-        LightBarController lightBarController = mIsOnDefaultDisplay
-                ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
+        LightBarController lightBarController = mLightBarControllerStore.forDisplay(mDisplayId);
         setLightBarController(lightBarController);
 
         // TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 0b37b5b..1ca3927 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -1146,4 +1146,4 @@
             updateState();
         }
     }
-}
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index 9c8e84f..bacff99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -38,12 +38,14 @@
 import androidx.compose.animation.fadeOut
 import androidx.compose.animation.togetherWith
 import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.layout.Arrangement.spacedBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.windowInsetsPadding
@@ -73,13 +75,14 @@
 import androidx.compose.ui.semantics.customActions
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.round
 import androidx.compose.ui.util.fastRoundToInt
+import androidx.compose.ui.viewinterop.AndroidView
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.animation.scene.ContentKey
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.ElementMatcher
@@ -98,10 +101,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.lifecycle.setSnapshotBinding
-import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.view.MediaHost
-import com.android.systemui.media.dagger.MediaModule.QS_PANEL
-import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.plugins.qs.QSContainerController
 import com.android.systemui.qs.composefragment.SceneKeys.QuickQuickSettings
@@ -127,7 +127,6 @@
 import java.io.PrintWriter
 import java.util.function.Consumer
 import javax.inject.Inject
-import javax.inject.Named
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
@@ -135,6 +134,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
 
 @SuppressLint("ValidFragment")
 class QSFragmentCompose
@@ -142,11 +142,11 @@
 constructor(
     private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
     private val dumpManager: DumpManager,
-    @Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
-    @Named(QS_PANEL) private val qsMediaHost: MediaHost,
 ) : LifecycleFragment(), QS, Dumpable {
 
     private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
+    private val collapsedMediaVisibilityChangedListener =
+        MutableStateFlow<(Consumer<Boolean>)?>(null)
     private val heightListener = MutableStateFlow<QS.HeightListener?>(null)
     private val qsContainerController = MutableStateFlow<QSContainerController?>(null)
 
@@ -183,8 +183,6 @@
         QSComposeFragment.isUnexpectedlyInLegacyMode()
         viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope)
 
-        qqsMediaHost.init(MediaHierarchyManager.LOCATION_QQS)
-        qsMediaHost.init(MediaHierarchyManager.LOCATION_QS)
         setListenerCollections()
         lifecycleScope.launch { viewModel.activate() }
     }
@@ -491,7 +489,7 @@
     }
 
     override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
-        // TODO (b/353253280)
+        collapsedMediaVisibilityChangedListener.value = listener
     }
 
     override fun setScrollListener(scrollListener: QS.ScrollListener?) {
@@ -534,6 +532,7 @@
             lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                 this@QSFragmentCompose.view?.setSnapshotBinding {
                     scrollListener.value?.onQsPanelScrollChanged(scrollState.value)
+                    collapsedMediaVisibilityChangedListener.value?.accept(viewModel.qqsMediaVisible)
                 }
                 launch {
                     setListenerJob(
@@ -569,7 +568,7 @@
                 .squishiness
                 .collectAsStateWithLifecycle()
 
-        Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
+        Column(modifier = Modifier.sysuiResTag(ResIdTags.quickQsPanel)) {
             Box(
                 modifier =
                     Modifier.fillMaxWidth()
@@ -581,6 +580,9 @@
                                 leftFromRoot + coordinates.size.width,
                                 topFromRoot + coordinates.size.height,
                             )
+                            if (squishiness == 1f) {
+                                viewModel.qqsHeight = coordinates.size.height
+                            }
                         }
                         // Use an approach layout to determien the height without squishiness, as
                         // that's the value that NPVC and QuickSettingsController care about
@@ -595,8 +597,7 @@
                         .padding(top = { qqsPadding }, bottom = { bottomPadding })
             ) {
                 if (viewModel.isQsEnabled) {
-                    QuickQuickSettings(
-                        viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
+                    Column(
                         modifier =
                             Modifier.collapseExpandSemanticAction(
                                     stringResource(
@@ -608,7 +609,16 @@
                                         QuickSettingsShade.Dimensions.Padding.roundToPx()
                                     }
                                 ),
-                    )
+                        verticalArrangement =
+                            spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
+                    ) {
+                        QuickQuickSettings(
+                            viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel
+                        )
+                        if (viewModel.qqsMediaVisible) {
+                            MediaObject(mediaHost = viewModel.qqsMediaHost)
+                        }
+                    }
                 }
             }
             Spacer(modifier = Modifier.weight(1f))
@@ -645,14 +655,27 @@
                                 }
                                 .onSizeChanged { viewModel.qsScrollHeight = it.height }
                                 .verticalScroll(scrollState)
+                                .sysuiResTag(ResIdTags.qsScroll)
                     ) {
                         Spacer(
                             modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() }
                         )
                         QuickSettingsLayout(
                             viewModel = viewModel.containerViewModel,
-                            modifier = Modifier.sysuiResTag("quick_settings_panel"),
+                            modifier = Modifier.sysuiResTag(ResIdTags.quickSettingsPanel),
                         )
+                        Spacer(modifier = Modifier.height(8.dp))
+                        if (viewModel.qsMediaVisible) {
+                            MediaObject(
+                                mediaHost = viewModel.qsMediaHost,
+                                modifier =
+                                    Modifier.padding(
+                                        horizontal = {
+                                            QuickSettingsShade.Dimensions.Padding.roundToPx()
+                                        }
+                                    ),
+                            )
+                        }
                     }
                 }
                 QuickSettingsTheme {
@@ -660,7 +683,7 @@
                         viewModel = viewModel.footerActionsViewModel,
                         qsVisibilityLifecycleOwner = this@QSFragmentCompose,
                         modifier =
-                            Modifier.sysuiResTag("qs_footer_actions")
+                            Modifier.sysuiResTag(ResIdTags.qsFooterActions)
                                 .element(ElementKeys.FooterActions),
                     )
                 }
@@ -914,3 +937,29 @@
     } else {
         this
     }
+
+@Composable
+private fun MediaObject(mediaHost: MediaHost, modifier: Modifier = Modifier) {
+    Box {
+        AndroidView(
+            modifier = modifier,
+            factory = {
+                mediaHost.hostView.apply {
+                    layoutParams =
+                        FrameLayout.LayoutParams(
+                            FrameLayout.LayoutParams.MATCH_PARENT,
+                            FrameLayout.LayoutParams.WRAP_CONTENT,
+                        )
+                }
+            },
+            onReset = {},
+        )
+    }
+}
+
+private object ResIdTags {
+    const val quickSettingsPanel = "quick_settings_panel"
+    const val quickQsPanel = "quick_qs_panel"
+    const val qsScroll = "expanded_qs_scroll_view"
+    const val qsFooterActions = "qs_footer_actions"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
new file mode 100644
index 0000000..5127320
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
@@ -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.systemui.qs.composefragment.dagger
+
+import android.content.Context
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.util.Utils
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+@Module
+interface QSFragmentComposeModule {
+
+    companion object {
+        const val QS_USING_MEDIA_PLAYER = "compose_fragment_using_media_player"
+
+        @Provides
+        @SysUISingleton
+        @Named(QS_USING_MEDIA_PLAYER)
+        fun providesUsingMedia(@Application context: Context): Boolean {
+            return Utils.useQsMediaPlayer(context)
+        }
+    }
+}
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 d30c6be..0ca621d 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
@@ -38,11 +38,17 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
+import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.dagger.MediaModule.QS_PANEL
+import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
-import com.android.systemui.qs.panels.ui.viewmodel.PaginatedGridViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.InFirstPageViewModel
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.model.Scenes
@@ -60,10 +66,14 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import java.io.PrintWriter
+import javax.inject.Named
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
@@ -83,7 +93,10 @@
     configurationInteractor: ConfigurationInteractor,
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
     private val squishinessInteractor: TileSquishinessInteractor,
-    private val paginatedGridViewModel: PaginatedGridViewModel,
+    private val inFirstPageViewModel: InFirstPageViewModel,
+    @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost,
+    @Named(QS_PANEL) val qsMediaHost: MediaHost,
+    @Named(QSFragmentComposeModule.QS_USING_MEDIA_PLAYER) private val usingMedia: Boolean,
     @Assisted private val lifecycleScope: LifecycleCoroutineScope,
 ) : Dumpable, ExclusiveActivatable() {
 
@@ -191,7 +204,7 @@
     var collapseExpandAccessibilityAction: Runnable? = null
 
     val inFirstPage: Boolean
-        get() = paginatedGridViewModel.inFirstPage
+        get() = inFirstPageViewModel.inFirstPage
 
     var overScrollAmount by mutableStateOf(0)
 
@@ -222,6 +235,30 @@
         }
     }
 
+    val showingMirror: Boolean
+        get() = containerViewModel.brightnessSliderViewModel.showMirror
+
+    // The initial values in these two are not meaningful. The flow will emit on start the correct
+    // values. This is because we need to lazily fetch them after initMediaHosts.
+    val qqsMediaVisible by
+        hydrator.hydratedStateOf(
+            traceName = "qqsMediaVisible",
+            initialValue = usingMedia,
+            source =
+                if (usingMedia) {
+                    mediaHostVisible(qqsMediaHost)
+                } else {
+                    flowOf(false)
+                },
+        )
+
+    val qsMediaVisible by
+        hydrator.hydratedStateOf(
+            traceName = "qsMediaVisible",
+            initialValue = usingMedia,
+            source = if (usingMedia) mediaHostVisible(qsMediaHost) else flowOf(false),
+        )
+
     private var qsBounds by mutableStateOf(Rect())
 
     private val constrainedSquishinessFraction: Float
@@ -259,9 +296,6 @@
                     .onStart { emit(sysuiStatusBarStateController.state) },
         )
 
-    val showingMirror: Boolean
-        get() = containerViewModel.brightnessSliderViewModel.showMirror
-
     private val isKeyguardState: Boolean
         get() = statusBarState == StatusBarState.KEYGUARD
 
@@ -323,6 +357,7 @@
         )
 
     override suspend fun onActivated(): Nothing {
+        initMediaHosts() // init regardless of using media (same as current QS).
         coroutineScope {
             launch { hydrateSquishinessInteractor() }
             launch { hydrator.activate() }
@@ -331,6 +366,19 @@
         }
     }
 
+    private fun initMediaHosts() {
+        qqsMediaHost.apply {
+            expansion = MediaHostState.EXPANDED
+            showsOnlyActiveMedia = true
+            init(MediaHierarchyManager.LOCATION_QQS)
+        }
+        qsMediaHost.apply {
+            expansion = MediaHostState.EXPANDED
+            showsOnlyActiveMedia = false
+            init(MediaHierarchyManager.LOCATION_QS)
+        }
+    }
+
     private suspend fun hydrateSquishinessInteractor() {
         snapshotFlow { constrainedSquishinessFraction }
             .collect { squishinessInteractor.setSquishinessValue(it) }
@@ -373,6 +421,10 @@
                 println("qqsHeight", "${qqsHeight}px")
                 println("qsScrollHeight", "${qsScrollHeight}px")
             }
+            printSection("Media") {
+                println("qqsMediaVisible", qqsMediaVisible)
+                println("qsMediaVisible", qsMediaVisible)
+            }
         }
     }
 
@@ -390,3 +442,21 @@
 }
 
 private val SHORT_PARALLAX_AMOUNT = 0.1f
+
+/**
+ * Returns a flow to track the visibility of a [MediaHost]. The flow will emit on start the visible
+ * state of the view.
+ */
+private fun mediaHostVisible(mediaHost: MediaHost): Flow<Boolean> {
+    return callbackFlow {
+            val listener: (Boolean) -> Unit = { visible: Boolean -> trySend(visible) }
+            mediaHost.addVisibilityChangeListener(listener)
+
+            awaitClose { mediaHost.removeVisibilityChangeListener(listener) }
+        }
+        // Need to use this to set initial state because on creation of the media host, the
+        // view visibility is not in sync with [MediaHost.visible], which is what we track with
+        // the listener. The correct state is set as part of init, so we need to get the state
+        // lazily.
+        .onStart { emit(mediaHost.visible) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 29bcad4..94b8a3a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -19,6 +19,7 @@
 import com.android.systemui.media.dagger.MediaModule;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.ReduceBrightColorsControllerImpl;
+import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule;
 import com.android.systemui.qs.external.QSExternalModule;
 import com.android.systemui.qs.panels.dagger.PanelsModule;
 import com.android.systemui.qs.pipeline.dagger.QSPipelineModule;
@@ -40,6 +41,7 @@
         includes = {
                 MediaModule.class,
                 PanelsModule.class,
+                QSFragmentComposeModule.class,
                 QSExternalModule.class,
                 QSFlagsModule.class,
                 QSHostModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index d55763a..6cc2cbc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -40,9 +40,9 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing
@@ -54,7 +54,7 @@
 class PaginatedGridLayout
 @Inject
 constructor(
-    private val viewModel: PaginatedGridViewModel,
+    private val viewModelFactory: PaginatedGridViewModel.Factory,
     @PaginatedBaseLayoutType private val delegateGridLayout: PaginatableGridLayout,
 ) : GridLayout by delegateGridLayout {
     @Composable
@@ -63,13 +63,18 @@
         modifier: Modifier,
         editModeStart: () -> Unit,
     ) {
+        val viewModel =
+            rememberViewModel(traceName = "PaginatedGridLayout-TileGrid") {
+                viewModelFactory.create()
+            }
+
         DisposableEffect(tiles) {
             val token = Any()
             tiles.forEach { it.startListening(token) }
             onDispose { tiles.forEach { it.stopListening(token) } }
         }
-        val columns by viewModel.columns.collectAsStateWithLifecycle()
-        val rows by viewModel.rows.collectAsStateWithLifecycle()
+        val columns by viewModel.columns
+        val rows = viewModel.rows
 
         val pages =
             remember(tiles, columns, rows) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index 99a6cda..ca28ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -41,7 +41,8 @@
     viewModel: QuickQuickSettingsViewModel,
     modifier: Modifier = Modifier,
 ) {
-    val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle()
+
+    val sizedTiles = viewModel.tileViewModels
     val tiles = sizedTiles.fastMap { it.tile }
     val bounceables = remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
     val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle()
@@ -52,7 +53,7 @@
         tiles.forEach { it.startListening(token) }
         onDispose { tiles.forEach { it.stopListening(token) } }
     }
-    val columns by viewModel.columns.collectAsStateWithLifecycle()
+    val columns = viewModel.columns
     var cellIndex = 0
     Box(modifier = modifier) {
         GridAnchor()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 91f2da2..19ab29e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -72,7 +72,7 @@
             rememberViewModel(traceName = "InfiniteGridLayout.TileGrid") {
                 viewModel.dynamicIconTilesViewModelFactory.create()
             }
-        val columns by viewModel.gridSizeViewModel.columns.collectAsStateWithLifecycle()
+        val columns by viewModel.gridSizeViewModel.columns
         val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) }
         val bounceables =
             remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } }
@@ -118,7 +118,7 @@
             rememberViewModel(traceName = "InfiniteGridLayout.EditTileGrid") {
                 viewModel.dynamicIconTilesViewModelFactory.create()
             }
-        val columns by viewModel.gridSizeViewModel.columns.collectAsStateWithLifecycle()
+        val columns by viewModel.gridSizeViewModel.columns
         val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
 
         // Non-current tiles should always be displayed as icon tiles.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModel.kt
new file mode 100644
index 0000000..225f542
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModel.kt
@@ -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 com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/*
+ * Tracks whether the current HorizontalPager (using this viewmodel) is in the first page.
+ * This requires it to be a `@SysUISingleton` to be shared between viewmodels.
+ */
+@SysUISingleton
+class InFirstPageViewModel @Inject constructor() {
+    var inFirstPage = true
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt
index 0d12067..d687100 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/InfiniteGridViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
+import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.qs.panels.ui.dialog.QSResetDialogDelegate
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
@@ -27,12 +28,16 @@
     val gridSizeViewModel: QSColumnsViewModel,
     val squishinessViewModel: TileSquishinessViewModel,
     private val resetDialogDelegate: QSResetDialogDelegate,
-) {
+) : ExclusiveActivatable() {
 
     fun showResetDialog() {
         resetDialogDelegate.showDialog()
     }
 
+    override suspend fun onActivated(): Nothing {
+        gridSizeViewModel.activate()
+    }
+
     @AssistedFactory
     interface Factory {
         fun create(): InfiniteGridViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
index 0f7dafc..8bd9ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModel.kt
@@ -16,33 +16,50 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.qs.panels.domain.interactor.PaginatedGridInteractor
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.stateIn
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
-@SysUISingleton
 class PaginatedGridViewModel
-@Inject
+@AssistedInject
 constructor(
     iconTilesViewModel: IconTilesViewModel,
-    gridSizeViewModel: QSColumnsViewModel,
+    private val gridSizeViewModel: QSColumnsViewModel,
     paginatedGridInteractor: PaginatedGridInteractor,
-    @Application applicationScope: CoroutineScope,
-) : IconTilesViewModel by iconTilesViewModel, QSColumnsViewModel by gridSizeViewModel {
-    val rows =
-        paginatedGridInteractor.rows.stateIn(
-            applicationScope,
-            SharingStarted.WhileSubscribed(),
-            paginatedGridInteractor.defaultRows,
+    inFirstPageViewModel: InFirstPageViewModel,
+) : IconTilesViewModel by iconTilesViewModel, ExclusiveActivatable() {
+
+    private val hydrator = Hydrator("PaginatedGridViewModel")
+
+    val rows by
+        hydrator.hydratedStateOf(
+            traceName = "rows",
+            initialValue = paginatedGridInteractor.defaultRows,
+            source = paginatedGridInteractor.rows,
         )
 
-    /*
-     * Tracks whether the current HorizontalPager (using this viewmodel) is in the first page.
-     * This requires it to be a `@SysUISingleton` to be shared between viewmodels.
-     */
-    var inFirstPage = true
+    var inFirstPage by inFirstPageViewModel::inFirstPage
+
+    val columns: State<Int>
+        get() = gridSizeViewModel.columns
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch { hydrator.activate() }
+            launch { gridSizeViewModel.activate() }
+            awaitCancellation()
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): PaginatedGridViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
index 0f1c77e..8926d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QSColumnsViewModel.kt
@@ -16,17 +16,25 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
-import com.android.systemui.dagger.SysUISingleton
+import androidx.compose.runtime.State
+import com.android.systemui.lifecycle.Activatable
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.qs.panels.domain.interactor.QSColumnsInteractor
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
 
-interface QSColumnsViewModel {
-    val columns: StateFlow<Int>
+interface QSColumnsViewModel : Activatable {
+    val columns: State<Int>
 }
 
-@SysUISingleton
 class QSColumnsSizeViewModelImpl @Inject constructor(interactor: QSColumnsInteractor) :
-    QSColumnsViewModel {
-    override val columns: StateFlow<Int> = interactor.columns
+    QSColumnsViewModel, ExclusiveActivatable() {
+    private val hydrator = Hydrator("QSColumnsSizeViewModelImpl")
+
+    override val columns =
+        hydrator.hydratedStateOf(traceName = "columns", source = interactor.columns)
+
+    override suspend fun onActivated(): Nothing {
+        hydrator.activate()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
index 887a70f..0859c86 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModel.kt
@@ -16,67 +16,69 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
 import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.qs.panels.domain.interactor.QuickQuickSettingsRowInteractor
-import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
 import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.stateIn
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
 class QuickQuickSettingsViewModel
-@Inject
+@AssistedInject
 constructor(
     tilesInteractor: CurrentTilesInteractor,
-    qsColumnsViewModel: QSColumnsViewModel,
+    private val qsColumnsViewModel: QSColumnsViewModel,
     quickQuickSettingsRowInteractor: QuickQuickSettingsRowInteractor,
     val squishinessViewModel: TileSquishinessViewModel,
-    private val iconTilesViewModel: IconTilesViewModel,
-    @Application private val applicationScope: CoroutineScope,
+    iconTilesViewModel: IconTilesViewModel,
     val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
-) {
+) : ExclusiveActivatable() {
 
-    val columns = qsColumnsViewModel.columns
+    private val hydrator = Hydrator("QuickQuickSettingsViewModel")
 
-    private val rows =
-        quickQuickSettingsRowInteractor.rows.stateIn(
-            applicationScope,
-            SharingStarted.WhileSubscribed(),
-            quickQuickSettingsRowInteractor.defaultRows,
+    val columns by qsColumnsViewModel.columns
+
+    private val largeTiles by
+        hydrator.hydratedStateOf(traceName = "largeTiles", source = iconTilesViewModel.largeTiles)
+
+    private val rows by
+        hydrator.hydratedStateOf(
+            traceName = "rows",
+            initialValue = quickQuickSettingsRowInteractor.defaultRows,
+            source = quickQuickSettingsRowInteractor.rows,
         )
 
-    val tileViewModels: StateFlow<List<SizedTile<TileViewModel>>> =
-        columns
-            .flatMapLatest { columns ->
-                tilesInteractor.currentTiles.combine(rows, ::Pair).mapLatest { (tiles, rows) ->
-                    tiles
-                        .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
-                        .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
-                }
-            }
-            .stateIn(
-                applicationScope,
-                SharingStarted.WhileSubscribed(),
-                tilesInteractor.currentTiles.value
-                    .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
-                    .let {
-                        splitInRowsSequence(it, columns.value).take(rows.value).toList().flatten()
-                    },
-            )
+    private val currentTiles by
+        hydrator.hydratedStateOf(traceName = "currentTiles", source = tilesInteractor.currentTiles)
+
+    val tileViewModels by derivedStateOf {
+        currentTiles
+            .map { SizedTileImpl(TileViewModel(it.tile, it.spec), it.spec.width) }
+            .let { splitInRowsSequence(it, columns).take(rows).toList().flatten() }
+    }
 
     private val TileSpec.width: Int
-        get() = if (iconTilesViewModel.isIconTile(this)) 1 else 2
+        get() = if (largeTiles.contains(this)) 2 else 1
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch { hydrator.activate() }
+            launch { qsColumnsViewModel.activate() }
+            awaitCancellation()
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): QuickQuickSettingsViewModel
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index b1eb3bb3..da175c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -24,22 +24,31 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
 
 class QuickSettingsContainerViewModel
 @AssistedInject
 constructor(
     brightnessSliderViewModelFactory: BrightnessSliderViewModel.Factory,
+    quickQuickSettingsViewModelFactory: QuickQuickSettingsViewModel.Factory,
     @Assisted supportsBrightnessMirroring: Boolean,
     val tileGridViewModel: TileGridViewModel,
     val editModeViewModel: EditModeViewModel,
-    val quickQuickSettingsViewModel: QuickQuickSettingsViewModel,
 ) : ExclusiveActivatable() {
 
     val brightnessSliderViewModel =
         brightnessSliderViewModelFactory.create(supportsBrightnessMirroring)
 
+    val quickQuickSettingsViewModel = quickQuickSettingsViewModelFactory.create()
+
     override suspend fun onActivated(): Nothing {
-        brightnessSliderViewModel.activate()
+        coroutineScope {
+            launch { brightnessSliderViewModel.activate() }
+            launch { quickQuickSettingsViewModel.activate() }
+            awaitCancellation()
+        }
     }
 
     @AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 5229acc..f3c6190 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -19,6 +19,7 @@
 package com.android.systemui.scene.domain.startable
 
 import android.app.StatusBarManager
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.compose.animation.scene.ObservableTransitionState
 import com.android.compose.animation.scene.SceneKey
 import com.android.internal.logging.UiEventLogger
@@ -102,7 +103,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Hooks up business logic that manipulates the state of the [SceneInteractor] for the system UI
@@ -300,7 +300,7 @@
             bouncerInteractor.onImeHiddenByUser.collectLatest {
                 if (sceneInteractor.currentScene.value == Scenes.Bouncer) {
                     sceneInteractor.changeScene(
-                        toScene = Scenes.Lockscreen, // TODO(b/336581871): add sceneState?
+                        toScene = Scenes.Lockscreen,
                         loggingReason = "IME hidden",
                     )
                 }
@@ -318,7 +318,6 @@
                     when {
                         isAnySimLocked -> {
                             switchToScene(
-                                // TODO(b/336581871): add sceneState?
                                 targetSceneKey = Scenes.Bouncer,
                                 loggingReason = "Need to authenticate locked SIM card.",
                             )
@@ -326,7 +325,6 @@
                         unlockStatus.isUnlocked &&
                             deviceEntryInteractor.canSwipeToEnter.value == false -> {
                             switchToScene(
-                                // TODO(b/336581871): add sceneState?
                                 targetSceneKey = Scenes.Gone,
                                 loggingReason =
                                     "All SIM cards unlocked and device already unlocked and " +
@@ -335,7 +333,6 @@
                         }
                         else -> {
                             switchToScene(
-                                // TODO(b/336581871): add sceneState?
                                 targetSceneKey = Scenes.Lockscreen,
                                 loggingReason =
                                     "All SIM cards unlocked and device still locked" +
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index cd38ca6..7d7cab4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -77,6 +77,11 @@
         }
     }
 
+    override fun onTouchEvent(event: MotionEvent?): Boolean {
+        event?.let { motionEventHandler?.onEmptySpaceMotionEvent(it) }
+        return super.onTouchEvent(event)
+    }
+
     companion object {
         private const val TAG = "SceneWindowRootView"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index 82f65cf..32d5cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.scene.ui.composable.Overlay
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
+import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
@@ -48,7 +49,6 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Models UI state for the scene container. */
 class SceneContainerViewModel
@@ -58,6 +58,7 @@
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
     shadeInteractor: ShadeInteractor,
+    private val remoteInputInteractor: RemoteInputInteractor,
     private val splitEdgeDetector: SplitEdgeDetector,
     private val logger: SceneLogger,
     hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory,
@@ -101,6 +102,10 @@
                         this@SceneContainerViewModel.onMotionEvent(motionEvent)
                     }
 
+                    override fun onEmptySpaceMotionEvent(motionEvent: MotionEvent) {
+                        this@SceneContainerViewModel.onEmptySpaceMotionEvent(motionEvent)
+                    }
+
                     override fun onMotionEventComplete() {
                         this@SceneContainerViewModel.onMotionEventComplete()
                     }
@@ -147,6 +152,23 @@
     }
 
     /**
+     * Notifies that a [MotionEvent] has propagated through the entire [SharedNotificationContainer]
+     * and Composable scene container hierarchy without being handled.
+     *
+     * Call this after the [MotionEvent] has finished propagating through the UI hierarchy.
+     */
+    fun onEmptySpaceMotionEvent(event: MotionEvent) {
+        // check if the touch is outside the window and if remote input is active.
+        // If true, close any active remote inputs.
+        if (
+            event.action == MotionEvent.ACTION_OUTSIDE &&
+                (remoteInputInteractor.isRemoteInputActive as StateFlow).value
+        ) {
+            remoteInputInteractor.closeRemoteInputs()
+        }
+    }
+
+    /**
      * Notifies that a scene container user interaction has begun.
      *
      * This is a user interaction that has reached the Composable hierarchy of the scene container,
@@ -263,6 +285,9 @@
         /** Notifies that a [MotionEvent] has occurred. */
         fun onMotionEvent(motionEvent: MotionEvent)
 
+        /** Notifies that a [MotionEvent] has occurred outside the root window. */
+        fun onEmptySpaceMotionEvent(motionEvent: MotionEvent)
+
         /**
          * Notifies that the previous [MotionEvent] reported by [onMotionEvent] has finished
          * processing.
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0e82bf8..c15c8f9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2053,6 +2053,9 @@
         }
         if (mQsController.getExpanded()) {
             mQsController.flingQs(0, FLING_COLLAPSE);
+        } else if (mBarState == KEYGUARD) {
+            mLockscreenShadeTransitionController.goToLockedShade(
+                    /* expandedView= */null, /* needsQSAnimation= */false);
         } else {
             expand(true /* animate */);
         }
@@ -3109,7 +3112,7 @@
         if (isTracking()) {
             onTrackingStopped(true);
         }
-        if (isExpanded() && !mQsController.getExpanded()) {
+        if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
             mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
             expandToQs();
         } else {
@@ -5091,13 +5094,6 @@
             }
             boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
 
-            if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
-                    event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
-                if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
-                    mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
-                }
-                return true;
-            }
             // This touch session has already resulted in shade expansion. Ignore everything else.
             if (ShadeExpandsOnStatusBarLongPress.isEnabled()
                     && event.getActionMasked() != MotionEvent.ACTION_DOWN
@@ -5105,6 +5101,13 @@
                 mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
                 return false;
             }
+            if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+                    event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
+                if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+                    mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+                }
+                return true;
+            }
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
                 mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
                 handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index be2bf82..f2c3906 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -88,6 +88,7 @@
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Controller for {@link NotificationShadeWindowView}.
@@ -193,7 +194,7 @@
             PrimaryBouncerInteractor primaryBouncerInteractor,
             AlternateBouncerInteractor alternateBouncerInteractor,
             BouncerViewBinder bouncerViewBinder,
-            @ShadeDisplayAware ConfigurationForwarder configurationForwarder,
+            @ShadeDisplayAware Provider<ConfigurationForwarder> configurationForwarder,
             BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
@@ -257,7 +258,7 @@
         }
 
         if (ShadeWindowGoesAround.isEnabled()) {
-            mView.setConfigurationForwarder(configurationForwarder);
+            mView.setConfigurationForwarder(configurationForwarder.get());
         }
         dumpManager.registerDumpable(this);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index 49fa80c..5b06ad2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade
 
 import android.view.MotionEvent
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.assist.AssistManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -39,7 +40,6 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.first
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.withContext
 
 /**
@@ -136,7 +136,6 @@
     }
 
     private fun animateCollapseShadeInternal() {
-        // TODO(b/336581871): add sceneState?
         shadeInteractor.collapseEitherShade(
             loggingReason = "ShadeController.animateCollapseShade",
             transitionKey = SlightlyFasterShadeCollapse,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 51f1f81..32255c6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,7 +20,11 @@
 import android.content.res.Resources
 import android.view.LayoutInflater
 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.ConfigurationStateImpl
 import com.android.systemui.common.ui.GlobalConfig
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
 import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
@@ -79,12 +83,12 @@
     fun provideShadeWindowConfigurationController(
         @ShadeDisplayAware shadeContext: Context,
         factory: ConfigurationControllerImpl.Factory,
-        @GlobalConfig globalConfigConfigController: ConfigurationController,
+        @GlobalConfig globalConfigController: ConfigurationController,
     ): ConfigurationController {
         return if (ShadeWindowGoesAround.isEnabled) {
             factory.create(shadeContext)
         } else {
-            globalConfigConfigController
+            globalConfigController
         }
     }
 
@@ -93,12 +97,40 @@
     @SysUISingleton
     fun provideShadeWindowConfigurationForwarder(
         @ShadeDisplayAware shadeConfigurationController: ConfigurationController,
-        @GlobalConfig globalConfigController: ConfigurationController,
     ): ConfigurationForwarder {
+        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+        return shadeConfigurationController
+    }
+
+    @SysUISingleton
+    @Provides
+    @ShadeDisplayAware
+    fun provideShadeDisplayAwareConfigurationState(
+        factory: ConfigurationStateImpl.Factory,
+        @ShadeDisplayAware configurationController: ConfigurationController,
+        @ShadeDisplayAware context: Context,
+        @GlobalConfig configurationState: ConfigurationState,
+    ): ConfigurationState {
         return if (ShadeWindowGoesAround.isEnabled) {
-            shadeConfigurationController
+            factory.create(context, configurationController)
         } else {
-            globalConfigController
+            configurationState
+        }
+    }
+
+    @SysUISingleton
+    @Provides
+    @ShadeDisplayAware
+    fun provideShadeDisplayAwareConfigurationRepository(
+        factory: ConfigurationRepositoryImpl.Factory,
+        @ShadeDisplayAware configurationController: ConfigurationController,
+        @ShadeDisplayAware context: Context,
+        @GlobalConfig globalConfigurationRepository: ConfigurationRepository
+    ): ConfigurationRepository {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            factory.create(context, configurationController)
+        } else {
+            globalConfigurationRepository
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
rename to packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index 6fb3ca5..ae36e81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -25,7 +25,7 @@
 
 /** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
 @SysUISingleton
-class LongPressGestureDetector
+class StatusBarLongPressGestureDetector
 @Inject
 constructor(context: Context, val shadeViewController: ShadeViewController) {
     val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
index 3a483f4..f151307 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImpl.kt
@@ -41,7 +41,6 @@
                 } else {
                     Scenes.Shade
                 }
-            // TODO(b/336581871): add sceneState?
             sceneInteractor.changeScene(key, "animateCollapseQs")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index e5d08a0..a8d616c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -51,7 +51,7 @@
     @Application private val applicationScope: CoroutineScope,
     @ShadeDisplayAware private val context: Context,
     @ShadeTouchLog private val touchLog: LogBuffer,
-    private val configurationRepository: ConfigurationRepository,
+    @ShadeDisplayAware private val configurationRepository: ConfigurationRepository,
     private val shadeRepository: ShadeRepository,
     private val splitShadeStateController: SplitShadeStateController,
     private val scrimShadeTransitionController: ScrimShadeTransitionController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 30f564f..3a24ec9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -61,15 +61,6 @@
             return row.getEntry().getSbn().getNotification();
         }
     };
-    private static final ResultApplicator GREY_APPLICATOR = new ResultApplicator() {
-        @Override
-        public void apply(View parent, View view, boolean apply, boolean reset) {
-            CachingIconView icon = view.findViewById(com.android.internal.R.id.icon);
-            if (icon != null) {
-                icon.setGrayedOut(apply);
-            }
-        }
-    };
 
     private final ExpandableNotificationRow mRow;
     private final ArrayList<Processor> mProcessors = new ArrayList<>();
@@ -78,14 +69,18 @@
     public NotificationGroupingUtil(ExpandableNotificationRow row) {
         mRow = row;
 
-        final IconComparator iconVisibilityComparator = new IconComparator(mRow) {
+        final IconComparator iconVisibilityComparator = new IconComparator() {
             public boolean compare(View parent, View child, Object parentData,
                     Object childData) {
+                if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
+                    // Icon is always the same when we're showing the app icon.
+                    return true;
+                }
                 return hasSameIcon(parentData, childData)
                         && hasSameColor(parentData, childData);
             }
         };
-        final IconComparator greyComparator = new IconComparator(mRow) {
+        final IconComparator greyComparator = new IconComparator() {
             public boolean compare(View parent, View child, Object parentData,
                     Object childData) {
                 if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
@@ -95,6 +90,19 @@
                         || hasSameColor(parentData, childData);
             }
         };
+        final ResultApplicator greyApplicator = new ResultApplicator() {
+            @Override
+            public void apply(View parent, View view, boolean apply, boolean reset) {
+                if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
+                    // Do nothing.
+                    return;
+                }
+                CachingIconView icon = view.findViewById(com.android.internal.R.id.icon);
+                if (icon != null) {
+                    icon.setGrayedOut(apply);
+                }
+            }
+        };
 
         // To hide the icons if they are the same and the color is the same
         mProcessors.add(new Processor(mRow,
@@ -107,7 +115,7 @@
                 com.android.internal.R.id.status_bar_latest_event_content,
                 ICON_EXTRACTOR,
                 greyComparator,
-                GREY_APPLICATOR));
+                greyApplicator));
         // To show the large icon on the left side instead if all the small icons are the same
         mProcessors.add(new Processor(mRow,
                 com.android.internal.R.id.status_bar_latest_event_content,
@@ -319,13 +327,14 @@
 
     private interface ViewComparator {
         /**
-         * @param parent the view with the given id in the group header
-         * @param child the view with the given id in the child notification
+         * @param parent     the view with the given id in the group header
+         * @param child      the view with the given id in the child notification
          * @param parentData optional data for the parent
-         * @param childData optional data for the child
+         * @param childData  optional data for the child
          * @return whether to views are the same
          */
         boolean compare(View parent, View child, Object parentData, Object childData);
+
         boolean isEmpty(View view);
     }
 
@@ -368,21 +377,12 @@
     }
 
     private abstract static class IconComparator implements ViewComparator {
-        private final ExpandableNotificationRow mRow;
-
-        IconComparator(ExpandableNotificationRow row) {
-            mRow = row;
-        }
-
         @Override
         public boolean compare(View parent, View child, Object parentData, Object childData) {
             return false;
         }
 
         protected boolean hasSameIcon(Object parentData, Object childData) {
-            if (Flags.notificationsRedesignAppIcons() && mRow.isShowingAppIcon()) {
-                return true;
-            }
             Icon parentIcon = getIcon((Notification) parentData);
             Icon childIcon = getIcon((Notification) childData);
             return parentIcon.sameAs(childIcon);
@@ -420,9 +420,9 @@
     private interface ResultApplicator {
         /**
          * @param parent the root view of the child notification
-         * @param view the view with the given id in the child notification
-         * @param apply whether the state should be applied or removed
-         * @param reset if [de]application is the result of a reset
+         * @param view   the view with the given id in the child notification
+         * @param apply  whether the state should be applied or removed
+         * @param reset  if [de]application is the result of a reset
          */
         void apply(View parent, View view, boolean apply, boolean reset);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 9b1e782..fdc1c0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -53,6 +53,7 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.dagger.CentralSurfacesDependenciesModule;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -686,6 +687,7 @@
     }
 
     public void checkRemoteInputOutside(MotionEvent event) {
+        SceneContainerFlag.assertInLegacyMode();
         if (event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
                 && event.getX() == 0 && event.getY() == 0  // a touch outside both bars
                 && isRemoteInputActive()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
index 57c8bc6..614f0f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/CommandQueueInitializer.kt
@@ -46,7 +46,7 @@
 ) : CoreStartable {
 
     override fun start() {
-        StatusBarSimpleFragment.assertInNewMode()
+        StatusBarConnectedDisplays.assertInNewMode()
         val result: RegisterStatusBarResult =
             try {
                 barService.registerStatusBar(commandQueue)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index e115922..9b3513e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.statusbar.core
 
 import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStore
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
@@ -29,7 +31,6 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Responsible for creating and starting the status bar components for each display. Also does it
@@ -48,6 +49,7 @@
     private val initializerStore: StatusBarInitializerStore,
     private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
     private val statusBarInitializerStore: StatusBarInitializerStore,
+    private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
 ) : CoreStartable {
 
     init {
@@ -71,6 +73,7 @@
         val displayId = display.displayId
         createAndStartOrchestratorForDisplay(displayId)
         createAndStartInitializerForDisplay(displayId)
+        startPrivacyDotForDisplay(displayId)
     }
 
     private fun createAndStartOrchestratorForDisplay(displayId: Int) {
@@ -89,4 +92,12 @@
     private fun createAndStartInitializerForDisplay(displayId: Int) {
         statusBarInitializerStore.forDisplay(displayId).start()
     }
+
+    private fun startPrivacyDotForDisplay(displayId: Int) {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            // For the default display, privacy dot is started via ScreenDecorations
+            return
+        }
+        privacyDotWindowControllerStore.forDisplay(displayId).start()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index f65ae67..4341200 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.log.LogBufferFactory
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.data.StatusBarDataLayerModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore
 import com.android.systemui.statusbar.phone.LightBarController
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
@@ -55,26 +56,26 @@
  *   [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
  */
 @Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
-abstract class StatusBarModule {
+interface StatusBarModule {
 
     @Binds
     @IntoMap
     @ClassKey(OngoingCallController::class)
-    abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
+    fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
 
     @Binds
     @IntoMap
     @ClassKey(LightBarController::class)
-    abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+    fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable
 
     @Binds
     @IntoMap
     @ClassKey(StatusBarSignalPolicy::class)
-    abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+    fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
 
     @Binds
     @SysUISingleton
-    abstract fun statusBarWindowControllerFactory(
+    fun statusBarWindowControllerFactory(
         implFactory: StatusBarWindowControllerImpl.Factory
     ): StatusBarWindowController.Factory
 
@@ -82,6 +83,12 @@
 
         @Provides
         @SysUISingleton
+        fun lightBarController(store: LightBarControllerStore): LightBarController {
+            return store.defaultDisplay
+        }
+
+        @Provides
+        @SysUISingleton
         fun windowControllerStore(
             multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
             singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index c416bf7..39de28e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -16,10 +16,12 @@
 package com.android.systemui.statusbar.data
 
 import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule
 import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
 import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStoreModule
 import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
 import dagger.Module
 
@@ -27,11 +29,13 @@
     includes =
         [
             KeyguardStatusBarRepositoryModule::class,
+            LightBarControllerStoreModule::class,
             RemoteInputRepositoryModule::class,
             StatusBarConfigurationControllerModule::class,
             StatusBarContentInsetsProviderStoreModule::class,
             StatusBarModeRepositoryModule::class,
             StatusBarPhoneDataLayerModule::class,
+            SystemEventChipAnimationControllerStoreModule::class,
         ]
 )
 object StatusBarDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
new file mode 100644
index 0000000..ff50e31
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.statusbar.data.repository
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarControllerImpl
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [LightBarController]. */
+interface LightBarControllerStore : PerDisplayStore<LightBarController>
+
+@SysUISingleton
+class LightBarControllerStoreImpl
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    displayRepository: DisplayRepository,
+    private val factory: LightBarControllerImpl.Factory,
+    private val displayScopeRepository: DisplayScopeRepository,
+    private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
+) :
+    LightBarControllerStore,
+    PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
+
+    override fun createInstanceForDisplay(displayId: Int): LightBarController {
+        return factory
+            .create(
+                displayId,
+                displayScopeRepository.scopeForDisplay(displayId),
+                statusBarModeRepositoryStore.forDisplay(displayId),
+            )
+            .also { it.start() }
+    }
+
+    override suspend fun onDisplayRemovalAction(instance: LightBarController) {
+        instance.stop()
+    }
+
+    override val instanceClass = LightBarController::class.java
+}
+
+@Module
+interface LightBarControllerStoreModule {
+
+    @Binds fun store(impl: LightBarControllerStoreImpl): LightBarControllerStore
+
+    @Binds
+    @IntoMap
+    @ClassKey(LightBarControllerStore::class)
+    fun storeAsCoreStartable(impl: LightBarControllerStoreImpl): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
new file mode 100644
index 0000000..a1f5655
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.view.Display
+import android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.events.PrivacyDotWindowController
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Providers per display instances of [PrivacyDotWindowController]. */
+interface PrivacyDotWindowControllerStore : PerDisplayStore<PrivacyDotWindowController>
+
+@SysUISingleton
+class PrivacyDotWindowControllerStoreImpl
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    displayRepository: DisplayRepository,
+    private val windowControllerFactory: PrivacyDotWindowController.Factory,
+    private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+    private val privacyDotViewControllerStore: PrivacyDotViewControllerStore,
+    private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
+) :
+    PrivacyDotWindowControllerStore,
+    PerDisplayStoreImpl<PrivacyDotWindowController>(backgroundApplicationScope, displayRepository) {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController {
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            throw IllegalArgumentException("This class should only be used for connected displays")
+        }
+        val displayWindowProperties =
+            displayWindowPropertiesRepository.get(displayId, TYPE_NAVIGATION_BAR_PANEL)
+        return windowControllerFactory.create(
+            displayId = displayId,
+            privacyDotViewController = privacyDotViewControllerStore.forDisplay(displayId),
+            viewCaptureAwareWindowManager =
+                viewCaptureAwareWindowManagerFactory.create(displayWindowProperties.windowManager),
+            inflater = displayWindowProperties.layoutInflater,
+        )
+    }
+
+    override val instanceClass = PrivacyDotWindowController::class.java
+}
+
+@Module
+interface PrivacyDotWindowControllerStoreModule {
+
+    @Binds fun store(impl: PrivacyDotWindowControllerStoreImpl): PrivacyDotWindowControllerStore
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(PrivacyDotWindowControllerStore::class)
+        fun storeAsCoreStartable(
+            storeLazy: Lazy<PrivacyDotWindowControllerStoreImpl>
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                storeLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
index 9af4b8c..cd1e2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/RemoteInputRepository.kt
@@ -16,16 +16,21 @@
 
 package com.android.systemui.statusbar.data.repository
 
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.RemoteInputController
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
 import dagger.Binds
 import dagger.Module
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * Repository used for tracking the state of notification remote input (e.g. when the user presses
@@ -42,30 +47,50 @@
     val remoteInputRowBottomBound: Flow<Float?>
 
     fun setRemoteInputRowBottomBound(bottom: Float?)
+
+    /** Close any active remote inputs */
+    fun closeRemoteInputs()
 }
 
 @SysUISingleton
 class RemoteInputRepositoryImpl
 @Inject
-constructor(private val notificationRemoteInputManager: NotificationRemoteInputManager) :
-    RemoteInputRepository {
-    override val isRemoteInputActive: Flow<Boolean> = conflatedCallbackFlow {
-        trySend(false) // initial value is false
+constructor(
+    @Application applicationScope: CoroutineScope,
+    private val notificationRemoteInputManager: NotificationRemoteInputManager,
+) : RemoteInputRepository {
+    private val _isRemoteInputActive = conflatedCallbackFlow {
         val callback =
             object : RemoteInputController.Callback {
                 override fun onRemoteInputActive(active: Boolean) {
                     trySend(active)
                 }
             }
+        trySend(notificationRemoteInputManager.isRemoteInputActive)
         notificationRemoteInputManager.addControllerCallback(callback)
         awaitClose { notificationRemoteInputManager.removeControllerCallback(callback) }
     }
 
+    override val isRemoteInputActive =
+        if (SceneContainerFlag.isEnabled) {
+            _isRemoteInputActive.stateIn(
+                applicationScope,
+                SharingStarted.WhileSubscribed(),
+                notificationRemoteInputManager.isRemoteInputActive,
+            )
+        } else {
+            _isRemoteInputActive
+        }
+
     override val remoteInputRowBottomBound = MutableStateFlow<Float?>(null)
 
     override fun setRemoteInputRowBottomBound(bottom: Float?) {
         remoteInputRowBottomBound.value = bottom
     }
+
+    override fun closeRemoteInputs() {
+        notificationRemoteInputManager.closeRemoteInputs()
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
new file mode 100644
index 0000000..7760f58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.events.SystemEventChipAnimationController
+import com.android.systemui.statusbar.events.SystemEventChipAnimationControllerImpl
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [SystemEventChipAnimationController]. */
+interface SystemEventChipAnimationControllerStore :
+    PerDisplayStore<SystemEventChipAnimationController>
+
+@SysUISingleton
+class SystemEventChipAnimationControllerStoreImpl
+@Inject
+constructor(
+    @Background backgroundApplicationScope: CoroutineScope,
+    displayRepository: DisplayRepository,
+    private val factory: SystemEventChipAnimationControllerImpl.Factory,
+    private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+    private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+    private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+) :
+    SystemEventChipAnimationControllerStore,
+    PerDisplayStoreImpl<SystemEventChipAnimationController>(
+        backgroundApplicationScope,
+        displayRepository,
+    ) {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun createInstanceForDisplay(displayId: Int): SystemEventChipAnimationController {
+        return factory.create(
+            displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR).context,
+            statusBarWindowControllerStore.forDisplay(displayId),
+            statusBarContentInsetsProviderStore.forDisplay(displayId),
+        )
+    }
+
+    override suspend fun onDisplayRemovalAction(instance: SystemEventChipAnimationController) {
+        instance.stop()
+    }
+
+    override val instanceClass = SystemEventChipAnimationController::class.java
+}
+
+@Module
+interface SystemEventChipAnimationControllerStoreModule {
+
+    @Binds
+    @SysUISingleton
+    fun store(
+        impl: SystemEventChipAnimationControllerStoreImpl
+    ): SystemEventChipAnimationControllerStore
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        @IntoMap
+        @ClassKey(SystemEventChipAnimationControllerStore::class)
+        fun storeAsCoreStartable(
+            implLazy: Lazy<SystemEventChipAnimationControllerStoreImpl>
+        ): CoreStartable {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                implLazy.get()
+            } else {
+                CoreStartable.NOP
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
index b83b0cc..ea8c3e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/domain/interactor/RemoteInputInteractor.kt
@@ -40,4 +40,9 @@
     fun setRemoteInputRowBottomBound(bottom: Float?) {
         remoteInputRepository.setRemoteInputRowBottomBound(bottom)
     }
+
+    /** Close any active remote inputs */
+    fun closeRemoteInputs() {
+        remoteInputRepository.closeRemoteInputs()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
new file mode 100644
index 0000000..f2bb7b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.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.statusbar.events
+
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorSet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStore
+import javax.inject.Inject
+
+/**
+ * A [SystemEventChipAnimationController] that handles animations for multiple displays. It
+ * delegates the animation tasks to individual controllers for each display.
+ */
+@SysUISingleton
+class MultiDisplaySystemEventChipAnimationController
+@Inject
+constructor(
+    private val displayRepository: DisplayRepository,
+    private val controllerStore: SystemEventChipAnimationControllerStore,
+) : SystemEventChipAnimationController {
+
+    init {
+        StatusBarConnectedDisplays.assertInNewMode()
+    }
+
+    override fun prepareChipAnimation(viewCreator: ViewCreator) {
+        forEachController { it.prepareChipAnimation(viewCreator) }
+    }
+
+    override fun init() {
+        forEachController { it.init() }
+    }
+
+    override fun stop() {
+        forEachController { it.stop() }
+    }
+
+    override fun announceForAccessibility(contentDescriptions: String) {
+        forEachController { it.announceForAccessibility(contentDescriptions) }
+    }
+
+    override fun onSystemEventAnimationBegin(): Animator {
+        val animators = controllersForAllDisplays().map { it.onSystemEventAnimationBegin() }
+        return AnimatorSet().apply { playTogether(animators) }
+    }
+
+    override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
+        val animators =
+            controllersForAllDisplays().map { it.onSystemEventAnimationFinish(hasPersistentDot) }
+        return AnimatorSet().apply { playTogether(animators) }
+    }
+
+    private fun forEachController(consumer: (SystemEventChipAnimationController) -> Unit) {
+        controllersForAllDisplays().forEach { consumer(it) }
+    }
+
+    private fun controllersForAllDisplays() =
+        displayRepository.displays.value.map { controllerStore.forDisplay(it.displayId) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
new file mode 100644
index 0000000..9928ac6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
@@ -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.systemui.statusbar.events
+
+import android.view.Display
+import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM
+import android.view.DisplayCutout.BOUNDS_POSITION_LEFT
+import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT
+import android.view.DisplayCutout.BOUNDS_POSITION_TOP
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.ScreenDecorations
+import com.android.systemui.ScreenDecorationsThread
+import com.android.systemui.decor.DecorProvider
+import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl
+import com.android.systemui.decor.PrivacyDotDecorProviderFactory
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight
+import com.android.systemui.util.containsExactly
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
+
+/**
+ * Responsible for adding the privacy dot to a window.
+ *
+ * It will create one window per corner (top left, top right, bottom left, bottom right), which are
+ * used dependant on the display's rotation.
+ */
+class PrivacyDotWindowController
+@AssistedInject
+constructor(
+    @Assisted private val displayId: Int,
+    @Assisted private val privacyDotViewController: PrivacyDotViewController,
+    @Assisted private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+    @Assisted private val inflater: LayoutInflater,
+    @ScreenDecorationsThread private val uiExecutor: Executor,
+    private val dotFactory: PrivacyDotDecorProviderFactory,
+) {
+
+    fun start() {
+        uiExecutor.execute { startOnUiThread() }
+    }
+
+    private fun startOnUiThread() {
+        val providers = dotFactory.providers
+
+        val topLeft = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_LEFT)
+        val topRight = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_RIGHT)
+        val bottomLeft = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_LEFT)
+        val bottomRight = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_RIGHT)
+
+        topLeft.addToWindow(TopLeft)
+        topRight.addToWindow(TopRight)
+        bottomLeft.addToWindow(BottomLeft)
+        bottomRight.addToWindow(BottomRight)
+
+        privacyDotViewController.initialize(topLeft, topRight, bottomLeft, bottomRight)
+    }
+
+    private fun List<DecorProvider>.inflate(alignedBound1: Int, alignedBound2: Int): View {
+        val provider =
+            first { it.alignedBounds.containsExactly(alignedBound1, alignedBound2) }
+                as PrivacyDotCornerDecorProviderImpl
+        return inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null)
+    }
+
+    private fun View.addToWindow(corner: PrivacyDotCorner) {
+        val excludeFromScreenshots = displayId == Display.DEFAULT_DISPLAY
+        val params =
+            ScreenDecorations.getWindowLayoutBaseParams(excludeFromScreenshots).apply {
+                width = WRAP_CONTENT
+                height = WRAP_CONTENT
+                gravity = corner.rotatedCorner(context.display.rotation).gravity
+                title = "PrivacyDot${corner.title}$displayId"
+            }
+        // PrivacyDotViewController expects the dot view to have a FrameLayout parent.
+        val rootView = FrameLayout(context)
+        rootView.addView(this)
+        viewCaptureAwareWindowManager.addView(rootView, params)
+    }
+
+    @AssistedFactory
+    fun interface Factory {
+        fun create(
+            displayId: Int,
+            privacyDotViewController: PrivacyDotViewController,
+            viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+            inflater: LayoutInflater,
+        ): PrivacyDotWindowController
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index b286605..1038ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -32,13 +32,16 @@
 import androidx.core.animation.ValueAnimator
 import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Default
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
 import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
+import dagger.Lazy
 import dagger.Module
 import dagger.Provides
 import dagger.assisted.Assisted
@@ -57,6 +60,8 @@
 
     fun init()
 
+    fun stop()
+
     /** Announces [contentDescriptions] for accessibility. */
     fun announceForAccessibility(contentDescriptions: String)
 
@@ -287,6 +292,26 @@
         return animSet
     }
 
+    private val statusBarContentInsetsChangedListener =
+        object : StatusBarContentInsetsChangedListener {
+            override fun onStatusBarContentInsetsChanged() {
+                val newContentArea =
+                    contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
+                updateDimens(newContentArea)
+
+                // If we are currently animating, we have to re-solve for the chip bounds. If
+                // we're not animating then [prepareChipAnimation] will take care of it for us.
+                currentAnimatedView?.let {
+                    updateChipBounds(it, newContentArea)
+                    // Since updateCurrentAnimatedView can only be called during an animation,
+                    // we have to create a no-op animator here to apply the new chip bounds.
+                    val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
+                    animator.addUpdateListener { updateCurrentAnimatedView() }
+                    animator.start()
+                }
+            }
+        }
+
     override fun init() {
         initialized = true
         themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
@@ -303,28 +328,11 @@
 
         // Use contentInsetsProvider rather than configuration controller, since we only care
         // about status bar dimens
-        contentInsetsProvider.addCallback(
-            object : StatusBarContentInsetsChangedListener {
-                override fun onStatusBarContentInsetsChanged() {
-                    val newContentArea =
-                        contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
-                    updateDimens(newContentArea)
+        contentInsetsProvider.addCallback(statusBarContentInsetsChangedListener)
+    }
 
-                    // If we are currently animating, we have to re-solve for the chip bounds. If
-                    // we're
-                    // not animating then [prepareChipAnimation] will take care of it for us
-                    currentAnimatedView?.let {
-                        updateChipBounds(it, newContentArea)
-                        // Since updateCurrentAnimatedView can only be called during an animation,
-                        // we
-                        // have to create a dummy animator here to apply the new chip bounds
-                        val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
-                        animator.addUpdateListener { updateCurrentAnimatedView() }
-                        animator.start()
-                    }
-                }
-            }
-        )
+    override fun stop() {
+        contentInsetsProvider.removeCallback(statusBarContentInsetsChangedListener)
     }
 
     override fun announceForAccessibility(contentDescriptions: String) {
@@ -418,7 +426,7 @@
     }
 
     @AssistedFactory
-    interface Factory {
+    fun interface Factory {
         fun create(
             context: Context,
             statusBarWindowController: StatusBarWindowController,
@@ -446,20 +454,36 @@
 private const val RIGHT = 2
 
 @Module
-object SystemEventChipAnimationControllerModule {
+interface SystemEventChipAnimationControllerModule {
 
-    @Provides
-    @SysUISingleton
-    fun controller(
-        factory: SystemEventChipAnimationControllerImpl.Factory,
-        context: Context,
-        statusBarWindowControllerStore: StatusBarWindowControllerStore,
-        contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
-    ): SystemEventChipAnimationController {
-        return factory.create(
-            context,
-            statusBarWindowControllerStore.defaultDisplay,
-            contentInsetsProviderStore.defaultDisplay,
-        )
+    companion object {
+        @Provides
+        @Default
+        @SysUISingleton
+        fun defaultController(
+            factory: SystemEventChipAnimationControllerImpl.Factory,
+            context: Context,
+            statusBarWindowControllerStore: StatusBarWindowControllerStore,
+            contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+        ): SystemEventChipAnimationController {
+            return factory.create(
+                context,
+                statusBarWindowControllerStore.defaultDisplay,
+                contentInsetsProviderStore.defaultDisplay,
+            )
+        }
+
+        @Provides
+        @SysUISingleton
+        fun controller(
+            @Default defaultLazy: Lazy<SystemEventChipAnimationController>,
+            multiDisplayLazy: Lazy<MultiDisplaySystemEventChipAnimationController>,
+        ): SystemEventChipAnimationController {
+            return if (StatusBarConnectedDisplays.isEnabled) {
+                multiDisplayLazy.get()
+            } else {
+                defaultLazy.get()
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index aa2a08f..08d177f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1987,6 +1987,7 @@
         mColorUpdateLogger = colorUpdateLogger;
         mDismissibilityProvider = dismissibilityProvider;
         mFeatureFlags = featureFlags;
+        setHapticFeedbackEnabled(!com.android.systemui.Flags.msdlFeedback());
     }
 
     private void initDimens() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 23a2fac..baad616 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -64,6 +64,9 @@
 import com.android.systemui.util.time.SystemClock;
 import com.android.systemui.wmshell.BubblesManager;
 
+import com.google.android.msdl.data.model.MSDLToken;
+import com.google.android.msdl.domain.MSDLPlayer;
+
 import java.util.List;
 import java.util.Optional;
 
@@ -114,6 +117,7 @@
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     private final IStatusBarService mStatusBarService;
     private final UiEventLogger mUiEventLogger;
+    private final MSDLPlayer mMSDLPlayer;
 
     private final NotificationSettingsController mSettingsController;
 
@@ -273,7 +277,8 @@
             ExpandableNotificationRowDragController dragController,
             NotificationDismissibilityProvider dismissibilityProvider,
             IStatusBarService statusBarService,
-            UiEventLogger uiEventLogger) {
+            UiEventLogger uiEventLogger,
+            MSDLPlayer msdlPlayer) {
         mView = view;
         mListContainer = listContainer;
         mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory;
@@ -309,6 +314,7 @@
         mDismissibilityProvider = dismissibilityProvider;
         mStatusBarService = statusBarService;
         mUiEventLogger = uiEventLogger;
+        mMSDLPlayer = msdlPlayer;
     }
 
     /**
@@ -352,6 +358,9 @@
             }
 
             mView.setLongPressListener((v, x, y, item) -> {
+                if (com.android.systemui.Flags.msdlFeedback()) {
+                    mMSDLPlayer.playToken(MSDLToken.LONG_PRESS, null);
+                }
                 if (mView.isSummaryWithChildren()) {
                     mView.expandNotification();
                     return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index c1d72e4..57af8ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -678,7 +678,9 @@
         mGroupMembershipManager = Dependency.get(GroupMembershipManager.class);
         mGroupExpansionManager = Dependency.get(GroupExpansionManager.class);
         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-        setWindowInsetsAnimationCallback(mInsetsCallback);
+        if (!SceneContainerFlag.isEnabled()) {
+            setWindowInsetsAnimationCallback(mInsetsCallback);
+        }
     }
 
     /**
@@ -795,7 +797,7 @@
                 && !onKeyguard()
                 && mUpcomingStatusBarState != StatusBarState.KEYGUARD
                 // quick settings don't affect notifications when not in full screen
-                && (mQsExpansionFraction != 1 || !mQsFullScreen)
+                && (getQsExpansionFraction() != 1 || !mQsFullScreen)
                 && !mScreenOffAnimationController.shouldHideNotificationsFooter()
                 && !mIsRemoteInputActive;
     }
@@ -1526,7 +1528,7 @@
         float fraction = mAmbientState.getExpansionFraction();
         // If we are on quick settings, we need to quickly hide it to show the bouncer to avoid an
         // overlap. Otherwise, we maintain the normal fraction for smoothness.
-        if (mAmbientState.isBouncerInTransit() && mQsExpansionFraction > 0f) {
+        if (mAmbientState.isBouncerInTransit() && getQsExpansionFraction() > 0f) {
             fraction = BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(fraction);
         }
         final float stackY = MathUtils.lerp(0, endTopPosition, fraction);
@@ -1550,7 +1552,7 @@
             }
             updateInterpolatedStackHeight(endHeight, fraction);
         } else {
-            if (mQsExpansionFraction <= 0 && !shouldSkipHeightUpdate()) {
+            if (getQsExpansionFraction() <= 0 && !shouldSkipHeightUpdate()) {
                 final float endHeight = updateStackEndHeight(
                         getHeight(), getEmptyBottomMarginInternal(), getTopPadding());
                 updateInterpolatedStackHeight(endHeight, fraction);
@@ -1694,7 +1696,7 @@
             } else if (mQsFullScreen) {
                 int stackStartPosition =
                         getContentHeight() - getTopPadding() + getIntrinsicPadding();
-                int stackEndPosition = mMaxTopPadding + mShelf.getIntrinsicHeight();
+                int stackEndPosition = getMaxTopPadding() + mShelf.getIntrinsicHeight();
                 if (stackStartPosition <= stackEndPosition) {
                     stackHeight = stackEndPosition;
                 } else {
@@ -1703,7 +1705,7 @@
                         stackHeight = (int) height;
                     } else {
                         stackHeight = (int) NotificationUtils.interpolate(stackStartPosition,
-                                stackEndPosition, mQsExpansionFraction);
+                                stackEndPosition, getQsExpansionFraction());
                     }
                 }
             } else {
@@ -2086,6 +2088,7 @@
     }
 
     private void updateImeInset(WindowInsets windowInsets) {
+        SceneContainerFlag.assertInLegacyMode();
         mImeInset = windowInsets.getInsets(WindowInsets.Type.ime()).bottom;
 
         if (mFooterView != null && mFooterView.getViewState() != null) {
@@ -2112,7 +2115,7 @@
         if (cutout != null) {
             mWaterfallTopInset = cutout.getWaterfallInsets().top;
         }
-        if (!mIsInsetAnimationRunning) {
+        if (!SceneContainerFlag.isEnabled() && !mIsInsetAnimationRunning) {
             // update bottom inset e.g. after rotation
             updateImeInset(insets);
         }
@@ -2513,6 +2516,7 @@
     }
 
     private int getImeInset() {
+        SceneContainerFlag.assertInLegacyMode();
         // The NotificationStackScrollLayout does not extend all the way to the bottom of the
         // display. Therefore, subtract that space from the mImeInset, in order to only include
         // the portion of the bottom inset that actually overlaps the NotificationStackScrollLayout.
@@ -2851,11 +2855,6 @@
         setExpandedHeight(mExpandedHeight);
     }
 
-    public void setMaxTopPadding(int maxTopPadding) {
-        SceneContainerFlag.assertInLegacyMode();
-        mMaxTopPadding = maxTopPadding;
-    }
-
     public int getLayoutMinHeight() {
         SceneContainerFlag.assertInLegacyMode();
         return getLayoutMinHeightInternal();
@@ -3639,7 +3638,11 @@
      * @return Whether a y coordinate is inside the content.
      */
     public boolean isInContentBounds(float y) {
-        return y < getHeight() - getEmptyBottomMarginInternal();
+        if (SceneContainerFlag.isEnabled()) {
+            return y < mAmbientState.getStackCutoff();
+        } else {
+            return y < getHeight() - getEmptyBottomMarginInternal();
+        }
     }
 
     private float getTouchSlop(MotionEvent event) {
@@ -5268,17 +5271,22 @@
         return mQsFullScreen;
     }
 
+    private float getQsExpansionFraction() {
+        SceneContainerFlag.assertInLegacyMode();
+        return mQsExpansionFraction;
+    }
+
     public void setQsExpansionFraction(float qsExpansionFraction) {
         SceneContainerFlag.assertInLegacyMode();
-        boolean footerAffected = mQsExpansionFraction != qsExpansionFraction
-                && (mQsExpansionFraction == 1 || qsExpansionFraction == 1);
+        boolean footerAffected = getQsExpansionFraction() != qsExpansionFraction
+                && (getQsExpansionFraction() == 1 || qsExpansionFraction == 1);
         mQsExpansionFraction = qsExpansionFraction;
         updateUseRoundedRectClipping();
 
         // If notifications are scrolled,
         // clear out scrollY by the time we push notifications offscreen
         if (getOwnScrollY() > 0) {
-            setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, mQsExpansionFraction));
+            setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, getQsExpansionFraction()));
         }
         if (!FooterViewRefactor.isEnabled() && footerAffected) {
             updateFooter();
@@ -5510,9 +5518,7 @@
             println(pw, "alpha", getAlpha());
             println(pw, "suppressChildrenMeasureLayout", mSuppressChildrenMeasureAndLayout);
             println(pw, "scrollY", mAmbientState.getScrollY());
-            println(pw, "maxTopPadding", mMaxTopPadding);
             println(pw, "showShelfOnly", mShouldShowShelfOnly);
-            println(pw, "qsExpandFraction", mQsExpansionFraction);
             println(pw, "isCurrentUserSetup", mIsCurrentUserSetup);
             println(pw, "hideAmount", mAmbientState.getHideAmount());
             println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp());
@@ -5547,6 +5553,8 @@
                 println(pw, "intrinsicContentHeight", getIntrinsicContentHeight());
                 println(pw, "contentHeight", getContentHeight());
                 println(pw, "topPadding", getTopPadding());
+                println(pw, "maxTopPadding", getMaxTopPadding());
+                println(pw, "qsExpandFraction", getQsExpansionFraction());
             }
         });
         pw.println();
@@ -5619,7 +5627,9 @@
                     pw.println("mIsCurrentUserSetup: " + mIsCurrentUserSetup);
                     pw.println("onKeyguard: " + onKeyguard());
                     pw.println("mUpcomingStatusBarState: " + mUpcomingStatusBarState);
-                    pw.println("mQsExpansionFraction: " + mQsExpansionFraction);
+                    if (!SceneContainerFlag.isEnabled()) {
+                        pw.println("QsExpansionFraction: " + getQsExpansionFraction());
+                    }
                     pw.println("mQsFullScreen: " + mQsFullScreen);
                     pw.println(
                             "mScreenOffAnimationController"
@@ -6243,7 +6253,8 @@
         if (SceneContainerFlag.isEnabled()) return;
         // We don't want to clip notifications when QS is expanded, because incoming heads up on
         // the bottom would be clipped otherwise
-        boolean qsAllowsClipping = mQsExpansionFraction < 0.5f || mShouldUseSplitNotificationShade;
+        boolean qsAllowsClipping =
+                getQsExpansionFraction() < 0.5f || mShouldUseSplitNotificationShade;
         boolean clip = mIsExpanded && qsAllowsClipping;
         if (clip != mShouldUseRoundedRectClipping) {
             mShouldUseRoundedRectClipping = clip;
@@ -6953,10 +6964,12 @@
 
     /** Use {@link ScrollViewFields#intrinsicStackHeight}, when SceneContainerFlag is enabled. */
     private int getContentHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mContentHeight;
     }
 
     private void setContentHeight(int contentHeight) {
+        SceneContainerFlag.assertInLegacyMode();
         mContentHeight = contentHeight;
     }
 
@@ -6965,10 +6978,23 @@
      * @return the height of the content ignoring the footer.
      */
     public float getIntrinsicContentHeight() {
+        SceneContainerFlag.assertInLegacyMode();
         return mIntrinsicContentHeight;
     }
 
     private void setIntrinsicContentHeight(float intrinsicContentHeight) {
+        SceneContainerFlag.assertInLegacyMode();
         mIntrinsicContentHeight = intrinsicContentHeight;
     }
+
+    private int getMaxTopPadding() {
+        SceneContainerFlag.assertInLegacyMode();
+        return mMaxTopPadding;
+    }
+
+    /** Not used with SceneContainerFlag, because we rely on the placeholder for placement. */
+    public void setMaxTopPadding(int maxTopPadding) {
+        SceneContainerFlag.assertInLegacyMode();
+        mMaxTopPadding = maxTopPadding;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 96d0d76..827e2bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -228,9 +228,14 @@
                     configurationInteractor.onAnyConfigurationChange,
                 ) { isShadeLayoutWide, shadeMode, _ ->
                     with(context.resources) {
-                        // TODO(b/338033836): Define separate horizontal margins for dual shade.
                         val marginHorizontal =
-                            getDimensionPixelSize(R.dimen.notification_panel_margin_horizontal)
+                            getDimensionPixelSize(
+                                if (shadeMode is Dual) {
+                                    R.dimen.shade_panel_margin_horizontal
+                                } else {
+                                    R.dimen.notification_panel_margin_horizontal
+                                }
+                            )
 
                         val horizontalPosition =
                             when (shadeMode) {
@@ -248,10 +253,7 @@
 
                         ConfigurationBasedDimensions(
                             horizontalPosition = horizontalPosition,
-                            marginStart =
-                                if (horizontalPosition is HorizontalPosition.EdgeToEdge)
-                                    marginHorizontal
-                                else 0,
+                            marginStart = if (shadeMode is Split) 0 else marginHorizontal,
                             marginEnd = marginHorizontal,
                             marginBottom =
                                 getDimensionPixelSize(R.dimen.notification_panel_margin_bottom),
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 99efba4..dceee22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,12 +171,14 @@
 import com.android.systemui.shade.NotificationShadeWindowViewController;
 import com.android.systemui.shade.QuickSettingsController;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
 import com.android.systemui.shade.ShadeSurface;
 import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.AutoHideUiElement;
@@ -366,6 +368,7 @@
 
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
+    private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
     private final AuthRippleController mAuthRippleController;
     @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -671,6 +674,7 @@
             ShadeController shadeController,
             WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
             ViewMediatorCallback viewMediatorCallback,
             InitController initController,
             @Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -778,6 +782,7 @@
         mShadeController = shadeController;
         mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         mKeyguardViewMediatorCallback = viewMediatorCallback;
         mInitController = initController;
         mPluginDependencyProvider = pluginDependencyProvider;
@@ -1527,6 +1532,11 @@
                 // to touch outside the customizer to close it, such as on the status or nav bar.
                 mShadeController.onStatusBarTouch(event);
             }
+            if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+                    && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                mStatusBarLongPressGestureDetector.get().handleTouch(event);
+            }
+
             return getNotificationShadeWindowView().onTouchEvent(event);
         };
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 98869be..4915b84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -45,6 +45,8 @@
 import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.domain.interactor.DozeInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
@@ -85,6 +87,7 @@
     private final BatteryController mBatteryController;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final DozeInteractor mDozeInteractor;
+    private final KeyguardTransitionInteractor mTransitionInteractor;
     private final FoldAodAnimationController mFoldAodAnimationController;
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
     private final UserTracker mUserTracker;
@@ -134,6 +137,7 @@
             StatusBarStateController statusBarStateController,
             UserTracker userTracker,
             DozeInteractor dozeInteractor,
+            KeyguardTransitionInteractor transitionInteractor,
             SecureSettings secureSettings) {
         mResources = resources;
         mAmbientDisplayConfiguration = ambientDisplayConfiguration;
@@ -148,6 +152,7 @@
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mUserTracker = userTracker;
         mDozeInteractor = dozeInteractor;
+        mTransitionInteractor = transitionInteractor;
         mSecureSettings = secureSettings;
 
         keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
@@ -353,6 +358,9 @@
      * delayed for a few seconds. This might be useful to play animations without reducing FPS.
      */
     public boolean shouldDelayDisplayDozeTransition() {
+        if (mTransitionInteractor.getTransitionState().getValue().getTo() == KeyguardState.AOD) {
+            return true;
+        }
         return willAnimateFromLockScreenToAod()
                 || mScreenOffAnimationController.shouldDelayDisplayDozeTransition();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
new file mode 100644
index 0000000..b5b3162
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.statusbar.phone
+
+import android.view.WindowInsetsController
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.CoreStartable
+
+/** Controls how light status bar flag applies to the icons. */
+interface LightBarController : CoreStartable {
+
+    fun stop()
+
+    fun setNavigationBar(navigationBar: LightBarTransitionsController)
+
+    fun setBiometricUnlockController(biometricUnlockController: BiometricUnlockController)
+
+    fun onNavigationBarAppearanceChanged(
+        @WindowInsetsController.Appearance appearance: Int,
+        nbModeChanged: Boolean,
+        navigationBarMode: Int,
+        navbarColorManagedByIme: Boolean,
+    )
+
+    fun onNavigationBarModeChanged(newBarMode: Int)
+
+    fun setQsCustomizing(customizing: Boolean)
+
+    /** Set if Quick Settings is fully expanded, which affects notification scrim visibility. */
+    fun setQsExpanded(expanded: Boolean)
+
+    /** Set if Global Actions dialog is visible, which requires dark mode (light buttons). */
+    fun setGlobalActionsVisible(visible: Boolean)
+
+    /**
+     * Controls the light status bar temporarily for back navigation.
+     *
+     * @param appearance the customized appearance.
+     */
+    fun customizeStatusBarAppearance(appearance: AppearanceRegion)
+
+    /**
+     * Sets whether the direct-reply is in use or not.
+     *
+     * @param directReplying `true` when the direct-reply is in-use.
+     */
+    fun setDirectReplying(directReplying: Boolean)
+
+    fun setScrimState(
+        scrimState: ScrimState,
+        scrimBehindAlpha: Float,
+        scrimInFrontColor: ColorExtractor.GradientColors,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index a33996b..6ff9f4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.systemui.statusbar.phone;
@@ -22,9 +22,9 @@
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
 import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
 
-import android.content.Context;
 import android.graphics.Rect;
 import android.util.Log;
+import android.view.Display;
 import android.view.InsetsFlags;
 import android.view.ViewDebug;
 import android.view.WindowInsetsController.Appearance;
@@ -34,30 +34,32 @@
 
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.statusbar.data.model.StatusBarAppearance;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.Compile;
-import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.kotlin.JavaAdapterKt;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import kotlin.coroutines.CoroutineContext;
+
+import kotlinx.coroutines.CoroutineScope;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
 
-import javax.inject.Inject;
-
 /**
  * Controls how light status bar flag applies to the icons.
  */
-@SysUISingleton
-public class LightBarController implements
-        BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable {
+public class LightBarControllerImpl implements
+        BatteryController.BatteryStateChangeCallback, LightBarController {
 
     private static final String TAG = "LightBarController";
     private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
@@ -65,10 +67,13 @@
 
     private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
 
-    private final JavaAdapter mJavaAdapter;
+    private final CoroutineScope mCoroutineScope;
     private final SysuiDarkIconDispatcher mStatusBarIconController;
     private final BatteryController mBatteryController;
-    private final StatusBarModeRepositoryStore mStatusBarModeRepository;
+    private final NavigationModeController mNavModeController;
+    private final DumpManager mDumpManager;
+    private final StatusBarModePerDisplayRepository mStatusBarModeRepository;
+    private final CoroutineContext mMainContext;
     private BiometricUnlockController mBiometricUnlockController;
 
     private LightBarTransitionsController mNavigationBarController;
@@ -119,42 +124,59 @@
     private String mLastNavigationBarAppearanceChangedLog;
     private StringBuilder mLogStringBuilder = null;
 
-    @Inject
-    public LightBarController(
-            Context ctx,
-            JavaAdapter javaAdapter,
+    private final String mDumpableName;
+
+    private final NavigationModeController.ModeChangedListener mNavigationModeListener =
+            (mode) -> mNavigationMode = mode;
+
+    @AssistedInject
+    public LightBarControllerImpl(
+            @Assisted int displayId,
+            @Assisted CoroutineScope coroutineScope,
             DarkIconDispatcher darkIconDispatcher,
             BatteryController batteryController,
             NavigationModeController navModeController,
-            StatusBarModeRepositoryStore statusBarModeRepository,
+            @Assisted StatusBarModePerDisplayRepository statusBarModeRepository,
             DumpManager dumpManager,
-            DisplayTracker displayTracker) {
-        mJavaAdapter = javaAdapter;
+            @Main CoroutineContext mainContext) {
+        mCoroutineScope = coroutineScope;
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
         mBatteryController = batteryController;
-        mBatteryController.addCallback(this);
+        mNavModeController = navModeController;
+        mDumpManager = dumpManager;
         mStatusBarModeRepository = statusBarModeRepository;
-        mNavigationMode = navModeController.addListener((mode) -> {
-            mNavigationMode = mode;
-        });
-
-        if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) {
-            dumpManager.registerDumpable(getClass().getSimpleName(), this);
-        }
+        mMainContext = mainContext;
+        String dumpableNameSuffix =
+                displayId == Display.DEFAULT_DISPLAY ? "" : String.valueOf(displayId);
+        mDumpableName = getClass().getSimpleName() + dumpableNameSuffix;
     }
 
     @Override
     public void start() {
-        mJavaAdapter.alwaysCollectFlow(
-                mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(),
+        mDumpManager.registerCriticalDumpable(mDumpableName, this);
+        mBatteryController.addCallback(this);
+        mNavigationMode = mNavModeController.addListener(mNavigationModeListener);
+        JavaAdapterKt.collectFlow(
+                mCoroutineScope,
+                mMainContext,
+                mStatusBarModeRepository.getStatusBarAppearance(),
                 this::onStatusBarAppearanceChanged);
     }
 
+    @Override
+    public void stop() {
+        mDumpManager.unregisterDumpable(mDumpableName);
+        mBatteryController.removeCallback(this);
+        mNavModeController.removeListener(mNavigationModeListener);
+    }
+
+    @Override
     public void setNavigationBar(LightBarTransitionsController navigationBar) {
         mNavigationBarController = navigationBar;
         updateNavigation();
     }
 
+    @Override
     public void setBiometricUnlockController(
             BiometricUnlockController biometricUnlockController) {
         mBiometricUnlockController = biometricUnlockController;
@@ -202,6 +224,7 @@
         mNavbarColorManagedByIme = navbarColorManagedByIme;
     }
 
+    @Override
     public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
             int navigationBarMode, boolean navbarColorManagedByIme) {
         int diff = appearance ^ mAppearance;
@@ -244,6 +267,7 @@
         mNavbarColorManagedByIme = navbarColorManagedByIme;
     }
 
+    @Override
     public void onNavigationBarModeChanged(int newBarMode) {
         mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
     }
@@ -258,30 +282,28 @@
                 mNavigationBarMode, mNavbarColorManagedByIme);
     }
 
+    @Override
     public void setQsCustomizing(boolean customizing) {
         if (mQsCustomizing == customizing) return;
         mQsCustomizing = customizing;
         reevaluate();
     }
 
-    /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */
+    @Override
     public void setQsExpanded(boolean expanded) {
         if (mQsExpanded == expanded) return;
         mQsExpanded = expanded;
         reevaluate();
     }
 
-    /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */
+    @Override
     public void setGlobalActionsVisible(boolean visible) {
         if (mGlobalActionsVisible == visible) return;
         mGlobalActionsVisible = visible;
         reevaluate();
     }
 
-    /**
-     * Controls the light status bar temporarily for back navigation.
-     * @param appearance the custmoized appearance.
-     */
+    @Override
     public void customizeStatusBarAppearance(AppearanceRegion appearance) {
         if (appearance != null) {
             final ArrayList<AppearanceRegion> appearancesList = new ArrayList<>();
@@ -303,16 +325,14 @@
         }
     }
 
-    /**
-     * Sets whether the direct-reply is in use or not.
-     * @param directReplying {@code true} when the direct-reply is in-use.
-     */
+    @Override
     public void setDirectReplying(boolean directReplying) {
         if (mDirectReplying == directReplying) return;
         mDirectReplying = directReplying;
         reevaluate();
     }
 
+    @Override
     public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
             GradientColors scrimInFrontColor) {
         boolean bouncerVisibleLast = mBouncerVisible;
@@ -387,20 +407,17 @@
             }
         }
 
-        // If no one is light, all icons become white.
         if (lightBarBounds.isEmpty()) {
-            mStatusBarIconController.getTransitionsController().setIconsDark(
-                    false, animateChange());
-        }
-
-        // If all stacks are light, all icons get dark.
-        else if (lightBarBounds.size() == numStacks) {
+            // If no one is light, all icons become white.
+            mStatusBarIconController
+                    .getTransitionsController()
+                    .setIconsDark(false, animateChange());
+        } else if (lightBarBounds.size() == numStacks) {
+            // If all stacks are light, all icons get dark.
             mStatusBarIconController.setIconsDarkArea(null);
             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
-        }
-
-        // Not the same for every stack, magic!
-        else {
+        } else {
+            // Not the same for every stack, magic!
             mStatusBarIconController.setIconsDarkArea(lightBarBounds);
             mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
         }
@@ -468,47 +485,15 @@
         }
     }
 
-    /**
-     * Injectable factory for creating a {@link LightBarController}.
-     */
-    public static class Factory {
-        private final JavaAdapter mJavaAdapter;
-        private final DarkIconDispatcher mDarkIconDispatcher;
-        private final BatteryController mBatteryController;
-        private final NavigationModeController mNavModeController;
-        private final StatusBarModeRepositoryStore mStatusBarModeRepository;
-        private final DumpManager mDumpManager;
-        private final DisplayTracker mDisplayTracker;
+    /** Injectable factory for creating a {@link LightBarControllerImpl}. */
+    @AssistedFactory
+    @FunctionalInterface
+    public interface Factory {
 
-        @Inject
-        public Factory(
-                JavaAdapter javaAdapter,
-                DarkIconDispatcher darkIconDispatcher,
-                BatteryController batteryController,
-                NavigationModeController navModeController,
-                StatusBarModeRepositoryStore statusBarModeRepository,
-                DumpManager dumpManager,
-                DisplayTracker displayTracker) {
-            mJavaAdapter = javaAdapter;
-            mDarkIconDispatcher = darkIconDispatcher;
-            mBatteryController = batteryController;
-            mNavModeController = navModeController;
-            mStatusBarModeRepository = statusBarModeRepository;
-            mDumpManager = dumpManager;
-            mDisplayTracker = displayTracker;
-        }
-
-        /** Create an {@link LightBarController} */
-        public LightBarController create(Context context) {
-            return new LightBarController(
-                    context,
-                    mJavaAdapter,
-                    mDarkIconDispatcher,
-                    mBatteryController,
-                    mNavModeController,
-                    mStatusBarModeRepository,
-                    mDumpManager,
-                    mDisplayTracker);
-        }
+        /** Creates a {@link LightBarControllerImpl}. */
+        LightBarControllerImpl create(
+                int displayId,
+                CoroutineScope coroutineScope,
+                StatusBarModePerDisplayRepository statusBarModePerDisplayRepository);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 91c43dd..176dd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@
 import com.android.systemui.Flags;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.res.R;
-import com.android.systemui.shade.LongPressGestureDetector;
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
 import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@
     private InsetsFetcher mInsetsFetcher;
     private int mDensity;
     private float mFontScale;
-    private LongPressGestureDetector mLongPressGestureDetector;
+    private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
 
     /**
      * Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,9 +81,10 @@
         mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
     }
 
-    void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+    void setLongPressGestureDetector(
+            StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
         if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
-            mLongPressGestureDetector = longPressGestureDetector;
+            mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
         }
     }
 
@@ -207,8 +208,9 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
-            mLongPressGestureDetector.handleTouch(event);
+        if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+                && mStatusBarLongPressGestureDetector != null) {
+            mStatusBarLongPressGestureDetector.handleTouch(event);
         }
         if (mTouchEventHandler == null) {
             Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index c24f432..4245494 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -33,11 +33,11 @@
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeController
 import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
 import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -68,7 +68,7 @@
     private val shadeController: ShadeController,
     private val shadeViewController: ShadeViewController,
     private val panelExpansionInteractor: PanelExpansionInteractor,
-    private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+    private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
     private val windowRootView: Provider<WindowRootView>,
     private val shadeLogger: ShadeLogger,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -118,7 +118,7 @@
         addCursorSupportToIconContainers()
 
         if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
-            mView.setLongPressGestureDetector(longPressGestureDetector.get())
+            mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
         }
 
         progressProvider?.setReadyToHandleTransition(true)
@@ -335,7 +335,7 @@
         private val shadeController: ShadeController,
         private val shadeViewController: ShadeViewController,
         private val panelExpansionInteractor: PanelExpansionInteractor,
-        private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+        private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
         private val windowRootView: Provider<WindowRootView>,
         private val shadeLogger: ShadeLogger,
         private val viewUtil: ViewUtil,
@@ -360,7 +360,7 @@
                 shadeController,
                 shadeViewController,
                 panelExpansionInteractor,
-                longPressGestureDetector,
+                statusBarLongPressGestureDetector,
                 windowRootView,
                 shadeLogger,
                 statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 9cda199..0c511aea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1348,6 +1348,7 @@
         if (!canHandleBackPressed()) {
             return;
         }
+        mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
 
         boolean hideBouncerOverDream = isBouncerShowing()
                 && mDreamOverlayStateController.isOverlayActive();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index a658115..d2c2003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -37,6 +37,7 @@
 import com.android.systemui.ScreenDecorations;
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
@@ -78,6 +79,7 @@
     private View mNotificationShadeWindowView;
     private View mNotificationPanelView;
     private boolean mForceCollapsedUntilLayout = false;
+    private Boolean mCommunalVisible = false;
 
     private Region mTouchableRegion = new Region();
     private int mDisplayCutoutTouchableRegionSize;
@@ -98,7 +100,8 @@
             JavaAdapter javaAdapter,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             PrimaryBouncerInteractor primaryBouncerInteractor,
-            AlternateBouncerInteractor alternateBouncerInteractor
+            AlternateBouncerInteractor alternateBouncerInteractor,
+            CommunalSceneInteractor communalSceneInteractor
     ) {
         mContext = context;
         initResources();
@@ -145,6 +148,9 @@
             javaAdapter.alwaysCollectFlow(
                     shadeInteractor.isAnyExpanded(),
                     this::onShadeOrQsExpanded);
+            javaAdapter.alwaysCollectFlow(
+                    communalSceneInteractor.isCommunalVisible(),
+                    this::onCommunalVisible);
         }
 
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
@@ -196,6 +202,10 @@
         }
     }
 
+    private void onCommunalVisible(Boolean visible) {
+        mCommunalVisible = visible;
+    }
+
     /**
      * Calculates the touch region needed for heads up notifications, taking into consideration
      * any existing display cutouts (notch)
@@ -304,6 +314,9 @@
                 && (!mIsSceneContainerUiEmpty || mIsRemoteUserInteractionOngoing))
                 || mPrimaryBouncerInteractor.isShowing().getValue()
                 || mAlternateBouncerInteractor.isVisibleState()
+                // The glanceable hub is a full-screen UI within the notification shade window. When
+                // it's visible, the touchable region should be the full screen.
+                || mCommunalVisible
                 || mUnlockedScreenOffAnimationController.isAnimationPlaying();
     }
 
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 e4a75be..94de3510 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
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.core.StatusBarOrchestrator
 import com.android.systemui.statusbar.core.StatusBarSimpleFragment
 import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule
+import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule
 import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
 import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
 import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
@@ -48,7 +49,12 @@
 
 /** Similar in purpose to [StatusBarModule], but scoped only to phones */
 @Module(
-    includes = [PrivacyDotViewControllerModule::class, PrivacyDotViewControllerStoreModule::class]
+    includes =
+        [
+            PrivacyDotViewControllerModule::class,
+            PrivacyDotWindowControllerStoreModule::class,
+            PrivacyDotViewControllerStoreModule::class,
+        ]
 )
 interface StatusBarPhoneModule {
 
@@ -143,7 +149,7 @@
         fun commandQueueInitializerCoreStartable(
             initializerLazy: Lazy<CommandQueueInitializer>
         ): CoreStartable {
-            return if (StatusBarSimpleFragment.isEnabled) {
+            return if (StatusBarConnectedDisplays.isEnabled) {
                 initializerLazy.get()
             } else {
                 CoreStartable.NOP
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
index 1d08f2b..98eed84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
@@ -37,6 +37,9 @@
 
     /** Clients must observe this property, as device-based satellite is location-dependent */
     val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean>
+
+    /** When enabled, a satellite icon will display when all other connections are OOS */
+    val isOpportunisticSatelliteIconEnabled: Boolean
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
index 58c30e0..de42b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
@@ -97,6 +97,9 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
 
+    override val isOpportunisticSatelliteIconEnabled: Boolean
+        get() = activeRepo.value.isOpportunisticSatelliteIconEnabled
+
     override val isSatelliteProvisioned: StateFlow<Boolean> =
         activeRepo
             .flatMapLatest { it.isSatelliteProvisioned }
@@ -118,6 +121,6 @@
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
-                realImpl.isSatelliteAllowedForCurrentLocation.value
+                realImpl.isSatelliteAllowedForCurrentLocation.value,
             )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
index d557bbf..755899f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
@@ -16,15 +16,18 @@
 
 package com.android.systemui.statusbar.pipeline.satellite.data.demo
 
+import android.content.res.Resources
+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.dagger.qualifiers.Main
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
 import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** A satellite repository that represents the latest satellite values sent via demo mode. */
 @SysUISingleton
@@ -33,9 +36,13 @@
 constructor(
     private val dataSource: DemoDeviceBasedSatelliteDataSource,
     @Application private val scope: CoroutineScope,
+    @Main resources: Resources,
 ) : DeviceBasedSatelliteRepository {
     private var demoCommandJob: Job? = null
 
+    override val isOpportunisticSatelliteIconEnabled =
+        resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
     override val isSatelliteProvisioned = MutableStateFlow(true)
     override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown)
     override val signalStrength = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 7686338..a36ef56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.satellite.data.prod
 
+import android.content.res.Resources
 import android.os.OutcomeReceiver
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
@@ -27,14 +28,17 @@
 import android.telephony.satellite.SatelliteProvisionStateCallback
 import android.telephony.satellite.SatelliteSupportedStateCallback
 import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.MessageInitializer
 import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog
 import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
@@ -66,7 +70,6 @@
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
 
@@ -146,10 +149,14 @@
     @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
     @VerboseDeviceBasedSatelliteInputLog private val verboseLogBuffer: LogBuffer,
     private val systemClock: SystemClock,
+    @Main resources: Resources,
 ) : RealDeviceBasedSatelliteRepository {
 
     private val satelliteManager: SatelliteManager?
 
+    override val isOpportunisticSatelliteIconEnabled: Boolean =
+        resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
     // Some calls into satellite manager will throw exceptions if it is not supported.
     // This is never expected to change after boot, but may need to be retried in some cases
     @get:VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index f1a444f..08a98c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -53,6 +53,9 @@
     @DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
     @DeviceBasedSatelliteTableLog private val tableLog: TableLogBuffer,
 ) {
+    /** Whether or not we should show the satellite icon when all connections are OOS */
+    val isOpportunisticSatelliteIconEnabled = repo.isOpportunisticSatelliteIconEnabled
+
     /** Must be observed by any UI showing Satellite iconography */
     val isSatelliteAllowed =
         if (Flags.oemEnabledSatelliteFlag()) {
@@ -93,12 +96,7 @@
                 flowOf(0)
             }
             .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLog,
-                columnPrefix = "",
-                columnName = COL_LEVEL,
-                initialValue = 0,
-            )
+            .logDiffsForTable(tableLog, columnPrefix = "", columnName = COL_LEVEL, initialValue = 0)
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
     val isSatelliteProvisioned = repo.isSatelliteProvisioned
@@ -132,10 +130,9 @@
     /** When all connections are considered OOS, satellite connectivity is potentially valid */
     val areAllConnectionsOutOfService =
         if (Flags.oemEnabledSatelliteFlag()) {
-                combine(
-                    allConnectionsOos,
-                    iconsInteractor.isDeviceInEmergencyCallsOnlyMode,
-                ) { connectionsOos, deviceEmergencyOnly ->
+                combine(allConnectionsOos, iconsInteractor.isDeviceInEmergencyCallsOnlyMode) {
+                    connectionsOos,
+                    deviceEmergencyOnly ->
                     logBuffer.log(
                         TAG,
                         LogLevel.INFO,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 37f2f19..13ac321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -77,35 +77,38 @@
 
     // This adds a 10 seconds delay before showing the icon
     private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
-        interactor.areAllConnectionsOutOfService
-            .flatMapLatest { shouldShow ->
-                if (shouldShow) {
-                    logBuffer.log(
-                        TAG,
-                        LogLevel.INFO,
-                        { long1 = DELAY_DURATION.inWholeSeconds },
-                        { "Waiting $long1 seconds before showing the satellite icon" }
+        if (interactor.isOpportunisticSatelliteIconEnabled) {
+                interactor.areAllConnectionsOutOfService
+                    .flatMapLatest { shouldShow ->
+                        if (shouldShow) {
+                            logBuffer.log(
+                                TAG,
+                                LogLevel.INFO,
+                                { long1 = DELAY_DURATION.inWholeSeconds },
+                                { "Waiting $long1 seconds before showing the satellite icon" },
+                            )
+                            delay(DELAY_DURATION)
+                            flowOf(true)
+                        } else {
+                            flowOf(false)
+                        }
+                    }
+                    .distinctUntilChanged()
+                    .logDiffsForTable(
+                        tableLog,
+                        columnPrefix = "vm",
+                        columnName = COL_VISIBLE_FOR_OOS,
+                        initialValue = false,
                     )
-                    delay(DELAY_DURATION)
-                    flowOf(true)
-                } else {
-                    flowOf(false)
-                }
+            } else {
+                flowOf(false)
             }
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                tableLog,
-                columnPrefix = "vm",
-                columnName = COL_VISIBLE_FOR_OOS,
-                initialValue = false,
-            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     private val canShowIcon =
-        combine(
-            interactor.isSatelliteAllowed,
-            interactor.isSatelliteProvisioned,
-        ) { allowed, provisioned ->
+        combine(interactor.isSatelliteAllowed, interactor.isSatelliteProvisioned) {
+            allowed,
+            provisioned ->
             allowed && provisioned
         }
 
@@ -141,11 +144,10 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val icon: StateFlow<Icon?> =
-        combine(
-                showIcon,
-                interactor.connectionState,
-                interactor.signalStrength,
-            ) { shouldShow, state, signalStrength ->
+        combine(showIcon, interactor.connectionState, interactor.signalStrength) {
+                shouldShow,
+                state,
+                signalStrength ->
                 if (shouldShow) {
                     SatelliteIconModel.fromConnectionState(state, signalStrength)
                 } else {
@@ -155,10 +157,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), null)
 
     override val carrierText: StateFlow<String?> =
-        combine(
-                showIcon,
-                interactor.connectionState,
-            ) { shouldShow, connectionState ->
+        combine(showIcon, interactor.connectionState) { shouldShow, connectionState ->
                 logBuffer.log(
                     TAG,
                     LogLevel.INFO,
@@ -166,7 +165,7 @@
                         bool1 = shouldShow
                         str1 = connectionState.name
                     },
-                    { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
+                    { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" },
                 )
                 if (shouldShow) {
                     when (connectionState) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 76389f3..b033b36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -46,7 +46,7 @@
 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModeTileViewModel
 
 @Composable
-fun ModeTile(viewModel: ModeTileViewModel) {
+fun ModeTile(viewModel: ModeTileViewModel, modifier: Modifier = Modifier) {
     val tileColor: Color by
         animateColorAsState(
             if (viewModel.enabled) MaterialTheme.colorScheme.primary
@@ -59,7 +59,7 @@
         )
 
     CompositionLocalProvider(LocalContentColor provides contentColor) {
-        Surface(color = tileColor, shape = RoundedCornerShape(16.dp)) {
+        Surface(color = tileColor, shape = RoundedCornerShape(16.dp), modifier = modifier) {
             Row(
                 modifier =
                     Modifier.combinedClickable(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
index 903c7e1..16f24f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -17,29 +17,74 @@
 package com.android.systemui.statusbar.policy.ui.dialog.composable
 
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.Flags
+import com.android.systemui.qs.panels.ui.compose.PagerDots
 import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
 
 @Composable
 fun ModeTileGrid(viewModel: ModesDialogViewModel) {
     val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList())
 
-    LazyVerticalGrid(
-        columns = GridCells.Fixed(1),
-        modifier = Modifier.fillMaxWidth().heightIn(max = 280.dp),
-        verticalArrangement = Arrangement.spacedBy(8.dp),
-        horizontalArrangement = Arrangement.spacedBy(8.dp),
-    ) {
-        items(tiles.size, key = { index -> tiles[index].id }) { index ->
-            ModeTile(viewModel = tiles[index])
+    if (Flags.modesUiDialogPaging()) {
+        val tilesPerPage = 3
+        val totalPages = { (tiles.size + tilesPerPage - 1) / tilesPerPage }
+        val pagerState = rememberPagerState(initialPage = 0, pageCount = totalPages)
+
+        Column {
+            HorizontalPager(
+                state = pagerState,
+                modifier = Modifier.fillMaxWidth(),
+                pageSpacing = 16.dp,
+                verticalAlignment = Alignment.Top,
+                // Pre-emptively layout and compose the next page, to make sure the height stays
+                // the same even if we have fewer than [tilesPerPage] tiles on the last page.
+                beyondViewportPageCount = 1,
+            ) { page ->
+                Column(
+                    modifier = Modifier.fillMaxWidth().fillMaxHeight(),
+                    verticalArrangement = Arrangement.spacedBy(8.dp, alignment = Alignment.Top),
+                ) {
+                    val startIndex = page * tilesPerPage
+                    val endIndex = minOf((page + 1) * tilesPerPage, tiles.size)
+                    for (index in startIndex until endIndex) {
+                        ModeTile(viewModel = tiles[index], modifier = Modifier.fillMaxWidth())
+                    }
+                }
+            }
+
+            PagerDots(
+                pagerState = pagerState,
+                activeColor = MaterialTheme.colorScheme.primary,
+                nonActiveColor = MaterialTheme.colorScheme.onSurfaceVariant,
+                modifier = Modifier.align(Alignment.CenterHorizontally).padding(top = 8.dp),
+            )
+        }
+    } else {
+        LazyVerticalGrid(
+            columns = GridCells.Fixed(1),
+            modifier = Modifier.fillMaxWidth().heightIn(max = 280.dp),
+            verticalArrangement = Arrangement.spacedBy(8.dp),
+            horizontalArrangement = Arrangement.spacedBy(8.dp),
+        ) {
+            items(tiles.size, key = { index -> tiles[index].id }) { index ->
+                ModeTile(viewModel = tiles[index])
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9c8ef04..1c3fece 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -242,10 +242,16 @@
         }
     };
 
-    private int getLatestWallpaperType(int userId) {
-        return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
-                > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
-                ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+    private int getDefaultWallpaperColorsSource(int userId) {
+        if (com.android.systemui.shared.Flags.newCustomizationPickerUi()) {
+            // The wallpaper colors source is always the home wallpaper.
+            return WallpaperManager.FLAG_SYSTEM;
+        } else {
+            // The wallpaper colors source is based on the last set wallpaper.
+            return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
+                    > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
+                    ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+        }
     }
 
     private boolean isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors) {
@@ -279,9 +285,9 @@
     private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId) {
         final int currentUser = mUserTracker.getUserId();
         final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
-        int latestWallpaperType = getLatestWallpaperType(userId);
-        boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0;
-        if (eventForLatestWallpaper) {
+        int wallpaperColorsSource = getDefaultWallpaperColorsSource(userId);
+        boolean wallpaperColorsNeedUpdate = (flags & wallpaperColorsSource) != 0;
+        if (wallpaperColorsNeedUpdate) {
             mCurrentColors.put(userId, wallpaperColors);
             if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
         }
@@ -328,7 +334,7 @@
             boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource);
             boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor;
 
-            if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper
+            if (!userChosePresetColor && !preserveLockScreenColor && wallpaperColorsNeedUpdate
                     && !isSeedColorSet(jsonObject, wallpaperColors)) {
                 mSkipSettingChange = true;
                 if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
@@ -494,7 +500,7 @@
         // Upon boot, make sure we have the most up to date colors
         Runnable updateColors = () -> {
             WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
-                    getLatestWallpaperType(mUserTracker.getUserId()));
+                    getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
             Runnable applyColors = () -> {
                 if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
                 mCurrentColors.put(mUserTracker.getUserId(), systemColor);
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 3159124..63a5b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -20,6 +20,7 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.coroutineScope
 import androidx.lifecycle.repeatOnLifecycle
+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.lifecycle.repeatWhenAttached
@@ -35,7 +36,7 @@
 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.plus
 
 /** A class allowing Java classes to collect on Kotlin flows. */
 @SysUISingleton
@@ -102,6 +103,22 @@
     }
 }
 
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event on the
+ * specified [collectContext].
+ *
+ * Collection will continue until the given [scope] is cancelled.
+ */
+@JvmOverloads
+fun <T> collectFlow(
+    scope: CoroutineScope,
+    collectContext: CoroutineContext = scope.coroutineContext,
+    flow: Flow<T>,
+    consumer: Consumer<T>,
+): Job {
+    return scope.plus(collectContext).launch { flow.collect { consumer.accept(it) } }
+}
+
 fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
     return combine(flow1, flow2, bifunction)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
index 097a60f..4fc9a7c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -19,10 +19,8 @@
 import android.app.Dialog
 import android.content.Context
 import android.os.Bundle
-import android.view.ContextThemeWrapper
 import android.view.MotionEvent
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.res.R
 import com.android.systemui.volume.Events
 import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.ui.binder.VolumeDialogViewBinder
@@ -34,7 +32,7 @@
     @Application context: Context,
     private val viewBinder: VolumeDialogViewBinder,
     private val visibilityInteractor: VolumeDialogVisibilityInteractor,
-) : Dialog(ContextThemeWrapper(context, R.style.volume_dialog_theme)) {
+) : Dialog(context) {
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
index c05a0b2..6816d35 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/binder/VolumeDialogRingerViewBinder.kt
@@ -16,15 +16,26 @@
 
 package com.android.systemui.volume.dialog.ringer.ui.binder
 
+import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageButton
+import androidx.annotation.LayoutRes
+import androidx.compose.ui.util.fastForEachIndexed
 import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.lifecycle.setSnapshotBinding
 import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.res.R
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerButtonViewModel
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerDrawerState
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModel
+import com.android.systemui.volume.dialog.ringer.ui.viewmodel.RingerViewModelState
 import com.android.systemui.volume.dialog.ringer.ui.viewmodel.VolumeDialogRingerDrawerViewModel
 import javax.inject.Inject
-import kotlinx.coroutines.awaitCancellation
+import kotlin.math.abs
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 
 @VolumeDialogScope
 class VolumeDialogRingerViewBinder
@@ -33,16 +44,124 @@
 
     fun bind(view: View) {
         with(view) {
+            val drawerAndRingerContainer =
+                requireViewById<View>(R.id.volume_ringer_and_drawer_container)
+            val drawerContainer = requireViewById<View>(R.id.volume_drawer_container)
+            val selectedButtonView =
+                requireViewById<ImageButton>(R.id.volume_new_ringer_active_button)
+            val volumeDialogView = requireViewById<ViewGroup>(R.id.volume_dialog)
             repeatWhenAttached {
                 viewModel(
                     traceName = "VolumeDialogRingerViewBinder",
                     minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                     factory = { viewModelFactory.create() },
                 ) { viewModel ->
-                    setSnapshotBinding {}
-                    awaitCancellation()
+                    viewModel.ringerViewModel
+                        .onEach { ringerState ->
+                            when (ringerState) {
+                                is RingerViewModelState.Available -> {
+                                    val uiModel = ringerState.uiModel
+
+                                    bindSelectedButton(viewModel, uiModel, selectedButtonView)
+                                    bindDrawerButtons(viewModel, uiModel.availableButtons)
+
+                                    // Set up views background and visibility
+                                    drawerAndRingerContainer.visibility = View.VISIBLE
+                                    when (uiModel.drawerState) {
+                                        is RingerDrawerState.Initial -> {
+                                            drawerContainer.visibility = View.GONE
+                                            selectedButtonView.visibility = View.VISIBLE
+                                            volumeDialogView.setBackgroundResource(
+                                                R.drawable.volume_dialog_background
+                                            )
+                                        }
+
+                                        is RingerDrawerState.Closed -> {
+                                            drawerContainer.visibility = View.GONE
+                                            selectedButtonView.visibility = View.VISIBLE
+                                            volumeDialogView.setBackgroundResource(
+                                                R.drawable.volume_dialog_background
+                                            )
+                                        }
+
+                                        is RingerDrawerState.Open -> {
+                                            drawerContainer.visibility = View.VISIBLE
+                                            selectedButtonView.visibility = View.GONE
+                                            if (
+                                                uiModel.currentButtonIndex !=
+                                                    uiModel.availableButtons.size - 1
+                                            ) {
+                                                volumeDialogView.setBackgroundResource(
+                                                    R.drawable.volume_dialog_background_small_radius
+                                                )
+                                            }
+                                        }
+                                    }
+                                }
+
+                                is RingerViewModelState.Unavailable -> {
+                                    drawerAndRingerContainer.visibility = View.GONE
+                                    volumeDialogView.setBackgroundResource(
+                                        R.drawable.volume_dialog_background
+                                    )
+                                }
+                            }
+                        }
+                        .launchIn(this)
                 }
             }
         }
     }
+
+    private fun View.bindDrawerButtons(
+        viewModel: VolumeDialogRingerDrawerViewModel,
+        availableButtons: List<RingerButtonViewModel?>,
+    ) {
+        val drawerOptions = requireViewById<ViewGroup>(R.id.volume_drawer_options)
+        val count = availableButtons.size
+        drawerOptions.ensureChildCount(R.layout.volume_ringer_button, count)
+
+        availableButtons.fastForEachIndexed { index, ringerButton ->
+            ringerButton?.let {
+                drawerOptions.getChildAt(count - index - 1).bindDrawerButton(it, viewModel)
+            }
+        }
+    }
+
+    private fun View.bindDrawerButton(
+        buttonViewModel: RingerButtonViewModel,
+        viewModel: VolumeDialogRingerDrawerViewModel,
+    ) {
+        with(requireViewById<ImageButton>(R.id.volume_drawer_button)) {
+            setImageResource(buttonViewModel.imageResId)
+            contentDescription = context.getString(buttonViewModel.contentDescriptionResId)
+            setOnClickListener { viewModel.onRingerButtonClicked(buttonViewModel.ringerMode) }
+        }
+    }
+
+    private fun ViewGroup.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int) {
+        val childCountDelta = childCount - count
+        when {
+            childCountDelta > 0 -> {
+                removeViews(0, childCountDelta)
+            }
+            childCountDelta < 0 -> {
+                val inflater = LayoutInflater.from(context)
+                repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) }
+            }
+        }
+    }
+
+    private fun bindSelectedButton(
+        viewModel: VolumeDialogRingerDrawerViewModel,
+        uiModel: RingerViewModel,
+        selectedButtonView: ImageButton,
+    ) {
+        with(uiModel) {
+            selectedButtonView.setImageResource(selectedButton.imageResId)
+            selectedButtonView.setOnClickListener {
+                viewModel.onRingerButtonClicked(selectedButton.ringerMode)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
index a09bfeb..96d4f62 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
@@ -22,6 +22,8 @@
     val availableButtons: List<RingerButtonViewModel?>,
     /** The index of the currently selected button */
     val currentButtonIndex: Int,
+    /** Currently selected button. */
+    val selectedButton: RingerButtonViewModel,
     /** For open and close animations */
     val drawerState: RingerDrawerState,
 )
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 5b73107..d4da226 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
@@ -41,8 +41,6 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.stateIn
 
-private const val TAG = "VolumeDialogRingerDrawerViewModel"
-
 class VolumeDialogRingerDrawerViewModel
 @AssistedInject
 constructor(
@@ -111,31 +109,45 @@
         return if (currentIndex == -1 || isSingleVolume) {
             RingerViewModelState.Unavailable
         } else {
-            RingerViewModelState.Available(
-                RingerViewModel(
-                    availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
-                    currentButtonIndex = currentIndex,
-                    drawerState = drawerState,
+            toButtonViewModel(currentRingerMode, isSelectedButton = true)?.let {
+                RingerViewModelState.Available(
+                    RingerViewModel(
+                        availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
+                        currentButtonIndex = currentIndex,
+                        selectedButton = it,
+                        drawerState = drawerState,
+                    )
                 )
-            )
+            } ?: RingerViewModelState.Unavailable
         }
     }
 
     private fun VolumeDialogRingerModel.toButtonViewModel(
-        ringerMode: RingerMode
+        ringerMode: RingerMode,
+        isSelectedButton: Boolean = false,
     ): RingerButtonViewModel? {
         return when (ringerMode.value) {
             RINGER_MODE_SILENT ->
                 RingerButtonViewModel(
                     imageResId = R.drawable.ic_speaker_mute,
-                    contentDescriptionResId = R.string.volume_ringer_status_silent,
+                    contentDescriptionResId =
+                        if (isSelectedButton) {
+                            R.string.volume_ringer_status_silent
+                        } else {
+                            R.string.volume_ringer_hint_mute
+                        },
                     hintLabelResId = R.string.volume_ringer_hint_unmute,
                     ringerMode = ringerMode,
                 )
             RINGER_MODE_VIBRATE ->
                 RingerButtonViewModel(
                     imageResId = R.drawable.ic_volume_ringer_vibrate,
-                    contentDescriptionResId = R.string.volume_ringer_status_vibrate,
+                    contentDescriptionResId =
+                        if (isSelectedButton) {
+                            R.string.volume_ringer_status_vibrate
+                        } else {
+                            R.string.volume_ringer_hint_vibrate
+                        },
                     hintLabelResId = R.string.volume_ringer_hint_vibrate,
                     ringerMode = ringerMode,
                 )
@@ -143,8 +155,18 @@
                 when {
                     isMuted && isEnabled ->
                         RingerButtonViewModel(
-                            imageResId = R.drawable.ic_speaker_mute,
-                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            imageResId =
+                                if (isSelectedButton) {
+                                    R.drawable.ic_speaker_mute
+                                } else {
+                                    R.drawable.ic_speaker_on
+                                },
+                            contentDescriptionResId =
+                                if (isSelectedButton) {
+                                    R.string.volume_ringer_status_normal
+                                } else {
+                                    R.string.volume_ringer_hint_unmute
+                                },
                             hintLabelResId = R.string.volume_ringer_hint_unmute,
                             ringerMode = ringerMode,
                         )
@@ -152,7 +174,12 @@
                     availableModes.contains(RingerMode(RINGER_MODE_VIBRATE)) ->
                         RingerButtonViewModel(
                             imageResId = R.drawable.ic_speaker_on,
-                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            contentDescriptionResId =
+                                if (isSelectedButton) {
+                                    R.string.volume_ringer_status_normal
+                                } else {
+                                    R.string.volume_ringer_hint_unmute
+                                },
                             hintLabelResId = R.string.volume_ringer_hint_vibrate,
                             ringerMode = ringerMode,
                         )
@@ -160,7 +187,12 @@
                     else ->
                         RingerButtonViewModel(
                             imageResId = R.drawable.ic_speaker_on,
-                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            contentDescriptionResId =
+                                if (isSelectedButton) {
+                                    R.string.volume_ringer_status_normal
+                                } else {
+                                    R.string.volume_ringer_hint_unmute
+                                },
                             hintLabelResId = R.string.volume_ringer_hint_mute,
                             ringerMode = ringerMode,
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index 68f7896..cec3d1e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -22,11 +22,13 @@
 import android.media.AudioManager.STREAM_MUSIC
 import android.media.AudioManager.STREAM_NOTIFICATION
 import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.internal.logging.UiEventLogger
 import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.settingslib.volume.shared.model.AudioStreamModel
 import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.modes.shared.ModesUiIcons
@@ -49,7 +51,6 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /** Models a particular slider state. */
 class AudioStreamSliderViewModel
@@ -62,7 +63,7 @@
     private val zenModeInteractor: ZenModeInteractor,
     private val uiEventLogger: UiEventLogger,
     private val volumePanelLogger: VolumePanelLogger,
-    override val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
+    private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
 ) : SliderViewModel {
 
     private val volumeChanges = MutableStateFlow<Int?>(null)
@@ -171,6 +172,13 @@
         }
     }
 
+    override fun getSliderHapticsViewModelFactory(): SliderHapticsViewModel.Factory? =
+        if (Flags.hapticsForComposeSliders() && slider.value != SliderState.Empty) {
+            hapticsViewModelFactory
+        } else {
+            null
+        }
+
     private fun AudioStreamModel.toState(
         isEnabled: Boolean,
         ringerMode: RingerMode,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 8efe915..0d80452 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import android.media.session.MediaController.PlaybackInfo
+import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
 import com.android.systemui.res.R
@@ -32,7 +34,6 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 class CastVolumeSliderViewModel
 @AssistedInject
@@ -41,7 +42,7 @@
     @Assisted private val coroutineScope: CoroutineScope,
     private val context: Context,
     private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
-    override val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
+    private val hapticsViewModelFactory: SliderHapticsViewModel.Factory,
 ) : SliderViewModel {
 
     override val slider: StateFlow<SliderState> =
@@ -62,6 +63,13 @@
         // do nothing because this action isn't supported for Cast sliders.
     }
 
+    override fun getSliderHapticsViewModelFactory(): SliderHapticsViewModel.Factory? =
+        if (Flags.hapticsForComposeSliders() && slider.value != SliderState.Empty) {
+            hapticsViewModelFactory
+        } else {
+            null
+        }
+
     private fun PlaybackInfo.getCurrentState(): State {
         val volumeRange = 0..maxVolume
         return State(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
index 9c1783b..4d9552f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/SliderViewModel.kt
@@ -24,11 +24,11 @@
 
     val slider: StateFlow<SliderState>
 
-    val hapticsViewModelFactory: SliderHapticsViewModel.Factory
-
     fun onValueChanged(state: SliderState, newValue: Float)
 
     fun onValueChangeFinished()
 
     fun toggleMuted(state: SliderState)
+
+    fun getSliderHapticsViewModelFactory(): SliderHapticsViewModel.Factory?
 }
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index a95735e..82cfab6 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -56,7 +56,6 @@
 @RunWith(AndroidTestingRunner.class)
 @SmallTest
 public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
-
     private static final String TAG = "AAA++VerifyTest";
 
     private static final Class[] BASE_CLS_TO_INCLUDE = {
@@ -149,6 +148,9 @@
      */
     private boolean isTestClass(Class<?> loadedClass) {
         try {
+            if (loadedClass.getAnnotation(SkipSysuiVerification.class) != null) {
+                return false;
+            }
             if (Modifier.isAbstract(loadedClass.getModifiers())) {
                 logDebug(String.format("Skipping abstract class %s: not a test",
                         loadedClass.getName()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 071acfa..288ed4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -77,7 +77,10 @@
     @get:Rule(order = 2) val animatorTestRule = android.animation.AnimatorTestRule(this)
     @get:Rule(order = 3)
     val motionRule =
-        MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope), pathManager)
+        MotionTestRule(
+            AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope) { activityRule.scenario },
+            pathManager,
+        )
 
     @Test
     fun backgroundAnimation_whenLaunching() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 06e8b1c..05ec85a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -610,6 +610,7 @@
 
     @Test
     @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
+    @DisableSceneContainer
     public void testUpdateFooter_remoteInput() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -643,6 +644,7 @@
 
     @Test
     @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
+    @DisableSceneContainer
     public void testUpdateFooter_oneClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -659,6 +661,7 @@
 
     @Test
     @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
+    @DisableSceneContainer
     public void testUpdateFooter_withoutHistory() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -692,6 +695,7 @@
 
     @Test
     @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
+    @DisableSceneContainer
     public void testUpdateFooter_oneNonClearableNotification() {
         setBarStateForTest(StatusBarState.SHADE);
         mStackScroller.setCurrentUserSetup(true);
@@ -725,7 +729,9 @@
     }
 
     @Test
-    @DisableFlags({FooterViewRefactor.FLAG_NAME, ModesEmptyShadeFix.FLAG_NAME})
+    @DisableFlags({FooterViewRefactor.FLAG_NAME,
+        ModesEmptyShadeFix.FLAG_NAME,
+        NotifRedesignFooter.FLAG_NAME})
     public void testReInflatesFooterViews() {
         when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
         clearInvocations(mStackScroller);
@@ -1207,6 +1213,7 @@
     }
 
     @Test
+    @DisableSceneContainer
     public void testWindowInsetAnimationProgress_updatesBottomInset() {
         int imeInset = 100;
         WindowInsets windowInsets = new WindowInsets.Builder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f472fd1..11b5603 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -155,6 +155,7 @@
 import com.android.systemui.shade.ShadeControllerImpl;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
 import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -174,7 +175,6 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
 import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
-import com.android.systemui.statusbar.core.StatusBarOrchestrator;
 import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -371,7 +371,7 @@
     @Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
     @Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
     @Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
-    @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
+    @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
     private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -602,6 +602,7 @@
                 mShadeController,
                 mWindowRootViewVisibilityInteractor,
                 mStatusBarKeyguardViewManager,
+                () -> mStatusBarLongPressGestureDetector,
                 mViewMediatorCallback,
                 mInitController,
                 new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 638f195..69efa87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,14 +40,13 @@
 import com.android.systemui.plugins.fakeDarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
 import com.android.systemui.shade.ShadeControllerImpl
 import com.android.systemui.shade.ShadeLogger
 import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
 import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -98,7 +97,7 @@
     @Mock private lateinit var windowRootView: Provider<WindowRootView>
     @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var viewUtil: ViewUtil
-    @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
+    @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
     private lateinit var statusBarWindowStateController: StatusBarWindowStateController
 
     private lateinit var view: PhoneStatusBarView
@@ -395,7 +394,7 @@
                 shadeControllerImpl,
                 shadeViewController,
                 panelExpansionInteractor,
-                { longPressGestureDetector },
+                { mStatusBarLongPressGestureDetector },
                 windowRootView,
                 shadeLogger,
                 viewUtil,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 4d293b9..6326e73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -102,6 +102,7 @@
                     logBuffer = FakeLogBuffer.Factory.create(),
                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
                     systemClock,
+                    context.resources,
                 )
 
             val connectionState by collectLastValue(underTest.connectionState)
@@ -267,11 +268,7 @@
     fun satelliteProvisioned_notSupported_defaultFalse() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
 
             assertThat(underTest.isSatelliteProvisioned.value).isFalse()
         }
@@ -280,11 +277,7 @@
     fun satelliteProvisioned_supported_defaultFalse() =
         testScope.runTest {
             // GIVEN satellite is supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = true,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
 
             // THEN default provisioned state is false
             assertThat(underTest.isSatelliteProvisioned.value).isFalse()
@@ -323,6 +316,7 @@
                     logBuffer = FakeLogBuffer.Factory.create(),
                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
                     systemClock,
+                    context.resources,
                 )
 
             // WHEN we try to check for provisioned status
@@ -361,6 +355,7 @@
                     logBuffer = FakeLogBuffer.Factory.create(),
                     verboseLogBuffer = FakeLogBuffer.Factory.create(),
                     systemClock,
+                    context.resources,
                 )
 
             // WHEN we try to check for provisioned status
@@ -445,11 +440,7 @@
     fun satelliteProvisioned_supported_tracksCallback_reRegistersOnCrash() =
         testScope.runTest {
             // GIVEN satellite is supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = true,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
 
             val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
 
@@ -487,11 +478,7 @@
     fun satelliteNotSupported_listenersAreNotRegistered() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
 
             // WHEN data is requested from the repo
             val connectionState by collectLastValue(underTest.connectionState)
@@ -517,11 +504,7 @@
     fun satelliteNotSupported_registersCallbackForStateChanges() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
 
             runCurrent()
             // THEN the repo registers for state changes of satellite support
@@ -577,11 +560,7 @@
     fun satelliteNotSupported_supportShowsUp_registersListeners() =
         testScope.runTest {
             // GIVEN satellite is not supported
-            setUpRepo(
-                uptime = MIN_UPTIME,
-                satMan = satelliteManager,
-                satelliteSupported = false,
-            )
+            setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
             runCurrent()
 
             val callback =
@@ -610,11 +589,7 @@
     fun repoDoesNotCheckForSupportUntilMinUptime() =
         testScope.runTest {
             // GIVEN we init 100ms after sysui starts up
-            setUpRepo(
-                uptime = 100,
-                satMan = satelliteManager,
-                satelliteSupported = true,
-            )
+            setUpRepo(uptime = 100, satMan = satelliteManager, satelliteSupported = true)
 
             // WHEN data is requested
             val connectionState by collectLastValue(underTest.connectionState)
@@ -726,6 +701,7 @@
                 logBuffer = FakeLogBuffer.Factory.create(),
                 verboseLogBuffer = FakeLogBuffer.Factory.create(),
                 systemClock,
+                context.resources,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index c4c2aa7..48106de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -2533,6 +2533,42 @@
         verify(mBubbleLogger).log(BubbleLogger.Event.BUBBLE_BAR_DISMISSED_DRAG_BAR);
     }
 
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_expandAndCollapse() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        mEntryListener.onEntryAdded(mRow);
+        mBubbleController.expandStackAndSelectBubbleFromLauncher(mRow.getKey(), 0);
+
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
+
+        mBubbleController.collapseStack();
+
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_COLLAPSED));
+    }
+
+    @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
+    @Test
+    public void testEventLogging_bubbleBar_autoExpandingBubble() {
+        mBubbleProperties.mIsBubbleBarEnabled = true;
+        mPositioner.setIsLargeScreen(true);
+        FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
+        mBubbleController.registerBubbleStateListener(bubbleStateListener);
+
+        setMetadataFlags(mRow,
+                Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
+        mEntryListener.onEntryAdded(mRow);
+
+        verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()),
+                eq(BubbleLogger.Event.BUBBLE_BAR_EXPANDED));
+    }
+
     /** Creates a bubble using the userId and package. */
     private Bubble createBubble(int userId, String pkg) {
         final UserHandle userHandle = new UserHandle(userId);
diff --git a/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt
new file mode 100644
index 0000000..c28449f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.view
+
+import android.content.Context
+import android.graphics.Region
+import android.view.WindowManager.LayoutParams
+
+class FakeWindowManager(private val context: Context) : WindowManager {
+
+    val addedViews = mutableMapOf<View, LayoutParams>()
+
+    override fun addView(view: View, params: ViewGroup.LayoutParams) {
+        addedViews[view] = params as LayoutParams
+    }
+
+    override fun removeView(view: View) {
+        addedViews.remove(view)
+    }
+
+    override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
+        addedViews[view] = params as LayoutParams
+    }
+
+    override fun getApplicationLaunchKeyboardShortcuts(deviceId: Int): KeyboardShortcutGroup {
+        return KeyboardShortcutGroup("Fake group")
+    }
+
+    override fun getCurrentImeTouchRegion(): Region {
+        return Region.obtain()
+    }
+
+    override fun getDefaultDisplay(): Display {
+        return context.display
+    }
+
+    override fun removeViewImmediate(view: View) {
+        addedViews.remove(view)
+    }
+
+    override fun requestAppKeyboardShortcuts(
+        receiver: WindowManager.KeyboardShortcutsReceiver,
+        deviceId: Int,
+    ) {
+        receiver.onKeyboardShortcutsReceived(emptyList())
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index d5451ee..025f556 100644
--- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -16,9 +16,12 @@
 
 package android.view
 
+import android.content.applicationContext
 import com.android.systemui.kosmos.Kosmos
 import org.mockito.Mockito.mock
 
+val Kosmos.fakeWindowManager by Kosmos.Fixture { FakeWindowManager(applicationContext) }
+
 val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) }
 
 var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
index e1c6699..021c7bb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
@@ -16,11 +16,21 @@
 
 package com.android.app.viewcapture
 
+import android.view.fakeWindowManager
 import com.android.systemui.kosmos.Kosmos
 import org.mockito.kotlin.mock
 
 val Kosmos.mockViewCaptureAwareWindowManager by
     Kosmos.Fixture { mock<ViewCaptureAwareWindowManager>() }
 
+val Kosmos.realCaptureAwareWindowManager by
+    Kosmos.Fixture {
+        ViewCaptureAwareWindowManager(
+            fakeWindowManager,
+            lazyViewCapture = lazy { mock<ViewCapture>() },
+            isViewCaptureEnabled = false,
+        )
+    }
+
 var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by
     Kosmos.Fixture { mockViewCaptureAwareWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
new file mode 100644
index 0000000..778614b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
@@ -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 com.android.systemui
+
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runners.model.MultipleFailureException
+
+/**
+ * Rule that allows teardown steps to be added right next to the places where it becomes clear they
+ * are needed. This can avoid the need for complicated or conditional logic in a single teardown
+ * method. Examples:
+ * ```
+ * @get:Rule teardownRule = OnTeardownRule()
+ *
+ * // setup and teardown right next to each other
+ * @Before
+ * fun setUp() {
+ *   val oldTimeout = getGlobalTimeout()
+ *   teardownRule.onTeardown { setGlobalTimeout(oldTimeout) }
+ *   overrideGlobalTimeout(5000)
+ * }
+ *
+ * // add teardown logic for fixtures that aren't used in every test
+ * fun addCustomer() {
+ *   val id = globalDatabase.addCustomer(TEST_NAME, TEST_ADDRESS, ...)
+ *   teardownRule.onTeardown { globalDatabase.deleteCustomer(id) }
+ * }
+ * ```
+ */
+class OnTeardownRule : TestWatcher() {
+    private var canAdd = true
+    private val teardowns = mutableListOf<() -> Unit>()
+
+    fun onTeardown(teardownRunnable: () -> Unit) {
+        if (!canAdd) {
+            throw IllegalStateException("Cannot add new teardown routines after test complete.")
+        }
+        teardowns.add(teardownRunnable)
+    }
+
+    fun onTeardown(teardownRunnable: Runnable) {
+        if (!canAdd) {
+            throw IllegalStateException("Cannot add new teardown routines after test complete.")
+        }
+        teardowns.add { teardownRunnable.run() }
+    }
+
+    override fun finished(description: Description?) {
+        canAdd = false
+        val errors = mutableListOf<Throwable>()
+        teardowns.reversed().forEach {
+            try {
+                it()
+            } catch (e: Throwable) {
+                errors.add(e)
+            }
+        }
+        MultipleFailureException.assertEmpty(errors)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 27a2cab..153a8be 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -56,6 +56,8 @@
 
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
@@ -69,6 +71,17 @@
 // background on Ravenwood is available at go/ravenwood-docs
 @DisabledOnRavenwood
 public abstract class SysuiTestCase {
+    /**
+     * Especially when self-testing test utilities, we may have classes that look like test
+     * classes, but we don't expect to ever actually run as a top-level test.
+     * For example, {@link com.android.systemui.TryToDoABadThing}.
+     * Verifying properties on these as a part of structural tests like
+     * AAAPlusPlusVerifySysuiRequiredTestPropertiesTest is a waste of our time, and makes things
+     * look more confusing, so this lets us skip when appropriate.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface SkipSysuiVerification {
+    }
 
     private static final String TAG = "SysuiTestCase";
 
@@ -172,6 +185,15 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
+    @Rule public final OnTeardownRule mTearDownRule = new OnTeardownRule();
+
+    /**
+     * Schedule a cleanup routine to happen when the test state is torn down.
+     */
+    protected void onTeardown(Runnable tearDownRunnable) {
+        mTearDownRule.onTeardown(tearDownRunnable);
+    }
+
     // set the highest order so it's the innermost rule
     @Rule(order = Integer.MAX_VALUE)
     public TestWithLooperRule mlooperRule = new TestWithLooperRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 3041240..b8be6aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -29,6 +29,8 @@
 import android.util.Log;
 import android.view.Display;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.res.R;
 
@@ -43,6 +45,9 @@
     private final Map<UserHandle, Context> mContextForUser = new HashMap<>();
     private final Map<String, Context> mContextForPackage = new HashMap<>();
 
+    @Nullable
+    private Display mCustomDisplay;
+
     public SysuiTestableContext(Context base) {
         super(base);
         setTheme(R.style.Theme_SystemUI);
@@ -64,6 +69,18 @@
         return context;
     }
 
+    public void setDisplay(Display display) {
+        mCustomDisplay = display;
+    }
+
+    @Override
+    public Display getDisplay() {
+        if (mCustomDisplay != null) {
+            return mCustomDisplay;
+        }
+        return super.getDisplay();
+    }
+
     public SysuiTestableContext createDefaultDisplayContext() {
         Display display = getBaseContext().getSystemService(DisplayManager.class).getDisplays()[0];
         return (SysuiTestableContext) createDisplayContext(display);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 62d221d..b27dadc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -40,8 +40,18 @@
     ) {
         coroutineScope.launch {
             val id = nextWidgetId++
-            val providerInfo = AppWidgetProviderInfo().apply { this.provider = provider }
-            val configured = configurator?.configureWidget(id) ?: true
+            val providerInfo = createAppWidgetProviderInfo(provider, user.identifier)
+
+            fakeDatabase[id] =
+                CommunalWidgetContentModel.Available(
+                    appWidgetId = id,
+                    rank = rank ?: 0,
+                    providerInfo = providerInfo,
+                    spanY = 3,
+                )
+            updateListFromDatabase()
+
+            val configured = configurator?.configureWidget(id) != false
             if (configured) {
                 onConfigured(id, providerInfo, rank ?: -1)
             }
@@ -61,20 +71,15 @@
                 appWidgetId = appWidgetId,
                 rank = rank,
                 providerInfo =
-                    AppWidgetProviderInfo().apply {
-                        provider = ComponentName.unflattenFromString(componentName)!!
-                        widgetCategory = category
-                        providerInfo =
-                            ActivityInfo().apply {
-                                applicationInfo =
-                                    ApplicationInfo().apply {
-                                        uid = userId * UserHandle.PER_USER_RANGE
-                                    }
-                            }
-                    },
+                    createAppWidgetProviderInfo(
+                        ComponentName.unflattenFromString(componentName)!!,
+                        userId,
+                        category,
+                    ),
                 spanY = spanY,
             )
         updateListFromDatabase()
+        nextWidgetId = appWidgetId + 1
     }
 
     fun addPendingWidget(
@@ -151,4 +156,20 @@
             )
         updateListFromDatabase()
     }
+
+    private fun createAppWidgetProviderInfo(
+        componentName: ComponentName,
+        userId: Int,
+        category: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
+    ): AppWidgetProviderInfo {
+        return AppWidgetProviderInfo().apply {
+            provider = componentName
+            widgetCategory = category
+            providerInfo =
+                ActivityInfo().apply {
+                    applicationInfo =
+                        ApplicationInfo().apply { uid = userId * UserHandle.PER_USER_RANGE }
+                }
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 629fda6..1f68195 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -54,7 +54,6 @@
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         communalSettingsInteractor = communalSettingsInteractor,
-        appWidgetHost = mock(),
         editWidgetsActivityStarter = editWidgetsActivityStarter,
         userTracker = userTracker,
         activityStarter = activityStarter,
@@ -62,7 +61,7 @@
         sceneInteractor = sceneInteractor,
         logBuffer = logcatLogBuffer("CommunalInteractor"),
         tableLogBuffer = mock(),
-        managedProfileController = fakeManagedProfileController
+        managedProfileController = fakeManagedProfileController,
     )
 }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/FakeGlanceableHubMultiUserHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/FakeGlanceableHubMultiUserHelper.kt
new file mode 100644
index 0000000..de44399
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/FakeGlanceableHubMultiUserHelper.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.communal.shared.model
+
+class FakeGlanceableHubMultiUserHelper(
+    override val glanceableHubHsumFlagEnabled: Boolean = true,
+    private var isHeadlessSystemUserMode: Boolean = false,
+    private var isInHeadlessSystemUser: Boolean = false,
+) : GlanceableHubMultiUserHelper {
+
+    override fun isHeadlessSystemUserMode(): Boolean {
+        return isHeadlessSystemUserMode
+    }
+
+    fun setIsHeadlessSystemUserMode(isHeadlessSystemUserMode: Boolean) {
+        this.isHeadlessSystemUserMode = isHeadlessSystemUserMode
+    }
+
+    override fun isInHeadlessSystemUser(): Boolean {
+        return isInHeadlessSystemUser
+    }
+
+    fun setIsInHeadlessSystemUser(isInHeadlessSystemUser: Boolean) {
+        this.isInHeadlessSystemUser = isInHeadlessSystemUser
+    }
+
+    override fun assertInHeadlessSystemUser() {
+        check(isInHeadlessSystemUser())
+    }
+
+    override fun assertNotInHeadlessSystemUser() {
+        check(!isInHeadlessSystemUser())
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelperKosmos.kt
new file mode 100644
index 0000000..adee440
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/model/GlanceableHubMultiUserHelperKosmos.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.communal.shared.model
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeGlanceableHubMultiUserHelper by Kosmos.Fixture { FakeGlanceableHubMultiUserHelper() }
+
+val Kosmos.glanceableHubMultiUserHelper by
+    Kosmos.Fixture<GlanceableHubMultiUserHelper> { fakeGlanceableHubMultiUserHelper }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerKosmos.kt
new file mode 100644
index 0000000..583ae44
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.communal.widgets
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockGlanceableHubWidgetManager by Kosmos.Fixture<GlanceableHubWidgetManager> { mock() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
new file mode 100644
index 0000000..4bcff55
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.decor
+
+import android.content.testableContext
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.privacyDotDecorProviderFactory by
+    Kosmos.Fixture {
+        PrivacyDotDecorProviderFactory(testableContext.orCreateTestableResources.resources)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 4976cc2..19e077c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -53,6 +53,15 @@
     private val initInLockscreen: Boolean = true,
 
     /**
+     * Initial value for [FakeKeyguardTransitionRepository.sendTransitionStepsOnStartTransition].
+     * This needs to be configurable in the constructor since some transitions are triggered on
+     * init, before a test has the chance to set sendTransitionStepsOnStartTransition to false.
+     */
+    private val initiallySendTransitionStepsOnStartTransition: Boolean = true,
+    private val testScope: TestScope,
+) : KeyguardTransitionRepository {
+
+    /**
      * If true, calls to [startTransition] will automatically emit STARTED, RUNNING, and FINISHED
      * transition steps from/to the given states.
      *
@@ -64,11 +73,9 @@
      *
      * If your test needs to make assertions at specific points between STARTED/FINISHED, or if it's
      * difficult to set up all of the conditions to make the transition interactors actually call
-     * startTransition, then construct a FakeKeyguardTransitionRepository with this value false.
+     * startTransition, set this value to false.
      */
-    private val sendTransitionStepsOnStartTransition: Boolean = true,
-    private val testScope: TestScope,
-) : KeyguardTransitionRepository {
+    var sendTransitionStepsOnStartTransition = initiallySendTransitionStepsOnStartTransition
 
     private val _transitions =
         MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
@@ -77,7 +84,11 @@
     @Inject
     constructor(
         testScope: TestScope
-    ) : this(initInLockscreen = true, sendTransitionStepsOnStartTransition = true, testScope)
+    ) : this(
+        initInLockscreen = true,
+        initiallySendTransitionStepsOnStartTransition = true,
+        testScope
+    )
 
     private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
         MutableStateFlow(
@@ -176,6 +187,20 @@
         testScheduler: TestCoroutineScheduler,
         throughTransitionState: TransitionState = TransitionState.FINISHED,
     ) {
+        val lastStep = _transitions.replayCache.lastOrNull()
+        if (lastStep != null && lastStep.transitionState != TransitionState.FINISHED) {
+            sendTransitionStep(
+                step =
+                TransitionStep(
+                    transitionState = TransitionState.CANCELED,
+                    from = lastStep.from,
+                    to = lastStep.to,
+                    value = 0f,
+                )
+            )
+            testScheduler.runCurrent()
+        }
+
         sendTransitionStep(
             step =
                 TransitionStep(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplKosmos.kt
new file mode 100644
index 0000000..c337298
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplKosmos.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.media.controls.domain.pipeline
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+/** Empty mock */
+val Kosmos.legacyMediaDataManagerImpl by Kosmos.Fixture { mock<LegacyMediaDataManagerImpl>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerKosmos.kt
new file mode 100644
index 0000000..f733da1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataManagerKosmos.kt
@@ -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 com.android.systemui.media.controls.domain.pipeline
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.mediaDataManager: MediaDataManager by Kosmos.Fixture()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerKosmos.kt
new file mode 100644
index 0000000..3c35ce9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerKosmos.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.media.controls.ui.controller
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+/** Empty mock */
+val Kosmos.mediaCarouselController by Kosmos.Fixture { mock<MediaCarouselController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLoggerKosmos.kt
new file mode 100644
index 0000000..9f5a832
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerLoggerKosmos.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.media.controls.ui.controller
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.core.FakeLogBuffer
+
+val Kosmos.mediaCarouselControllerLogger by
+    Kosmos.Fixture { MediaCarouselControllerLogger(FakeLogBuffer.Factory.create()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
index 7c24b4c..624e174 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerKosmos.kt
@@ -16,8 +16,15 @@
 
 package com.android.systemui.media.controls.ui.controller
 
+import android.content.testableContext
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.animation.UniqueObjectHostView
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
 
-var Kosmos.mediaHierarchyManager by Fixture { mock<MediaHierarchyManager>() }
+var Kosmos.mediaHierarchyManager by Fixture {
+    mock<MediaHierarchyManager> {
+        on { register(any()) }.thenAnswer { UniqueObjectHostView(this@Fixture.testableContext) }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManagerKosmos.kt
new file mode 100644
index 0000000..8b02c57
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/controller/MediaHostStatesManagerKosmos.kt
@@ -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 com.android.systemui.media.controls.ui.controller
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.mediaHostStatesManager by Kosmos.Fixture { MediaHostStatesManager() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/view/MediaHostKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/view/MediaHostKosmos.kt
new file mode 100644
index 0000000..9638e11
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/view/MediaHostKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.media.controls.ui.view
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.domain.pipeline.mediaDataManager
+import com.android.systemui.media.controls.ui.controller.mediaCarouselController
+import com.android.systemui.media.controls.ui.controller.mediaCarouselControllerLogger
+import com.android.systemui.media.controls.ui.controller.mediaHierarchyManager
+import com.android.systemui.media.controls.ui.controller.mediaHostStatesManager
+import java.util.function.Supplier
+
+private val Kosmos.mediaHostProvider by
+    Kosmos.Fixture {
+        Supplier<MediaHost> {
+            MediaHost(
+                MediaHost.MediaHostStateHolder(),
+                mediaHierarchyManager,
+                mediaDataManager,
+                mediaHostStatesManager,
+                mediaCarouselController,
+                mediaCarouselControllerLogger,
+            )
+        }
+    }
+
+val Kosmos.qqsMediaHost by Kosmos.Fixture { mediaHostProvider.get() }
+val Kosmos.qsMediaHost by Kosmos.Fixture { mediaHostProvider.get() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModuleKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModuleKosmos.kt
new file mode 100644
index 0000000..fb8ffb0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModuleKosmos.kt
@@ -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 com.android.systemui.qs.composefragment.dagger
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.usingMediaInComposeFragment by Kosmos.Fixture<Boolean>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index 462b408..bda3192 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -22,10 +22,13 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.media.controls.ui.view.qqsMediaHost
+import com.android.systemui.media.controls.ui.view.qsMediaHost
+import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
 import com.android.systemui.qs.footerActionsController
 import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.panels.domain.interactor.tileSquishinessInteractor
-import com.android.systemui.qs.panels.ui.viewmodel.paginatedGridViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.inFirstPageViewModel
 import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModelFactory
 import com.android.systemui.shade.largeScreenHeaderHelper
 import com.android.systemui.shade.transition.largeScreenShadeInterpolator
@@ -53,7 +56,10 @@
                     configurationInteractor,
                     largeScreenHeaderHelper,
                     tileSquishinessInteractor,
-                    paginatedGridViewModel,
+                    inFirstPageViewModel,
+                    qqsMediaHost,
+                    qsMediaHost,
+                    usingMediaInComposeFragment,
                     lifecycleScope,
                 )
             }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModelKosmos.kt
new file mode 100644
index 0000000..1fa74ca
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/InFirstPageViewModelKosmos.kt
@@ -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 com.android.systemui.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.inFirstPageViewModel by Kosmos.Fixture { InFirstPageViewModel() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
index 48ef57e..5c8ca83 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/PaginatedGridViewModelKosmos.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.qs.panels.ui.viewmodel
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.qs.panels.domain.interactor.paginatedGridInteractor
 
 val Kosmos.paginatedGridViewModel by
@@ -26,6 +25,6 @@
             iconTilesViewModel,
             qsColumnsViewModel,
             paginatedGridInteractor,
-            applicationCoroutineScope,
+            inFirstPageViewModel,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
index 41ee260..20be5c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelKosmos.kt
@@ -18,19 +18,21 @@
 
 import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.qs.panels.domain.interactor.quickQuickSettingsRowInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor
 
-val Kosmos.quickQuickSettingsViewModel by
+val Kosmos.quickQuickSettingsViewModelFactory by
     Kosmos.Fixture {
-        QuickQuickSettingsViewModel(
-            currentTilesInteractor,
-            qsColumnsViewModel,
-            quickQuickSettingsRowInteractor,
-            tileSquishinessViewModel,
-            iconTilesViewModel,
-            applicationCoroutineScope,
-            tileHapticsViewModelFactoryProvider,
-        )
+        object : QuickQuickSettingsViewModel.Factory {
+            override fun create(): QuickQuickSettingsViewModel {
+                return QuickQuickSettingsViewModel(
+                    currentTilesInteractor,
+                    qsColumnsViewModel,
+                    quickQuickSettingsRowInteractor,
+                    tileSquishinessViewModel,
+                    iconTilesViewModel,
+                    tileHapticsViewModelFactoryProvider,
+                )
+            }
+        }
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
index 6ded751..ce103ec 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelKosmos.kt
@@ -19,7 +19,7 @@
 import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModelFactory
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel
-import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModel
+import com.android.systemui.qs.panels.ui.viewmodel.quickQuickSettingsViewModelFactory
 import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel
 
 val Kosmos.quickSettingsContainerViewModelFactory by
@@ -30,10 +30,10 @@
             ): QuickSettingsContainerViewModel {
                 return QuickSettingsContainerViewModel(
                     brightnessSliderViewModelFactory,
+                    quickQuickSettingsViewModelFactory,
                     supportsBrightnessMirroring,
                     tileGridViewModel,
                     editModeViewModel,
-                    quickQuickSettingsViewModel,
                 )
             }
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
index 4f414d9..3300c96 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt
@@ -17,6 +17,7 @@
 import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
 import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
 import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
 import kotlinx.coroutines.flow.MutableStateFlow
 import org.mockito.kotlin.mock
 
@@ -87,6 +88,7 @@
                 falsingInteractor = falsingInteractor,
                 powerInteractor = powerInteractor,
                 shadeInteractor = shadeInteractor,
+                remoteInputInteractor = remoteInputInteractor,
                 splitEdgeDetector = splitEdgeDetector,
                 logger = sceneLogger,
                 hapticsViewModelFactory = sceneContainerHapticsViewModelFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 87f7142..ad2654a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.shade.mockNotificationShadeWindowViewController
 import com.android.systemui.shade.mockShadeSurface
 import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.data.repository.privacyDotWindowControllerStore
 import com.android.systemui.statusbar.data.repository.statusBarModeRepository
 import com.android.systemui.statusbar.mockNotificationRemoteInputManager
 import com.android.systemui.statusbar.phone.mockAutoHideController
@@ -77,5 +78,6 @@
             statusBarInitializerStore,
             statusBarWindowControllerStore,
             statusBarInitializerStore,
+            privacyDotWindowControllerStore,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt
new file mode 100644
index 0000000..27845aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.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.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.PrivacyDotViewController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotViewControllerStore : PrivacyDotViewControllerStore {
+    private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotViewController>()
+
+    override val defaultDisplay: PrivacyDotViewController
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): PrivacyDotViewController {
+        return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt
new file mode 100644
index 0000000..f0aacc0d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt
@@ -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.systemui.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.PrivacyDotWindowController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotWindowControllerStore : PrivacyDotWindowControllerStore {
+
+    private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotWindowController>()
+
+    override val defaultDisplay: PrivacyDotWindowController
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): PrivacyDotWindowController {
+        return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
index 91602c2..375bede 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeRemoteInputRepository.kt
@@ -23,6 +23,11 @@
 class FakeRemoteInputRepository : RemoteInputRepository {
     override val isRemoteInputActive = MutableStateFlow(false)
     override val remoteInputRowBottomBound: Flow<Float?> = flowOf(null)
+    var areRemoteInputsClosed: Boolean = false
 
     override fun setRemoteInputRowBottomBound(bottom: Float?) {}
+
+    override fun closeRemoteInputs() {
+        areRemoteInputsClosed = true
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt
new file mode 100644
index 0000000..fa9f1bf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt
@@ -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.systemui.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.SystemEventChipAnimationController
+import org.mockito.kotlin.mock
+
+class FakeSystemEventChipAnimationControllerStore : SystemEventChipAnimationControllerStore {
+
+    private val perDisplayMocks = mutableMapOf<Int, SystemEventChipAnimationController>()
+
+    override val defaultDisplay: SystemEventChipAnimationController
+        get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+    override fun forDisplay(displayId: Int): SystemEventChipAnimationController {
+        return perDisplayMocks.computeIfAbsent(displayId) { mock() }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
new file mode 100644
index 0000000..5f33732
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.statusbar.data.repository
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayScopeRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.lightBarControllerStoreImpl by
+    Kosmos.Fixture {
+        LightBarControllerStoreImpl(
+            backgroundApplicationScope = applicationCoroutineScope,
+            displayRepository = displayRepository,
+            factory = { _, _, _ -> mock() },
+            displayScopeRepository = displayScopeRepository,
+            statusBarModeRepositoryStore = statusBarModeRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
new file mode 100644
index 0000000..3d428a1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.statusbar.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakePrivacyDotViewControllerStore by
+    Kosmos.Fixture { FakePrivacyDotViewControllerStore() }
+
+var Kosmos.privacyDotViewControllerStore: PrivacyDotViewControllerStore by
+    Kosmos.Fixture { fakePrivacyDotViewControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
new file mode 100644
index 0000000..aae32cfa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.fakePrivacyDotWindowControllerStore by
+    Kosmos.Fixture { FakePrivacyDotWindowControllerStore() }
+
+val Kosmos.privacyDotWindowControllerStoreImpl by
+    Kosmos.Fixture {
+        PrivacyDotWindowControllerStoreImpl(
+            backgroundApplicationScope = applicationCoroutineScope,
+            displayRepository = displayRepository,
+            windowControllerFactory = { _, _, _, _ -> mock() },
+            displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+            privacyDotViewControllerStore = privacyDotViewControllerStore,
+            viewCaptureAwareWindowManagerFactory =
+                object : ViewCaptureAwareWindowManager.Factory {
+                    override fun create(
+                        windowManager: WindowManager
+                    ): ViewCaptureAwareWindowManager {
+                        return mock()
+                    }
+                },
+        )
+    }
+
+var Kosmos.privacyDotWindowControllerStore: PrivacyDotWindowControllerStore by
+    Kosmos.Fixture { fakePrivacyDotWindowControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt
new file mode 100644
index 0000000..f0c8f4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.statusbar.data.repository
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.window.statusBarWindowControllerStore
+import org.mockito.kotlin.mock
+
+val Kosmos.fakeSystemEventChipAnimationControllerStore by
+    Kosmos.Fixture { FakeSystemEventChipAnimationControllerStore() }
+
+val Kosmos.systemEventChipAnimationControllerStoreImpl by
+    Kosmos.Fixture {
+        SystemEventChipAnimationControllerStoreImpl(
+            backgroundApplicationScope = applicationCoroutineScope,
+            displayRepository = displayRepository,
+            factory = { _, _, _ -> mock() },
+            displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+            statusBarWindowControllerStore = statusBarWindowControllerStore,
+            statusBarContentInsetsProviderStore = statusBarContentInsetsProviderStore,
+        )
+    }
+
+var Kosmos.systemEventChipAnimationControllerStore by
+    Kosmos.Fixture { fakeSystemEventChipAnimationControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
new file mode 100644
index 0000000..53c39a6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.statusbar.events
+
+import android.view.View
+
+class FakePrivacyDotViewController : PrivacyDotViewController {
+
+    var topLeft: View? = null
+        private set
+
+    var topRight: View? = null
+        private set
+
+    var bottomLeft: View? = null
+        private set
+
+    var bottomRight: View? = null
+        private set
+
+    var isInitialized = false
+        private set
+
+    override fun stop() {}
+
+    override var currentViewState: ViewState = ViewState()
+
+    override var showingListener: PrivacyDotViewController.ShowingListener? = null
+
+    override fun setNewRotation(rot: Int) {}
+
+    override fun hideDotView(dot: View, animate: Boolean) {}
+
+    override fun showDotView(dot: View, animate: Boolean) {}
+
+    override fun updateRotations(rotation: Int, paddingTop: Int) {}
+
+    override fun setCornerSizes(state: ViewState) {}
+
+    override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
+        this.topLeft = topLeft
+        this.topRight = topRight
+        this.bottomLeft = bottomLeft
+        this.bottomRight = bottomRight
+        isInitialized = true
+    }
+
+    override fun updateDotView(state: ViewState) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
new file mode 100644
index 0000000..9cbc975
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.statusbar.events
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockPrivacyDotViewController by Kosmos.Fixture { mock<PrivacyDotViewController>() }
+
+val Kosmos.fakePrivacyDotViewController by Kosmos.Fixture { FakePrivacyDotViewController() }
+
+var Kosmos.privacyDotViewController by Kosmos.Fixture { fakePrivacyDotViewController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
new file mode 100644
index 0000000..c738387
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.statusbar.events
+
+import android.content.testableContext
+import android.view.layoutInflater
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.decor.privacyDotDecorProviderFactory
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.privacyDotWindowController by
+    Kosmos.Fixture {
+        PrivacyDotWindowController(
+            testableContext.displayId,
+            privacyDotViewController,
+            realCaptureAwareWindowManager,
+            layoutInflater,
+            fakeExecutor,
+            privacyDotDecorProviderFactory,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt
new file mode 100644
index 0000000..186b045
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt
@@ -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 com.android.systemui.statusbar.events
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+
+val Kosmos.multiDisplaySystemEventChipAnimationController by
+    Kosmos.Fixture {
+        MultiDisplaySystemEventChipAnimationController(
+            displayRepository,
+            systemEventChipAnimationControllerStore,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt
index 8785256..87ea147 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManagerKosmos.kt
@@ -19,6 +19,7 @@
 import android.content.applicationContext
 import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -43,5 +44,6 @@
             mock<UnlockedScreenOffAnimationController>(),
             primaryBouncerInteractor,
             alternateBouncerInteractor,
+            communalSceneInteractor,
         )
     }
diff --git a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
index 8663593..44ea9a2 100644
--- a/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
+++ b/packages/WallpaperBackup/src/com/android/wallpaperbackup/WallpaperBackupAgent.java
@@ -945,10 +945,11 @@
                 String tag = parser.getName();
                 if (!sectionTag.equals(tag)) continue;
                 for (Pair<Integer, String> pair : List.of(
-                        new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
-                        new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
-                        new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
-                        new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"))) {
+                        new Pair<>(WallpaperManager.ORIENTATION_PORTRAIT, "Portrait"),
+                        new Pair<>(WallpaperManager.ORIENTATION_LANDSCAPE, "Landscape"),
+                        new Pair<>(WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, "SquarePortrait"),
+                        new Pair<>(WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE,
+                                "SquareLandscape"))) {
                     Rect cropHint = new Rect(
                             getAttributeInt(parser, "cropLeft" + pair.second, 0),
                             getAttributeInt(parser, "cropTop" + pair.second, 0),
diff --git a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
index 3ecdf3f..f5fb644 100644
--- a/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
+++ b/packages/WallpaperBackup/test/src/com/android/wallpaperbackup/WallpaperBackupAgentTest.java
@@ -827,16 +827,17 @@
 
     @Test
     public void testOnRestore_singleCropHint() throws Exception {
-        Map<Integer, Rect> testMap = Map.of(WallpaperManager.PORTRAIT, new Rect(1, 2, 3, 4));
+        Map<Integer, Rect> testMap = Map.of(
+                WallpaperManager.ORIENTATION_PORTRAIT, new Rect(1, 2, 3, 4));
         testParseCropHints(testMap);
     }
 
     @Test
     public void testOnRestore_multipleCropHints() throws Exception {
         Map<Integer, Rect> testMap = Map.of(
-                WallpaperManager.PORTRAIT, new Rect(1, 2, 3, 4),
-                WallpaperManager.SQUARE_PORTRAIT, new Rect(5, 6, 7, 8),
-                WallpaperManager.SQUARE_LANDSCAPE, new Rect(9, 10, 11, 12));
+                WallpaperManager.ORIENTATION_PORTRAIT, new Rect(1, 2, 3, 4),
+                WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, new Rect(5, 6, 7, 8),
+                WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE, new Rect(9, 10, 11, 12));
         testParseCropHints(testMap);
     }
 
@@ -936,10 +937,10 @@
         out.startTag(null, "wp");
         for (Map.Entry<Integer, Rect> entry: crops.entrySet()) {
             String orientation = switch (entry.getKey()) {
-                case WallpaperManager.PORTRAIT -> "Portrait";
-                case WallpaperManager.LANDSCAPE -> "Landscape";
-                case WallpaperManager.SQUARE_PORTRAIT -> "SquarePortrait";
-                case WallpaperManager.SQUARE_LANDSCAPE -> "SquareLandscape";
+                case WallpaperManager.ORIENTATION_PORTRAIT -> "Portrait";
+                case WallpaperManager.ORIENTATION_LANDSCAPE -> "Landscape";
+                case WallpaperManager.ORIENTATION_SQUARE_PORTRAIT -> "SquarePortrait";
+                case WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE -> "SquareLandscape";
                 default -> throw new IllegalArgumentException("Invalid orientation");
             };
             Rect rect = entry.getValue();
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 65ea9fe..b3f78ab 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -360,6 +360,239 @@
     ],
 }
 
+filegroup {
+    name: "ravenwood-data",
+    device_common_srcs: [
+        ":system-build.prop",
+        ":framework-res",
+        ":ravenwood-empty-res",
+        ":framework-platform-compat-config",
+        ":services-platform-compat-config",
+    ],
+    device_first_srcs: [
+        ":apex_icu.dat",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// Keep in sync with build/make/target/product/generic/Android.bp
+filegroup {
+    name: "ravenwood-fonts",
+    device_common_srcs: [
+        ":AndroidClock.ttf",
+        ":CarroisGothicSC-Regular.ttf",
+        ":ComingSoon.ttf",
+        ":CutiveMono.ttf",
+        ":DancingScript-Regular.ttf",
+        ":DroidSansMono.ttf",
+        ":NotoColorEmoji.ttf",
+        ":NotoColorEmojiFlags.ttf",
+        ":NotoNaskhArabic-Bold.ttf",
+        ":NotoNaskhArabic-Regular.ttf",
+        ":NotoNaskhArabicUI-Bold.ttf",
+        ":NotoNaskhArabicUI-Regular.ttf",
+        ":NotoSansAdlam-VF.ttf",
+        ":NotoSansAhom-Regular.otf",
+        ":NotoSansAnatolianHieroglyphs-Regular.otf",
+        ":NotoSansArmenian-VF.ttf",
+        ":NotoSansAvestan-Regular.ttf",
+        ":NotoSansBalinese-Regular.ttf",
+        ":NotoSansBamum-Regular.ttf",
+        ":NotoSansBassaVah-Regular.otf",
+        ":NotoSansBatak-Regular.ttf",
+        ":NotoSansBengali-VF.ttf",
+        ":NotoSansBengaliUI-VF.ttf",
+        ":NotoSansBhaiksuki-Regular.otf",
+        ":NotoSansBrahmi-Regular.ttf",
+        ":NotoSansBuginese-Regular.ttf",
+        ":NotoSansBuhid-Regular.ttf",
+        ":NotoSansCJK-Regular.ttc",
+        ":NotoSansCanadianAboriginal-Regular.ttf",
+        ":NotoSansCarian-Regular.ttf",
+        ":NotoSansChakma-Regular.otf",
+        ":NotoSansCham-Bold.ttf",
+        ":NotoSansCham-Regular.ttf",
+        ":NotoSansCherokee-Regular.ttf",
+        ":NotoSansCoptic-Regular.ttf",
+        ":NotoSansCuneiform-Regular.ttf",
+        ":NotoSansCypriot-Regular.ttf",
+        ":NotoSansDeseret-Regular.ttf",
+        ":NotoSansDevanagari-VF.ttf",
+        ":NotoSansDevanagariUI-VF.ttf",
+        ":NotoSansEgyptianHieroglyphs-Regular.ttf",
+        ":NotoSansElbasan-Regular.otf",
+        ":NotoSansEthiopic-VF.ttf",
+        ":NotoSansGeorgian-VF.ttf",
+        ":NotoSansGlagolitic-Regular.ttf",
+        ":NotoSansGothic-Regular.ttf",
+        ":NotoSansGrantha-Regular.ttf",
+        ":NotoSansGujarati-Bold.ttf",
+        ":NotoSansGujarati-Regular.ttf",
+        ":NotoSansGujaratiUI-Bold.ttf",
+        ":NotoSansGujaratiUI-Regular.ttf",
+        ":NotoSansGunjalaGondi-Regular.otf",
+        ":NotoSansGurmukhi-VF.ttf",
+        ":NotoSansGurmukhiUI-VF.ttf",
+        ":NotoSansHanifiRohingya-Regular.otf",
+        ":NotoSansHanunoo-Regular.ttf",
+        ":NotoSansHatran-Regular.otf",
+        ":NotoSansHebrew-Bold.ttf",
+        ":NotoSansHebrew-Regular.ttf",
+        ":NotoSansImperialAramaic-Regular.ttf",
+        ":NotoSansInscriptionalPahlavi-Regular.ttf",
+        ":NotoSansInscriptionalParthian-Regular.ttf",
+        ":NotoSansJavanese-Regular.otf",
+        ":NotoSansKaithi-Regular.ttf",
+        ":NotoSansKannada-VF.ttf",
+        ":NotoSansKannadaUI-VF.ttf",
+        ":NotoSansKayahLi-Regular.ttf",
+        ":NotoSansKharoshthi-Regular.ttf",
+        ":NotoSansKhmer-VF.ttf",
+        ":NotoSansKhmerUI-Bold.ttf",
+        ":NotoSansKhmerUI-Regular.ttf",
+        ":NotoSansKhojki-Regular.otf",
+        ":NotoSansLao-Bold.ttf",
+        ":NotoSansLao-Regular.ttf",
+        ":NotoSansLaoUI-Bold.ttf",
+        ":NotoSansLaoUI-Regular.ttf",
+        ":NotoSansLepcha-Regular.ttf",
+        ":NotoSansLimbu-Regular.ttf",
+        ":NotoSansLinearA-Regular.otf",
+        ":NotoSansLinearB-Regular.ttf",
+        ":NotoSansLisu-Regular.ttf",
+        ":NotoSansLycian-Regular.ttf",
+        ":NotoSansLydian-Regular.ttf",
+        ":NotoSansMalayalam-VF.ttf",
+        ":NotoSansMalayalamUI-VF.ttf",
+        ":NotoSansMandaic-Regular.ttf",
+        ":NotoSansManichaean-Regular.otf",
+        ":NotoSansMarchen-Regular.otf",
+        ":NotoSansMasaramGondi-Regular.otf",
+        ":NotoSansMedefaidrin-VF.ttf",
+        ":NotoSansMeeteiMayek-Regular.ttf",
+        ":NotoSansMeroitic-Regular.otf",
+        ":NotoSansMiao-Regular.otf",
+        ":NotoSansModi-Regular.ttf",
+        ":NotoSansMongolian-Regular.ttf",
+        ":NotoSansMro-Regular.otf",
+        ":NotoSansMultani-Regular.otf",
+        ":NotoSansMyanmar-Bold.otf",
+        ":NotoSansMyanmar-Medium.otf",
+        ":NotoSansMyanmar-Regular.otf",
+        ":NotoSansMyanmarUI-Bold.otf",
+        ":NotoSansMyanmarUI-Medium.otf",
+        ":NotoSansMyanmarUI-Regular.otf",
+        ":NotoSansNKo-Regular.ttf",
+        ":NotoSansNabataean-Regular.otf",
+        ":NotoSansNewTaiLue-Regular.ttf",
+        ":NotoSansNewa-Regular.otf",
+        ":NotoSansOgham-Regular.ttf",
+        ":NotoSansOlChiki-Regular.ttf",
+        ":NotoSansOldItalic-Regular.ttf",
+        ":NotoSansOldNorthArabian-Regular.otf",
+        ":NotoSansOldPermic-Regular.otf",
+        ":NotoSansOldPersian-Regular.ttf",
+        ":NotoSansOldSouthArabian-Regular.ttf",
+        ":NotoSansOldTurkic-Regular.ttf",
+        ":NotoSansOriya-Bold.ttf",
+        ":NotoSansOriya-Regular.ttf",
+        ":NotoSansOriyaUI-Bold.ttf",
+        ":NotoSansOriyaUI-Regular.ttf",
+        ":NotoSansOsage-Regular.ttf",
+        ":NotoSansOsmanya-Regular.ttf",
+        ":NotoSansPahawhHmong-Regular.otf",
+        ":NotoSansPalmyrene-Regular.otf",
+        ":NotoSansPauCinHau-Regular.otf",
+        ":NotoSansPhagsPa-Regular.ttf",
+        ":NotoSansPhoenician-Regular.ttf",
+        ":NotoSansRejang-Regular.ttf",
+        ":NotoSansRunic-Regular.ttf",
+        ":NotoSansSamaritan-Regular.ttf",
+        ":NotoSansSaurashtra-Regular.ttf",
+        ":NotoSansSharada-Regular.otf",
+        ":NotoSansShavian-Regular.ttf",
+        ":NotoSansSinhala-VF.ttf",
+        ":NotoSansSinhalaUI-VF.ttf",
+        ":NotoSansSoraSompeng-Regular.otf",
+        ":NotoSansSoyombo-VF.ttf",
+        ":NotoSansSundanese-Regular.ttf",
+        ":NotoSansSylotiNagri-Regular.ttf",
+        ":NotoSansSymbols-Regular-Subsetted.ttf",
+        ":NotoSansSymbols-Regular-Subsetted2.ttf",
+        ":NotoSansSyriacEastern-Regular.ttf",
+        ":NotoSansSyriacEstrangela-Regular.ttf",
+        ":NotoSansSyriacWestern-Regular.ttf",
+        ":NotoSansTagalog-Regular.ttf",
+        ":NotoSansTagbanwa-Regular.ttf",
+        ":NotoSansTaiLe-Regular.ttf",
+        ":NotoSansTaiTham-Regular.ttf",
+        ":NotoSansTaiViet-Regular.ttf",
+        ":NotoSansTakri-VF.ttf",
+        ":NotoSansTamil-VF.ttf",
+        ":NotoSansTamilUI-VF.ttf",
+        ":NotoSansTelugu-VF.ttf",
+        ":NotoSansTeluguUI-VF.ttf",
+        ":NotoSansThaana-Bold.ttf",
+        ":NotoSansThaana-Regular.ttf",
+        ":NotoSansThai-Bold.ttf",
+        ":NotoSansThai-Regular.ttf",
+        ":NotoSansThaiUI-Bold.ttf",
+        ":NotoSansThaiUI-Regular.ttf",
+        ":NotoSansTifinagh-Regular.otf",
+        ":NotoSansUgaritic-Regular.ttf",
+        ":NotoSansVai-Regular.ttf",
+        ":NotoSansWancho-Regular.otf",
+        ":NotoSansWarangCiti-Regular.otf",
+        ":NotoSansYi-Regular.ttf",
+        ":NotoSerif-Bold.ttf",
+        ":NotoSerif-BoldItalic.ttf",
+        ":NotoSerif-Italic.ttf",
+        ":NotoSerif-Regular.ttf",
+        ":NotoSerifArmenian-VF.ttf",
+        ":NotoSerifBengali-VF.ttf",
+        ":NotoSerifCJK-Regular.ttc",
+        ":NotoSerifDevanagari-VF.ttf",
+        ":NotoSerifDogra-Regular.ttf",
+        ":NotoSerifEthiopic-VF.ttf",
+        ":NotoSerifGeorgian-VF.ttf",
+        ":NotoSerifGujarati-VF.ttf",
+        ":NotoSerifGurmukhi-VF.ttf",
+        ":NotoSerifHebrew-Bold.ttf",
+        ":NotoSerifHebrew-Regular.ttf",
+        ":NotoSerifHentaigana.ttf",
+        ":NotoSerifKannada-VF.ttf",
+        ":NotoSerifKhmer-Bold.otf",
+        ":NotoSerifKhmer-Regular.otf",
+        ":NotoSerifLao-Bold.ttf",
+        ":NotoSerifLao-Regular.ttf",
+        ":NotoSerifMalayalam-VF.ttf",
+        ":NotoSerifMyanmar-Bold.otf",
+        ":NotoSerifMyanmar-Regular.otf",
+        ":NotoSerifNyiakengPuachueHmong-VF.ttf",
+        ":NotoSerifSinhala-VF.ttf",
+        ":NotoSerifTamil-VF.ttf",
+        ":NotoSerifTelugu-VF.ttf",
+        ":NotoSerifThai-Bold.ttf",
+        ":NotoSerifThai-Regular.ttf",
+        ":NotoSerifTibetan-VF.ttf",
+        ":NotoSerifYezidi-VF.ttf",
+        ":Roboto-Regular.ttf",
+        ":RobotoFlex-Regular.ttf",
+        ":RobotoStatic-Regular.ttf",
+        ":SourceSansPro-Bold.ttf",
+        ":SourceSansPro-BoldItalic.ttf",
+        ":SourceSansPro-Italic.ttf",
+        ":SourceSansPro-Regular.ttf",
+        ":SourceSansPro-SemiBold.ttf",
+        ":SourceSansPro-SemiBoldItalic.ttf",
+    ],
+    device_first_srcs: [
+        ":font_fallback.xml",
+        ":fonts.xml",
+    ],
+    visibility: ["//visibility:private"],
+}
+
 // JARs in "ravenwood-runtime" are set to the classpath, sorted alphabetically.
 // Rename some of the dependencies to make sure they're included in the intended order.
 
@@ -386,13 +619,8 @@
 
 android_ravenwood_libgroup {
     name: "ravenwood-runtime",
-    data: [
-        ":system-build.prop",
-        ":framework-res",
-        ":ravenwood-empty-res",
-        ":framework-platform-compat-config",
-        ":services-platform-compat-config",
-    ],
+    data: [":ravenwood-data"],
+    fonts: [":ravenwood-fonts"],
     libs: [
         "100-framework-minus-apex.ravenwood",
         "200-kxml2-android",
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 9002e40..0f16352 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -165,6 +165,17 @@
         RavenwoodSystemProperties.initialize(RAVENWOOD_BUILD_PROP);
         setSystemProperties(null);
 
+        // Do this after loading RAVENWOOD_NATIVE_RUNTIME_NAME (which backs Os.setenv()),
+        // before loadFrameworkNativeCode() (which uses $ANDROID_LOG_TAGS).
+        if (RAVENWOOD_VERBOSE_LOGGING) {
+            RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
+            try {
+                Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
+            } catch (ErrnoException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
         // Make sure libandroid_runtime is loaded.
         RavenwoodNativeLoader.loadFrameworkNativeCode();
 
@@ -175,15 +186,6 @@
         Objects.requireNonNull(Build.TYPE);
         Objects.requireNonNull(Build.VERSION.SDK);
 
-        if (RAVENWOOD_VERBOSE_LOGGING) {
-            RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
-            try {
-                Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
-            } catch (ErrnoException e) {
-                // Shouldn't happen.
-            }
-        }
-
         System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
         // This will let AndroidJUnit4 use the original runner.
         System.setProperty("android.junit.runner",
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 3ff0848..2a3c26e 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -173,6 +173,24 @@
     throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
 }
 
+static void maybeRedirectLog() {
+    auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
+    if (ravenwoodLogOut == NULL) {
+        return;
+    }
+    ALOGI("RAVENWOOD_LOG_OUT set. Redirecting output to %s", ravenwoodLogOut);
+
+    // Redirect stdin / stdout to /dev/tty.
+    int ttyFd = open(ravenwoodLogOut, O_WRONLY);
+    if (ttyFd == -1) {
+        ALOGW("$RAVENWOOD_LOG_OUT is set to %s, but failed to open: %s ", ravenwoodLogOut,
+                strerror(errno));
+        return;
+    }
+    dup2(ttyFd, 1);
+    dup2(ttyFd, 2);
+}
+
 // ---- Registration ----
 
 extern void register_android_system_OsConstants(JNIEnv* env);
@@ -192,6 +210,8 @@
 };
 
 extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+    maybeRedirectLog();
+
     ALOGI("%s: JNI_OnLoad", __FILE__);
 
     JNIEnv* env = GetJNIEnvOrDie(vm);
diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp
index ce0033d..4895a1a 100644
--- a/ravenwood/tests/bivalenttest/Android.bp
+++ b/ravenwood/tests/bivalenttest/Android.bp
@@ -43,9 +43,12 @@
 
         // To make sure it won't cause VerifyError (b/324063814)
         "platformprotosnano",
+
+        "com.android.internal.os.flags-aconfig-java",
     ],
     srcs: [
         "test/**/*.java",
+        "test/**/*.kt",
     ],
     jni_libs: [
         "libravenwoodbivalenttest_jni",
@@ -58,10 +61,12 @@
     // TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture
     exclude_srcs: [
         "test/**/ravenizer/*.java",
+        "test/**/ravenizer/*.kt",
     ],
     static_libs: [
         "junit",
         "truth",
+        "flag-junit",
         "ravenwood-junit",
     ],
     test_suites: [
diff --git a/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
new file mode 100644
index 0000000..fd6d6fb
--- /dev/null
+++ b/ravenwood/tests/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/aconfig/RavenwoodAconfigFlagTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.ravenwoodtest.bivalenttest.aconfig
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.internal.os.Flags
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigSimpleReadTests {
+    @Test
+    fun testFalseFlags() {
+        assertFalse(Flags.ravenwoodFlagRo1())
+        assertFalse(Flags.ravenwoodFlagRw1())
+    }
+
+    @Test
+    @Ignore // TODO: Enable this test after rolling out the "2" flags.
+    fun testTrueFlags() {
+        assertTrue(Flags.ravenwoodFlagRo2())
+        assertTrue(Flags.ravenwoodFlagRw2())
+    }
+}
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigCheckFlagsRuleTests {
+    @Rule
+    @JvmField
+    val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_RAVENWOOD_FLAG_RO_1)
+    fun testRequireFlagsEnabledRo() {
+        fail("This test shouldn't be executed")
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_RAVENWOOD_FLAG_RW_1)
+    fun testRequireFlagsEnabledRw() {
+        fail("This test shouldn't be executed")
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RO_2)
+    @Ignore // TODO: Enable this test after rolling out the "2" flags.
+    fun testRequireFlagsDisabledRo() {
+        fail("This test shouldn't be executed")
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_RAVENWOOD_FLAG_RW_2)
+    @Ignore // TODO: Enable this test after rolling out the "2" flags.
+    fun testRequireFlagsDisabledRw() {
+        fail("This test shouldn't be executed")
+    }
+}
+
+@RunWith(AndroidJUnit4::class)
+class RavenwoodAconfigSetFlagsRuleWithDefaultTests {
+    @Rule
+    @JvmField
+    val setFlagsRule = SetFlagsRule()
+
+    @Test
+    @EnableFlags(Flags.FLAG_RAVENWOOD_FLAG_RO_1)
+    fun testSetRoFlag() {
+        assertTrue(Flags.ravenwoodFlagRo1())
+        assertFalse(Flags.ravenwoodFlagRw1())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RAVENWOOD_FLAG_RW_1)
+    fun testSetRwFlag() {
+        assertFalse(Flags.ravenwoodFlagRo1())
+        assertTrue(Flags.ravenwoodFlagRw1())
+    }
+}
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index a26fe66..70c1d78 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -2,6 +2,9 @@
 
 com.android.internal.ravenwood.*
 
+com.android.server.FgThread
+com.android.server.ServiceThread
+
 com.android.internal.display.BrightnessSynchronizer
 com.android.internal.util.ArrayUtils
 com.android.internal.logging.MetricsLogger
diff --git a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index a02082d..f47aaba 100644
--- a/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -327,6 +327,10 @@
     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
 }
 
+fun ClassNode.isAbstract(): Boolean {
+    return (this.access and Opcodes.ACC_ABSTRACT) != 0
+}
+
 fun MethodNode.isSynthetic(): Boolean {
     return (this.access and Opcodes.ACC_SYNTHETIC) != 0
 }
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
index 32dcbe5..a0e5599 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -46,7 +46,7 @@
     var enableValidation: SetOnce<Boolean> = SetOnce(true),
 
     /** Whether the validation failure is fatal or not. */
-    var fatalValidation: SetOnce<Boolean> = SetOnce(false),
+    var fatalValidation: SetOnce<Boolean> = SetOnce(true),
 
     /** Whether to remove mockito and dexmaker classes. */
     var stripMockito: SetOnce<Boolean> = SetOnce(false),
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
index 27092d2..8ec0932 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Validator.kt
@@ -16,10 +16,12 @@
 package com.android.platform.test.ravenwood.ravenizer
 
 import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.isAbstract
 import com.android.hoststubgen.asm.startsWithAny
 import com.android.hoststubgen.asm.toHumanReadableClassName
 import com.android.hoststubgen.log
 import org.objectweb.asm.tree.ClassNode
+import java.util.regex.Pattern
 
 fun validateClasses(classes: ClassNodes): Boolean {
     var allOk = true
@@ -41,25 +43,35 @@
     }
     var allOk = true
 
+    log.i("Checking ${cn.name.toHumanReadableClassName()}")
+
     // See if there's any class that extends a legacy base class.
     // But ignore the base classes in android.test.
-    if (!cn.name.startsWithAny("android/test/")) {
-        allOk = checkSuperClass(cn, cn, classes) && allOk
+    if (!cn.isAbstract() && !cn.name.startsWith("android/test/")
+        && !isAllowListedLegacyTest(cn)
+        ) {
+        allOk = checkSuperClassForJunit3(cn, cn, classes) && allOk
     }
     return allOk
 }
 
-fun checkSuperClass(targetClass: ClassNode, currentClass: ClassNode, classes: ClassNodes): Boolean {
+fun checkSuperClassForJunit3(
+    targetClass: ClassNode,
+    currentClass: ClassNode,
+    classes: ClassNodes,
+): Boolean {
     if (currentClass.superName == null || currentClass.superName == "java/lang/Object") {
         return true // No parent class
     }
+    // Make sure the class doesn't extend a junit3 TestCase class.
     if (currentClass.superName.isLegacyTestBaseClass()) {
         log.e("Error: Class ${targetClass.name.toHumanReadableClassName()} extends"
-                + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}.")
+                + " a legacy test class ${currentClass.superName.toHumanReadableClassName()}"
+                + ", which is not supported on Ravenwood. Please migrate to Junit4 syntax.")
         return false
     }
     classes.findClass(currentClass.superName)?.let {
-        return checkSuperClass(targetClass, it, classes)
+        return checkSuperClassForJunit3(targetClass, it, classes)
     }
     // Super class not found.
     // log.w("Class ${currentClass.superName} not found.")
@@ -73,9 +85,64 @@
     return this.startsWithAny(
         "junit/framework/TestCase",
 
-        // In case the test doesn't statically include JUnit, we need
+        // In case the test doesn't statically include JUnit, we need the following.
         "android/test/AndroidTestCase",
         "android/test/InstrumentationTestCase",
         "android/test/InstrumentationTestSuite",
     )
 }
+
+private val allowListedLegacyTests = setOf(
+// List of existing test classes that use the JUnit3 syntax. We exempt them for now, but
+// will reject any more of them.
+//
+// Note, we want internal class names, but for convenience, we use '.'s and '%'s here
+// and replace them later. (a '$' would be parsed as a string template.)
+    *"""
+android.util.proto.cts.DebuggingTest
+android.util.proto.cts.EncodedBufferTest
+android.util.proto.cts.ProtoOutputStreamBoolTest
+android.util.proto.cts.ProtoOutputStreamBytesTest
+android.util.proto.cts.ProtoOutputStreamDoubleTest
+android.util.proto.cts.ProtoOutputStreamEnumTest
+android.util.proto.cts.ProtoOutputStreamFixed32Test
+android.util.proto.cts.ProtoOutputStreamFixed64Test
+android.util.proto.cts.ProtoOutputStreamFloatTest
+android.util.proto.cts.ProtoOutputStreamInt32Test
+android.util.proto.cts.ProtoOutputStreamInt64Test
+android.util.proto.cts.ProtoOutputStreamObjectTest
+android.util.proto.cts.ProtoOutputStreamSFixed32Test
+android.util.proto.cts.ProtoOutputStreamSFixed64Test
+android.util.proto.cts.ProtoOutputStreamSInt32Test
+android.util.proto.cts.ProtoOutputStreamSInt64Test
+android.util.proto.cts.ProtoOutputStreamStringTest
+android.util.proto.cts.ProtoOutputStreamSwitchedWriteTest
+android.util.proto.cts.ProtoOutputStreamTagTest
+android.util.proto.cts.ProtoOutputStreamUInt32Test
+android.util.proto.cts.ProtoOutputStreamUInt64Test
+
+android.os.cts.BadParcelableExceptionTest
+android.os.cts.DeadObjectExceptionTest
+android.os.cts.ParcelFormatExceptionTest
+android.os.cts.PatternMatcherTest
+android.os.cts.RemoteExceptionTest
+
+android.os.storage.StorageManagerBaseTest
+android.os.storage.StorageManagerIntegrationTest
+android.util.LogTest%PerformanceTest
+
+com.android.server.power.stats.BatteryStatsCounterTest
+com.android.server.power.stats.BatteryStatsDualTimerTest
+com.android.server.power.stats.BatteryStatsDurationTimerTest
+com.android.server.power.stats.BatteryStatsSamplingTimerTest
+com.android.server.power.stats.BatteryStatsStopwatchTimerTest
+com.android.server.power.stats.BatteryStatsTimeBaseTest
+com.android.server.power.stats.BatteryStatsTimerTest
+
+    """.trim().replace('%', '$').replace('.', '/')
+        .split(Pattern.compile("""\s+""")).toTypedArray()
+)
+
+private fun isAllowListedLegacyTest(targetClass: ClassNode): Boolean {
+    return allowListedLegacyTests.contains(targetClass.name)
+}
\ No newline at end of file
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index 89f14b0..268e564 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -86,7 +86,6 @@
     private final Context mContext;
     private final Map<String, Object> mLocks = new WeakHashMap<>();
 
-
     public AppFunctionManagerServiceImpl(@NonNull Context context) {
         this(
                 context,
@@ -201,7 +200,7 @@
         if (mCallerValidator.isUserOrganizationManaged(targetUser)) {
             safeExecuteAppFunctionCallback.onResult(
                     ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                            ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                             "Cannot run on a device with a device owner or from the managed"
                                     + " profile.",
                             /* extras= */ null));
@@ -256,7 +255,7 @@
                             if (serviceIntent == null) {
                                 safeExecuteAppFunctionCallback.onResult(
                                         ExecuteAppFunctionResponse.newFailure(
-                                                ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                                                ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                                                 "Cannot find the target service.",
                                                 /* extras= */ null));
                                 return;
@@ -449,7 +448,7 @@
             Slog.e(TAG, "Failed to bind to the AppFunctionService");
             safeExecuteAppFunctionCallback.onResult(
                     ExecuteAppFunctionResponse.newFailure(
-                            ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR,
+                            ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR,
                             "Failed to bind the AppFunctionService.",
                             /* extras= */ null));
         }
@@ -464,7 +463,7 @@
         if (e instanceof CompletionException) {
             e = e.getCause();
         }
-        int resultCode = ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
+        int resultCode = ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
         if (e instanceof AppSearchException appSearchException) {
             resultCode =
                     mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
@@ -486,13 +485,13 @@
 
         switch (resultCode) {
             case AppSearchResult.RESULT_NOT_FOUND:
-                return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT;
+                return ExecuteAppFunctionResponse.RESULT_FUNCTION_NOT_FOUND;
             case AppSearchResult.RESULT_INVALID_ARGUMENT:
             case AppSearchResult.RESULT_INTERNAL_ERROR:
             case AppSearchResult.RESULT_SECURITY_ERROR:
                 // fall-through
         }
-        return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR;
+        return ExecuteAppFunctionResponse.RESULT_SYSTEM_ERROR;
     }
 
     private void registerAppSearchObserver(@NonNull TargetUser user) {
@@ -543,12 +542,13 @@
                                     });
         }
     }
+
     /**
      * Retrieves the lock object associated with the given package name.
      *
-     * This method returns the lock object from the {@code mLocks} map if it exists.
-     * If no lock is found for the given package name, a new lock object is created,
-     * stored in the map, and returned.
+     * <p>This method returns the lock object from the {@code mLocks} map if it exists. If no lock
+     * is found for the given package name, a new lock object is created, stored in the map, and
+     * returned.
      */
     @VisibleForTesting
     @NonNull
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index f9abd85..68ff972 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -1469,9 +1469,7 @@
         mSecurityPolicy.enforceCallFromPackage(callingPackage);
 
         // Check that if a cross-profile binding is attempted, it is allowed.
-        // Cross-profile binding is also allowed if the caller has interact across users permission.
-        if (!mSecurityPolicy.isEnabledGroupProfile(providerProfileId)
-                && !mSecurityPolicy.hasCallerInteractAcrossUsersPermission()) {
+        if (!mSecurityPolicy.isEnabledGroupProfile(providerProfileId)) {
             return false;
         }
 
@@ -2440,10 +2438,8 @@
             Slog.i(TAG, "getInstalledProvidersForProfiles() " + userId);
         }
 
-        // Ensure the profile is in the group and enabled, or that the caller has permission to
-        // interact across users.
-        if (!mSecurityPolicy.isEnabledGroupProfile(profileId)
-                && !mSecurityPolicy.hasCallerInteractAcrossUsersPermission()) {
+        // Ensure the profile is in the group and enabled.
+        if (!mSecurityPolicy.isEnabledGroupProfile(profileId)) {
             return null;
         }
 
@@ -5235,11 +5231,14 @@
                 return true;
             }
             final int userId = UserHandle.getUserId(uid);
-            if ((widget.host.getUserId() == userId || (widget.provider != null
-                    && widget.provider.getUserId() == userId))
+            if ((widget.host.getUserId() == userId
+                    || (widget.provider != null && widget.provider.getUserId() == userId)
+                    || hasCallerInteractAcrossUsersPermission())
                     && callerHasPermission(android.Manifest.permission.BIND_APPWIDGET)) {
-                // Apps that run in the same user as either the host or the provider and
-                // have the bind widget permission have access to the widget.
+                // Access to the widget requires the app to:
+                // - Run in the same user as the host or provider, or have permission to interact
+                //   across users
+                // - Have bind widget permission
                 return true;
             }
             if (DEBUG) {
@@ -5260,16 +5259,12 @@
          * The provider is accessible by the caller if any of the following is true:
          * - The provider belongs to the caller
          * - The provider belongs to a profile of the caller and is allowlisted
-         * - The caller has permission to interact across users
          */
         public boolean canAccessProvider(String packageName, int profileId) {
             final int callerId = UserHandle.getCallingUserId();
             if (profileId == callerId) {
                 return true;
             }
-            if (hasCallerInteractAcrossUsersPermission()) {
-                return true;
-            }
             final int parentId = getProfileParent(profileId);
             if (parentId != callerId) {
                 return false;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a960015..51034d2 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -31,13 +31,16 @@
 
 import static com.android.internal.util.CollectionUtils.any;
 import static com.android.internal.util.Preconditions.checkState;
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
+import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
 import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
 import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
 
 import static java.util.Objects.requireNonNull;
+import static java.util.concurrent.TimeUnit.MINUTES;
 
 import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
@@ -66,22 +69,31 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.net.MacAddress;
+import android.net.NetworkPolicyManager;
 import android.os.Binder;
+import android.os.Environment;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PowerExemptionManager;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.permission.flags.Flags;
+import android.util.ArraySet;
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
+import com.android.internal.app.IAppOpsService;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.server.FgThread;
@@ -102,27 +114,35 @@
 import com.android.server.companion.devicepresence.ObservableUuid;
 import com.android.server.companion.devicepresence.ObservableUuidStore;
 import com.android.server.companion.transport.CompanionTransportManager;
+import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
+import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collection;
 import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.Set;
 
 @SuppressLint("LongLogTag")
 public class CompanionDeviceManagerService extends SystemService {
     private static final String TAG = "CDM_CompanionDeviceManagerService";
 
     private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
+
+    private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
+    private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
     private static final int MAX_CN_LENGTH = 500;
 
+    private final ActivityTaskManagerInternal mAtmInternal;
+    private final ActivityManagerInternal mAmInternal;
+    private final IAppOpsService mAppOpsManager;
+    private final PowerExemptionManager mPowerExemptionManager;
+    private final PackageManagerInternal mPackageManagerInternal;
+
     private final AssociationStore mAssociationStore;
     private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
     private final ObservableUuidStore mObservableUuidStore;
-
-    private final CompanionExemptionProcessor mCompanionExemptionProcessor;
     private final AssociationRequestsProcessor mAssociationRequestsProcessor;
     private final SystemDataTransferProcessor mSystemDataTransferProcessor;
     private final BackupRestoreProcessor mBackupRestoreProcessor;
@@ -136,15 +156,12 @@
         super(context);
 
         final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
-        final PowerExemptionManager powerExemptionManager = context.getSystemService(
-                PowerExemptionManager.class);
-        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
-        final ActivityTaskManagerInternal atmInternal = LocalServices.getService(
-                ActivityTaskManagerInternal.class);
-        final ActivityManagerInternal amInternal = LocalServices.getService(
-                ActivityManagerInternal.class);
-        final PackageManagerInternal packageManagerInternal = LocalServices.getService(
-                PackageManagerInternal.class);
+        mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
+        mAppOpsManager = IAppOpsService.Stub.asInterface(
+                ServiceManager.getService(Context.APP_OPS_SERVICE));
+        mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
+        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         final UserManager userManager = context.getSystemService(UserManager.class);
         final PowerManagerInternal powerManagerInternal = LocalServices.getService(
                 PowerManagerInternal.class);
@@ -156,29 +173,25 @@
 
         // Init processors
         mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
-                packageManagerInternal, mAssociationStore);
-        mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal,
+                mPackageManagerInternal, mAssociationStore);
+        mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal,
                 mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
                 mAssociationRequestsProcessor);
 
         mCompanionAppBinder = new CompanionAppBinder(context);
 
-        mCompanionExemptionProcessor = new CompanionExemptionProcessor(context,
-                powerExemptionManager, appOpsManager, packageManagerInternal, atmInternal,
-                amInternal, mAssociationStore);
-
         mDevicePresenceProcessor = new DevicePresenceProcessor(context,
                 mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
-                powerManagerInternal, mCompanionExemptionProcessor);
+                powerManagerInternal);
 
         mTransportManager = new CompanionTransportManager(context, mAssociationStore);
 
         mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
-                mAssociationStore, packageManagerInternal, mDevicePresenceProcessor,
+                mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
                 mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);
 
         mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
-                packageManagerInternal, mAssociationStore,
+                mPackageManagerInternal, mAssociationStore,
                 mSystemDataTransferRequestStore, mTransportManager);
 
         // TODO(b/279663946): move context sync to a dedicated system service
@@ -189,6 +202,7 @@
     public void onStart() {
         // Init association stores
         mAssociationStore.refreshCache();
+        mAssociationStore.registerLocalListener(mAssociationStoreChangeListener);
 
         // Init UUID store
         mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
@@ -226,11 +240,11 @@
 
         if (associations.isEmpty()) return;
 
-        mCompanionExemptionProcessor.updateAtm(userId, associations);
+        updateAtm(userId, associations);
 
-        try (ExecutorService executor = Executors.newSingleThreadExecutor()) {
-            executor.execute(mCompanionExemptionProcessor::updateAutoRevokeExemptions);
-        }
+        BackgroundThread.getHandler().sendMessageDelayed(
+                obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this),
+                MINUTES.toMillis(10));
     }
 
     @Override
@@ -248,12 +262,9 @@
         if (!associationsForPackage.isEmpty()) {
             Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=["
                     + packageName + "]. Cleaning up CDM data...");
-
-            for (AssociationInfo association : associationsForPackage) {
-                mDisassociationProcessor.disassociate(association.getId());
-            }
-
-            mCompanionAppBinder.onPackagesChanged(userId, packageName);
+        }
+        for (AssociationInfo association : associationsForPackage) {
+            mDisassociationProcessor.disassociate(association.getId());
         }
 
         // Clear observable UUIDs for the package.
@@ -262,16 +273,19 @@
         for (ObservableUuid uuid : uuidsTobeObserved) {
             mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
         }
+
+        mCompanionAppBinder.onPackagesChanged(userId);
     }
 
     private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
-        final List<AssociationInfo> associations =
+        final List<AssociationInfo> associationsForPackage =
                 mAssociationStore.getAssociationsByPackage(userId, packageName);
-        if (!associations.isEmpty()) {
-            mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
-
-            mCompanionAppBinder.onPackagesChanged(userId, packageName);
+        for (AssociationInfo association : associationsForPackage) {
+            updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+                    association.getPackageName());
         }
+
+        mCompanionAppBinder.onPackagesChanged(userId);
     }
 
     private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -751,6 +765,130 @@
         }
     }
 
+    /**
+     * Update special access for the association's package
+     */
+    public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
+        final PackageInfo packageInfo =
+                getPackageInfo(getContext(), userId, packageName);
+
+        Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
+    }
+
+    private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
+        if (packageInfo == null) {
+            return;
+        }
+
+        if (containsEither(packageInfo.requestedPermissions,
+                android.Manifest.permission.RUN_IN_BACKGROUND,
+                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
+            mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
+        } else {
+            try {
+                mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
+            } catch (UnsupportedOperationException e) {
+                Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
+                        + " whitelist. It might due to the package is whitelisted by the system.");
+            }
+        }
+
+        NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
+        try {
+            if (containsEither(packageInfo.requestedPermissions,
+                    android.Manifest.permission.USE_DATA_IN_BACKGROUND,
+                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) {
+                networkPolicyManager.addUidPolicy(
+                        packageInfo.applicationInfo.uid,
+                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+            } else {
+                networkPolicyManager.removeUidPolicy(
+                        packageInfo.applicationInfo.uid,
+                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+            }
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, e.getMessage());
+        }
+
+        exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
+    }
+
+    private void exemptFromAutoRevoke(String packageName, int uid) {
+        try {
+            mAppOpsManager.setMode(
+                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+                    uid,
+                    packageName,
+                    AppOpsManager.MODE_IGNORED);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e);
+        }
+    }
+
+    private void updateAtm(int userId, List<AssociationInfo> associations) {
+        final Set<Integer> companionAppUids = new ArraySet<>();
+        for (AssociationInfo association : associations) {
+            final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
+                    0, userId);
+            if (uid >= 0) {
+                companionAppUids.add(uid);
+            }
+        }
+        if (mAtmInternal != null) {
+            mAtmInternal.setCompanionAppUids(userId, companionAppUids);
+        }
+        if (mAmInternal != null) {
+            // Make a copy of the set and send it to ActivityManager.
+            mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
+        }
+    }
+
+    private void maybeGrantAutoRevokeExemptions() {
+        Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
+
+        PackageManager pm = getContext().getPackageManager();
+        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
+            SharedPreferences pref = getContext().getSharedPreferences(
+                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
+                    Context.MODE_PRIVATE);
+            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
+                continue;
+            }
+
+            try {
+                final List<AssociationInfo> associations =
+                        mAssociationStore.getActiveAssociationsByUser(userId);
+                for (AssociationInfo a : associations) {
+                    try {
+                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
+                        exemptFromAutoRevoke(a.getPackageName(), uid);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
+                    }
+                }
+            } finally {
+                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
+            }
+        }
+    }
+
+    private final AssociationStore.OnChangeListener mAssociationStoreChangeListener =
+            new AssociationStore.OnChangeListener() {
+                @Override
+                public void onAssociationChanged(int changeType, AssociationInfo association) {
+                    Slog.d(TAG, "onAssociationChanged changeType=[" + changeType
+                            + "], association=[" + association);
+
+                    final int userId = association.getUserId();
+                    final List<AssociationInfo> updatedAssociations =
+                            mAssociationStore.getActiveAssociationsByUser(userId);
+
+                    updateAtm(userId, updatedAssociations);
+                    updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
+                            association.getPackageName());
+                }
+            };
+
     private final PackageMonitor mPackageMonitor = new PackageMonitor() {
         @Override
         public void onPackageRemoved(String packageName, int uid) {
@@ -773,6 +911,10 @@
         }
     };
 
+    private static <T> boolean containsEither(T[] array, T a, T b) {
+        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
+    }
+
     private class LocalService implements CompanionDeviceManagerServiceInternal {
 
         @Override
diff --git a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
deleted file mode 100644
index 4969ffbf..0000000
--- a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
+++ /dev/null
@@ -1,219 +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.companion;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_IGNORED;
-
-import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
-
-import android.annotation.SuppressLint;
-import android.app.ActivityManagerInternal;
-import android.app.AppOpsManager;
-import android.companion.AssociationInfo;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.net.NetworkPolicyManager;
-import android.os.Binder;
-import android.os.Environment;
-import android.os.PowerExemptionManager;
-import android.util.ArraySet;
-import android.util.Slog;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.server.LocalServices;
-import com.android.server.companion.association.AssociationStore;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.wm.ActivityTaskManagerInternal;
-
-import java.io.File;
-import java.util.List;
-import java.util.Set;
-
-@SuppressLint("LongLogTag")
-public class CompanionExemptionProcessor {
-
-    private static final String TAG = "CDM_CompanionExemptionProcessor";
-
-    private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
-    private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
-
-    private final Context mContext;
-    private final PowerExemptionManager mPowerExemptionManager;
-    private final AppOpsManager mAppOpsManager;
-    private final PackageManagerInternal mPackageManager;
-    private final ActivityTaskManagerInternal mAtmInternal;
-    private final ActivityManagerInternal mAmInternal;
-    private final AssociationStore mAssociationStore;
-
-    public CompanionExemptionProcessor(Context context, PowerExemptionManager powerExemptionManager,
-            AppOpsManager appOpsManager, PackageManagerInternal packageManager,
-            ActivityTaskManagerInternal atmInternal, ActivityManagerInternal amInternal,
-            AssociationStore associationStore) {
-        mContext = context;
-        mPowerExemptionManager = powerExemptionManager;
-        mAppOpsManager = appOpsManager;
-        mPackageManager = packageManager;
-        mAtmInternal = atmInternal;
-        mAmInternal = amInternal;
-        mAssociationStore = associationStore;
-
-        mAssociationStore.registerLocalListener(new AssociationStore.OnChangeListener() {
-            @Override
-            public void onAssociationChanged(int changeType, AssociationInfo association) {
-                final int userId = association.getUserId();
-                final List<AssociationInfo> updatedAssociations =
-                        mAssociationStore.getActiveAssociationsByUser(userId);
-
-                updateAtm(userId, updatedAssociations);
-            }
-        });
-    }
-
-    /**
-     * Update ActivityManager and ActivityTaskManager exemptions
-     */
-    public void updateAtm(int userId, List<AssociationInfo> associations) {
-        final Set<Integer> companionAppUids = new ArraySet<>();
-        for (AssociationInfo association : associations) {
-            int uid = mPackageManager.getPackageUid(association.getPackageName(), 0, userId);
-            if (uid >= 0) {
-                companionAppUids.add(uid);
-            }
-        }
-        if (mAtmInternal != null) {
-            mAtmInternal.setCompanionAppUids(userId, companionAppUids);
-        }
-        if (mAmInternal != null) {
-            // Make a copy of the set and send it to ActivityManager.
-            mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
-        }
-    }
-
-    /**
-     * Update special access for the association's package
-     */
-    public void exemptPackage(int userId, String packageName, boolean hasPresentDevices) {
-        Slog.i(TAG, "Exempting package [" + packageName + "]...");
-
-        final PackageInfo packageInfo = getPackageInfo(mContext, userId, packageName);
-
-        Binder.withCleanCallingIdentity(
-                () -> exemptPackageAsSystem(userId, packageInfo, hasPresentDevices));
-    }
-
-    @SuppressLint("MissingPermission")
-    private void exemptPackageAsSystem(int userId, PackageInfo packageInfo,
-            boolean hasPresentDevices) {
-        if (packageInfo == null) {
-            return;
-        }
-
-        // If the app has run-in-bg permission and present devices, add it to power saver allowlist.
-        if (containsEither(packageInfo.requestedPermissions,
-                android.Manifest.permission.RUN_IN_BACKGROUND,
-                android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)
-                && hasPresentDevices) {
-            mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
-        } else {
-            try {
-                mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
-            } catch (UnsupportedOperationException e) {
-                Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
-                        + " allowlist. It might be due to the package being allowlisted by the"
-                        + " system.");
-            }
-        }
-
-        // If the app has run-in-bg permission and present device, allow metered network use.
-        NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(mContext);
-        try {
-            if (containsEither(packageInfo.requestedPermissions,
-                    android.Manifest.permission.USE_DATA_IN_BACKGROUND,
-                    android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)
-                    && hasPresentDevices) {
-                networkPolicyManager.addUidPolicy(
-                        packageInfo.applicationInfo.uid,
-                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
-            } else {
-                networkPolicyManager.removeUidPolicy(
-                        packageInfo.applicationInfo.uid,
-                        NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
-            }
-        } catch (IllegalArgumentException e) {
-            Slog.e(TAG, e.getMessage());
-        }
-
-        updateAutoRevokeExemption(packageInfo.packageName, packageInfo.applicationInfo.uid,
-                !mAssociationStore.getActiveAssociationsByPackage(userId,
-                        packageInfo.packageName).isEmpty());
-    }
-
-    /**
-     * Update auto revoke exemptions.
-     * If the app has any association, exempt it from permission auto revoke.
-     */
-    public void updateAutoRevokeExemptions() {
-        Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
-
-        PackageManager pm = mContext.getPackageManager();
-        for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
-            SharedPreferences pref = mContext.getSharedPreferences(
-                    new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
-                    Context.MODE_PRIVATE);
-            if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
-                continue;
-            }
-
-            try {
-                final List<AssociationInfo> associations =
-                        mAssociationStore.getActiveAssociationsByUser(userId);
-                for (AssociationInfo a : associations) {
-                    try {
-                        int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
-                        updateAutoRevokeExemption(a.getPackageName(), uid, true);
-                    } catch (PackageManager.NameNotFoundException e) {
-                        Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
-                    }
-                }
-            } finally {
-                pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
-            }
-        }
-    }
-
-    @SuppressLint("MissingPermission")
-    private void updateAutoRevokeExemption(String packageName, int uid, boolean hasAssociations) {
-        try {
-            mAppOpsManager.setMode(
-                    AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
-                    uid,
-                    packageName,
-                    hasAssociations ? MODE_IGNORED : MODE_ALLOWED);
-        } catch (Exception e) {
-            Slog.e(TAG, "Error while granting auto revoke exemption for " + packageName, e);
-        }
-    }
-
-    private <T> boolean containsEither(T[] array, T a, T b) {
-        return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
-    }
-
-}
diff --git a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
index 1eb6d13f..60f4688 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
@@ -95,10 +95,7 @@
     /**
      * On package changed.
      */
-    public void onPackagesChanged(@UserIdInt int userId, String packageName) {
-        // TODO: We shouldn't need to clean up the whole user registry. We only need to remove the
-        //       package. I will do it in a separate change since it's not appropriate to use
-        //       PerUser anymore.
+    public void onPackagesChanged(@UserIdInt int userId) {
         mCompanionServicesRegister.invalidate(userId);
     }
 
diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index 7b4dd7d..a374d27 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -57,7 +57,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.CollectionUtils;
-import com.android.server.companion.CompanionExemptionProcessor;
 import com.android.server.companion.association.AssociationStore;
 
 import java.io.PrintWriter;
@@ -102,8 +101,6 @@
     private final PowerManagerInternal mPowerManagerInternal;
     @NonNull
     private final UserManager mUserManager;
-    @NonNull
-    private final CompanionExemptionProcessor mCompanionExemptionProcessor;
 
     // NOTE: Same association may appear in more than one of the following sets at the same time.
     // (E.g. self-managed devices that have MAC addresses, could be reported as present by their
@@ -114,7 +111,7 @@
     @NonNull
     private final Set<Integer> mNearbyBleDevices = new HashSet<>();
     @NonNull
-    private final Set<Integer> mConnectedSelfManagedDevices = new HashSet<>();
+    private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
     @NonNull
     private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
     @NonNull
@@ -149,8 +146,7 @@
             @NonNull UserManager userManager,
             @NonNull AssociationStore associationStore,
             @NonNull ObservableUuidStore observableUuidStore,
-            @NonNull PowerManagerInternal powerManagerInternal,
-            @NonNull CompanionExemptionProcessor companionExemptionProcessor) {
+            @NonNull PowerManagerInternal powerManagerInternal) {
         mContext = context;
         mCompanionAppBinder = companionAppBinder;
         mAssociationStore = associationStore;
@@ -160,7 +156,6 @@
                 mObservableUuidStore, this);
         mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this);
         mPowerManagerInternal = powerManagerInternal;
-        mCompanionExemptionProcessor = companionExemptionProcessor;
     }
 
     /** Initialize {@link DevicePresenceProcessor} */
@@ -409,7 +404,7 @@
      * nearby (for "self-managed" associations).
      */
     public boolean isDevicePresent(int associationId) {
-        return mConnectedSelfManagedDevices.contains(associationId)
+        return mReportedSelfManagedDevices.contains(associationId)
                 || mConnectedBtDevices.contains(associationId)
                 || mNearbyBleDevices.contains(associationId)
                 || mSimulated.contains(associationId);
@@ -456,7 +451,7 @@
      * notifyDeviceAppeared()}
      */
     public void onSelfManagedDeviceConnected(int associationId) {
-        onDevicePresenceEvent(mConnectedSelfManagedDevices,
+        onDevicePresenceEvent(mReportedSelfManagedDevices,
                 associationId, EVENT_SELF_MANAGED_APPEARED);
     }
 
@@ -472,7 +467,7 @@
      * notifyDeviceDisappeared()}
      */
     public void onSelfManagedDeviceDisconnected(int associationId) {
-        onDevicePresenceEvent(mConnectedSelfManagedDevices,
+        onDevicePresenceEvent(mReportedSelfManagedDevices,
                 associationId, EVENT_SELF_MANAGED_DISAPPEARED);
     }
 
@@ -480,7 +475,7 @@
      * Marks a "self-managed" device as disconnected when binderDied.
      */
     public void onSelfManagedDeviceReporterBinderDied(int associationId) {
-        onDevicePresenceEvent(mConnectedSelfManagedDevices,
+        onDevicePresenceEvent(mReportedSelfManagedDevices,
                 associationId, EVENT_SELF_MANAGED_DISAPPEARED);
     }
 
@@ -688,7 +683,6 @@
 
                 if (association.shouldBindWhenPresent()) {
                     bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
-                    mCompanionExemptionProcessor.exemptPackage(userId, packageName, true);
                 } else {
                     return;
                 }
@@ -721,7 +715,6 @@
                 // Check if there are other devices associated to the app that are present.
                 if (!shouldBindPackage(userId, packageName)) {
                     mCompanionAppBinder.unbindCompanionApp(userId, packageName);
-                    mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
                 }
                 break;
             default:
@@ -947,7 +940,7 @@
 
         mConnectedBtDevices.remove(id);
         mNearbyBleDevices.remove(id);
-        mConnectedSelfManagedDevices.remove(id);
+        mReportedSelfManagedDevices.remove(id);
         mSimulated.remove(id);
         synchronized (mBtDisconnectedDevices) {
             mBtDisconnectedDevices.remove(id);
@@ -1107,7 +1100,7 @@
         out.append("Companion Device Present: ");
         if (mConnectedBtDevices.isEmpty()
                 && mNearbyBleDevices.isEmpty()
-                && mConnectedSelfManagedDevices.isEmpty()) {
+                && mReportedSelfManagedDevices.isEmpty()) {
             out.append("<empty>\n");
             return;
         } else {
@@ -1137,11 +1130,11 @@
         }
 
         out.append("  Self-Reported Devices: ");
-        if (mConnectedSelfManagedDevices.isEmpty()) {
+        if (mReportedSelfManagedDevices.isEmpty()) {
             out.append("<empty>\n");
         } else {
             out.append("\n");
-            for (int associationId : mConnectedSelfManagedDevices) {
+            for (int associationId : mReportedSelfManagedDevices) {
                 AssociationInfo a = mAssociationStore.getAssociationById(associationId);
                 out.append("    ").append(a.toShortString()).append('\n');
             }
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 363807d..72a9a2d 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -102,6 +102,7 @@
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IOnSubscriptionsChangedListener;
 import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.ISatelliteStateChangeListener;
 import com.android.internal.telephony.ITelephonyRegistry;
 import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.telephony.flags.Flags;
@@ -126,6 +127,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
 
 /**
@@ -164,6 +166,7 @@
         IOnSubscriptionsChangedListener onOpportunisticSubscriptionsChangedListenerCallback;
         ICarrierPrivilegesCallback carrierPrivilegesCallback;
         ICarrierConfigChangeListener carrierConfigChangeListener;
+        ISatelliteStateChangeListener satelliteStateChangeListener;
 
         int callerUid;
         int callerPid;
@@ -196,6 +199,10 @@
             return carrierConfigChangeListener != null;
         }
 
+        boolean matchSatelliteStateChangeListener() {
+            return satelliteStateChangeListener != null;
+        }
+
         boolean canReadCallLog() {
             try {
                 return TelephonyPermissions.checkReadCallLog(
@@ -215,6 +222,7 @@
                     + onOpportunisticSubscriptionsChangedListenerCallback
                     + " carrierPrivilegesCallback=" + carrierPrivilegesCallback
                     + " carrierConfigChangeListener=" + carrierConfigChangeListener
+                    + " satelliteStateChangeListener=" + satelliteStateChangeListener
                     + " subId=" + subId + " phoneId=" + phoneId + " events=" + eventList + "}";
         }
     }
@@ -433,6 +441,10 @@
 
     private List<IntArray> mCarrierRoamingNtnAvailableServices;
 
+    // Local cache to check if Satellite Modem is enabled
+    private AtomicBoolean mIsSatelliteEnabled;
+    private AtomicBoolean mWasSatelliteEnabledNotified;
+
     /**
      * Per-phone map of precise data connection state. The key of the map is the pair of transport
      * type and APN setting. This is the cache to prevent redundant callbacks to the listeners.
@@ -871,6 +883,9 @@
         mCarrierRoamingNtnMode = new boolean[numPhones];
         mCarrierRoamingNtnEligible = new boolean[numPhones];
         mCarrierRoamingNtnAvailableServices = new ArrayList<>();
+        mIsSatelliteEnabled = new AtomicBoolean();
+        mWasSatelliteEnabledNotified = new AtomicBoolean();
+
 
         for (int i = 0; i < numPhones; i++) {
             mCallState[i] =  TelephonyManager.CALL_STATE_IDLE;
@@ -3425,6 +3440,94 @@
     }
 
     @Override
+    public void addSatelliteStateChangeListener(@NonNull ISatelliteStateChangeListener listener,
+            @NonNull String pkg, @Nullable String featureId) {
+        final int callerUserId = UserHandle.getCallingUserId();
+        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+        enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(pkg, featureId,
+                "addSatelliteStateChangeListener");
+        if (VDBG) {
+            log("addSatelliteStateChangeListener pkg=" + pii(pkg)
+                    + " uid=" + Binder.getCallingUid()
+                    + " myUserId=" + UserHandle.myUserId() + " callerUerId" + callerUserId
+                    + " listener=" + listener + " listener.asBinder=" + listener.asBinder());
+        }
+
+        synchronized (mRecords) {
+            final IBinder b = listener.asBinder();
+            boolean doesLimitApply = doesLimitApplyForListeners(Binder.getCallingUid(),
+                    Process.myUid());
+            Record r = add(b, Binder.getCallingUid(), Binder.getCallingPid(), doesLimitApply);
+
+            if (r == null) {
+                loge("addSatelliteStateChangeListener: can not create Record instance!");
+                return;
+            }
+
+            r.context = mContext;
+            r.satelliteStateChangeListener = listener;
+            r.callingPackage = pkg;
+            r.callingFeatureId = featureId;
+            r.callerUid = Binder.getCallingUid();
+            r.callerPid = Binder.getCallingPid();
+            r.eventList = new ArraySet<>();
+            if (DBG) {
+                log("addSatelliteStateChangeListener:  Register r=" + r);
+            }
+
+            // Always notify registrants on registration if it has been notified before
+            if (mWasSatelliteEnabledNotified.get() && r.matchSatelliteStateChangeListener()) {
+                try {
+                    r.satelliteStateChangeListener.onSatelliteEnabledStateChanged(
+                            mIsSatelliteEnabled.get());
+                } catch (RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void removeSatelliteStateChangeListener(@NonNull ISatelliteStateChangeListener listener,
+            @NonNull String pkg) {
+        if (DBG) log("removeSatelliteStateChangeListener listener=" + listener + ", pkg=" + pkg);
+        mAppOps.checkPackage(Binder.getCallingUid(), pkg);
+        enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(pkg, null,
+                "removeSatelliteStateChangeListener");
+        remove(listener.asBinder());
+    }
+
+    @Override
+    public void notifySatelliteStateChanged(boolean isEnabled) {
+        if (!checkNotifyPermission("notifySatelliteStateChanged")) {
+            loge("notifySatelliteStateChanged: Caller has no notify permission!");
+            return;
+        }
+        if (VDBG) {
+            log("notifySatelliteStateChanged: isEnabled=" + isEnabled);
+        }
+
+        mWasSatelliteEnabledNotified.set(true);
+        mIsSatelliteEnabled.set(isEnabled);
+
+        synchronized (mRecords) {
+            mRemoveList.clear();
+            for (Record r : mRecords) {
+                // Listeners are "global", neither per-slot nor per-sub, so no idMatch check here
+                if (!r.matchSatelliteStateChangeListener()) {
+                    continue;
+                }
+                try {
+                    r.satelliteStateChangeListener.onSatelliteEnabledStateChanged(isEnabled);
+                } catch (RemoteException re) {
+                    mRemoveList.add(r.binder);
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    @Override
     public void notifyMediaQualityStatusChanged(int phoneId, int subId, MediaQualityStatus status) {
         if (!checkNotifyPermission("notifyMediaQualityStatusChanged()")) {
             return;
@@ -4622,4 +4725,32 @@
         if (packageNames.isEmpty() || Build.IS_DEBUGGABLE) return packageNames.toString();
         return "[***, size=" + packageNames.size() + "]";
     }
+
+    /**
+     * The method enforces the calling package at least has READ_BASIC_PHONE_STATE permission.
+     * That is, calling package either has READ_PRIVILEGED_PHONE_STATE, READ_PHONE_STATE or Carrier
+     * Privileges on ANY active subscription, or has READ_BASIC_PHONE_STATE permission.
+     */
+    private void enforceCallingOrSelfAtLeastReadBasicPhoneStatePermission(String pkgName,
+            String featureId, String message) {
+        // Check if calling app has READ_PHONE_STATE on ANY active subscription
+        boolean hasReadPhoneState = false;
+        SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
+        if (sm != null) {
+            for (int subId : sm.getActiveSubscriptionIdList()) {
+                if (TelephonyPermissions.checkCallingOrSelfReadPhoneStateNoThrow(mContext, subId,
+                        pkgName, featureId, message)) {
+                    hasReadPhoneState = true;
+                    break;
+                }
+            }
+        }
+
+        // If yes, pass. If not, then enforce READ_BASIC_PHONE_STATE permission
+        if (!hasReadPhoneState) {
+            mContext.enforceCallingOrSelfPermission(
+                    Manifest.permission.READ_BASIC_PHONE_STATE,
+                    message);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2a9cb43..1c3569d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -60,7 +60,6 @@
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_INSTRUMENTATION;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_PERSISTENT;
 import static android.app.ProcessMemoryState.HOSTING_COMPONENT_TYPE_SYSTEM;
-import static android.content.Intent.isPreventIntentRedirectEnabled;
 import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
 import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
 import static android.content.pm.PackageManager.MATCH_ALL;
@@ -131,6 +130,7 @@
 import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
 import static android.provider.Settings.Global.DEBUG_APP;
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
+import static android.security.Flags.preventIntentRedirect;
 import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
 import static android.view.Display.INVALID_DISPLAY;
 
@@ -14301,6 +14301,10 @@
         mBroadcastController.unregisterReceiver(receiver);
     }
 
+    public List<IntentFilter> getRegisteredIntentFilters(IIntentReceiver receiver) {
+        return mBroadcastController.getRegisteredIntentFilters(receiver);
+    }
+
     @GuardedBy("this")
     final int broadcastIntentLocked(ProcessRecord callerApp,
             String callerPackage, String callerFeatureId, Intent intent, String resolvedType,
@@ -19281,7 +19285,7 @@
      * @hide
      */
     public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
-        if (!isPreventIntentRedirectEnabled()) return;
+        if (!preventIntentRedirect()) return;
 
         if (intent == null || intent.getExtraIntentKeys() == null) return;
         for (String key : intent.getExtraIntentKeys()) {
diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java
index 71ddf05..b0f88071 100644
--- a/services/core/java/com/android/server/am/BroadcastController.java
+++ b/services/core/java/com/android/server/am/BroadcastController.java
@@ -679,6 +679,21 @@
         }
     }
 
+    List<IntentFilter> getRegisteredIntentFilters(IIntentReceiver receiver) {
+        synchronized (mService) {
+            final ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+            if (rl == null) {
+                return null;
+            }
+            final ArrayList<IntentFilter> filters = new ArrayList<>();
+            final int count = rl.size();
+            for (int i = 0; i < count; ++i) {
+                filters.add(rl.get(i));
+            }
+            return filters;
+        }
+    }
+
     int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
             Intent intent, String resolvedType, IIntentReceiver resultTo,
             int resultCode, String resultData, Bundle resultExtras,
diff --git a/services/core/java/com/android/server/am/OWNERS b/services/core/java/com/android/server/am/OWNERS
index 61079fc..c1d5597 100644
--- a/services/core/java/com/android/server/am/OWNERS
+++ b/services/core/java/com/android/server/am/OWNERS
@@ -49,6 +49,7 @@
 
 # Broadcasts
 per-file Broadcast* = file:/BROADCASTS_OWNERS
+per-file broadcasts_flags.aconfig = file:/BROADCASTS_OWNERS
 
 # Permissions & Packages
 per-file *Permission* = patb@google.com
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index de3e2c9..08632fe 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -114,6 +114,7 @@
 import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
 import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
 import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -696,7 +697,7 @@
             // In case the app goes from non-cached to cached but it doesn't have other reachable
             // processes, its adj could be still unknown as of now, assign one.
             processes.add(app);
-            assignCachedAdjIfNecessary(processes);
+            applyLruAdjust(processes);
             applyOomAdjLSP(app, false, mInjector.getUptimeMillis(),
                     mInjector.getElapsedRealtimeMillis(), oomAdjReason);
         }
@@ -1086,7 +1087,7 @@
         }
         mProcessesInCycle.clear();
 
-        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        applyLruAdjust(mProcessList.getLruProcessesLOSP());
 
         postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
 
@@ -1148,8 +1149,9 @@
     }
 
     @GuardedBy({"mService", "mProcLock"})
-    protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+    protected void applyLruAdjust(ArrayList<ProcessRecord> lruList) {
         final int numLru = lruList.size();
+        int nextPreviousAppAdj = PREVIOUS_APP_ADJ;
         if (mConstants.USE_TIERED_CACHED_ADJ) {
             final long now = mInjector.getUptimeMillis();
             int uiTargetAdj = 10;
@@ -1159,9 +1161,12 @@
                 ProcessRecord app = lruList.get(i);
                 final ProcessStateRecord state = app.mState;
                 final ProcessCachedOptimizerRecord opt = app.mOptRecord;
-                if (!app.isKilledByAm() && app.getThread() != null
-                        && (state.getCurAdj() >= UNKNOWN_ADJ
-                            || (state.hasShownUi() && state.getCurAdj() >= CACHED_APP_MIN_ADJ))) {
+                final int curAdj = state.getCurAdj();
+                if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+                    state.setCurAdj(nextPreviousAppAdj);
+                    nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+                } else if (!app.isKilledByAm() && app.getThread() != null && (curAdj >= UNKNOWN_ADJ
+                            || (state.hasShownUi() && curAdj >= CACHED_APP_MIN_ADJ))) {
                     final ProcessServiceRecord psr = app.mServices;
                     int targetAdj = CACHED_APP_MIN_ADJ;
 
@@ -1228,10 +1233,13 @@
             for (int i = numLru - 1; i >= 0; i--) {
                 ProcessRecord app = lruList.get(i);
                 final ProcessStateRecord state = app.mState;
-                // If we haven't yet assigned the final cached adj
-                // to the process, do that now.
-                if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
-                    >= UNKNOWN_ADJ) {
+                final int curAdj = state.getCurAdj();
+                if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+                    state.setCurAdj(nextPreviousAppAdj);
+                    nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+                } else if (!app.isKilledByAm() && app.getThread() != null
+                               && curAdj >= UNKNOWN_ADJ) {
+                    // If we haven't yet assigned the final cached adj to the process, do that now.
                     final ProcessServiceRecord psr = app.mServices;
                     switch (state.getCurProcState()) {
                         case PROCESS_STATE_LAST_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index e452c45..8b66055 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -54,6 +54,7 @@
 import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
 import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SERVICE_ADJ;
 import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
@@ -968,7 +969,7 @@
         mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
         computeConnectionsLSP();
 
-        assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+        applyLruAdjust(mProcessList.getLruProcessesLOSP());
         postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
     }
 
@@ -1049,20 +1050,24 @@
         // Now traverse and compute the connections of processes with changed importance.
         computeConnectionsLSP();
 
-        boolean unassignedAdj = false;
+        boolean needLruAdjust = false;
         for (int i = 0, size = reachables.size(); i < size; i++) {
             final ProcessStateRecord state = reachables.get(i).mState;
             state.setReachable(false);
             state.setCompletedAdjSeq(mAdjSeq);
-            if (state.getCurAdj() >= UNKNOWN_ADJ) {
-                unassignedAdj = true;
+            final int curAdj = state.getCurAdj();
+            // Processes assigned the PREV oomscore will have a laddered oomscore with respect to
+            // their positions in the LRU list. i.e. prev+0, prev+1, prev+2, etc.
+            final boolean isPrevApp = PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ;
+            if (curAdj >= UNKNOWN_ADJ || (Flags.oomadjusterPrevLaddering() && isPrevApp)) {
+                needLruAdjust = true;
             }
         }
 
         // If all processes have an assigned adj, no need to calculate and assign cached adjs.
-        if (unassignedAdj) {
+        if (needLruAdjust) {
             // TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
-            assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+            applyLruAdjust(mProcessList.getLruProcessesLOSP());
         }
 
         // Repopulate any uid record that may have changed.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cdb0188..f86474f 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -225,6 +225,7 @@
     // UI flow such as clicking on a URI in the e-mail app to view in the browser,
     // and then pressing back to return to e-mail.
     public static final int PREVIOUS_APP_ADJ = 700;
+    public static final int PREVIOUS_APP_MAX_ADJ = Flags.oomadjusterPrevLaddering() ? 799 : 700;
 
     // This is a process holding the home application -- we want to try
     // avoiding killing it, even if it would normally be in the background,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 7cfe829..8dc7c73 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -41,6 +41,7 @@
 import android.aconfigd.Aconfigd.StorageReturnMessages;
 import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
 import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
+import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
 
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
@@ -209,6 +210,7 @@
         "pixel_perf",
         "pixel_sensors",
         "pixel_system_sw_video",
+        "pixel_video_sw",
         "pixel_watch",
         "platform_compat",
         "platform_security",
@@ -525,14 +527,21 @@
      * @param proto
      * @param packageName the package of the flag
      * @param flagName the name of the flag
+     * @param immediate if true, clear immediately; otherwise, clear on next reboot
+     *
+     * @hide
      */
-    static void writeFlagOverrideRemovalRequest(
-        ProtoOutputStream proto, String packageName, String flagName) {
+    public static void writeFlagOverrideRemovalRequest(
+        ProtoOutputStream proto, String packageName, String flagName, boolean immediate) {
       long msgsToken = proto.start(StorageRequestMessages.MSGS);
       long msgToken = proto.start(StorageRequestMessage.REMOVE_LOCAL_OVERRIDE_MESSAGE);
       proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.PACKAGE_NAME, packageName);
       proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.FLAG_NAME, flagName);
       proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_ALL, false);
+      proto.write(StorageRequestMessage.RemoveLocalOverrideMessage.REMOVE_OVERRIDE_TYPE,
+          immediate
+              ? StorageRequestMessage.REMOVE_LOCAL_IMMEDIATE
+              : StorageRequestMessage.REMOVE_LOCAL_ON_REBOOT);
       proto.end(msgToken);
       proto.end(msgsToken);
     }
@@ -609,7 +618,11 @@
             String realFlagName = fullFlagName.substring(idx+1);
 
             if (Flags.syncLocalOverridesRemovalNewStorage() && flagValue == null) {
-              writeFlagOverrideRemovalRequest(requests, packageName, realFlagName);
+              if (supportClearLocalOverridesImmediately()) {
+                writeFlagOverrideRemovalRequest(requests, packageName, realFlagName, true);
+              } else {
+                writeFlagOverrideRemovalRequest(requests, packageName, realFlagName, false);
+              }
             } else {
               writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true);
             }
diff --git a/services/core/java/com/android/server/am/broadcasts_flags.aconfig b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
new file mode 100644
index 0000000..b1185d5
--- /dev/null
+++ b/services/core/java/com/android/server/am/broadcasts_flags.aconfig
@@ -0,0 +1,10 @@
+package: "com.android.server.am"
+container: "system"
+
+flag {
+    name: "restrict_priority_values"
+    namespace: "backstage_power"
+    description: "Restrict priority values defined by non-system apps"
+    is_fixed_read_only: true
+    bug: "369487976"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 8834820..7b4d6c7 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -235,14 +235,6 @@
 }
 
 flag {
-    name: "restrict_priority_values"
-    namespace: "backstage_power"
-    description: "Restrict priority values defined by non-system apps"
-    is_fixed_read_only: true
-    bug: "369487976"
-}
-
-flag {
     name: "unfreeze_bind_policy_fix"
     namespace: "backstage_power"
     description: "Make sure shouldNotFreeze state change correctly triggers updates."
@@ -250,4 +242,12 @@
     metadata {
         purpose: PURPOSE_BUGFIX
     }
+}
+
+flag {
+    name: "oomadjuster_prev_laddering"
+    namespace: "system_performance"
+    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
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 906e584..a3b20b9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -693,6 +693,8 @@
                 elapsed = System.currentTimeMillis() - start;
                 if (elapsed >= SET_COMMUNICATION_DEVICE_TIMEOUT_MS) {
                     Log.e(TAG, "Timeout waiting for communication device update.");
+                    // reset counter to avoid sticky out of sync condition
+                    mCommunicationDeviceUpdateCount = 0;
                     break;
                 }
             }
@@ -1342,8 +1344,8 @@
     }
 
     /*package*/ void postSetModeOwner(int mode, int pid, int uid, boolean signal) {
-        sendILMsgNoDelay(MSG_IL_SET_MODE_OWNER, SENDMSG_REPLACE,
-                signal ? 1 : 0, new AudioModeInfo(mode, pid, uid));
+        sendLMsgNoDelay(signal ? MSG_L_SET_MODE_OWNER_SIGNAL : MSG_L_SET_MODE_OWNER,
+                SENDMSG_REPLACE, new AudioModeInfo(mode, pid, uid));
     }
 
     /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) {
@@ -2026,7 +2028,8 @@
                         mBtHelper.setAvrcpAbsoluteVolumeIndex(msg.arg1);
                     }
                     break;
-                case MSG_IL_SET_MODE_OWNER:
+                case MSG_L_SET_MODE_OWNER:
+                case MSG_L_SET_MODE_OWNER_SIGNAL:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
                             mAudioModeOwner = (AudioModeInfo) msg.obj;
@@ -2037,7 +2040,7 @@
                             }
                         }
                     }
-                    if (msg.arg1 == 1 /*signal*/) {
+                    if (msg.what == MSG_L_SET_MODE_OWNER_SIGNAL) {
                         mAudioService.decrementAudioModeResetCount();
                     }
                     break;
@@ -2199,7 +2202,8 @@
     private static final int MSG_REPORT_NEW_ROUTES = 13;
     private static final int MSG_II_SET_HEARING_AID_VOLUME = 14;
     private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
-    private static final int MSG_IL_SET_MODE_OWNER = 16;
+    private static final int MSG_L_SET_MODE_OWNER = 16;
+    private static final int MSG_L_SET_MODE_OWNER_SIGNAL = 17;
 
     private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22;
     private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3dbe48a..0cf55bb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -457,7 +457,7 @@
     private static final int MSG_UPDATE_AUDIO_MODE = 36;
     private static final int MSG_RECORDING_CONFIG_CHANGE = 37;
     private static final int MSG_BT_DEV_CHANGED = 38;
-
+    private static final int MSG_UPDATE_AUDIO_MODE_SIGNAL = 39;
     private static final int MSG_DISPATCH_AUDIO_MODE = 40;
     private static final int MSG_ROUTING_UPDATED = 41;
     private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
@@ -493,7 +493,7 @@
     private static final int MSG_INIT_ADI_DEVICE_STATES = 103;
 
     private static final int MSG_INIT_INPUT_GAINS = 104;
-    private static final int MSG_SET_INPUT_GAIN_INDEX = 105;
+    private static final int MSG_APPLY_INPUT_GAIN_INDEX = 105;
     private static final int MSG_PERSIST_INPUT_GAIN_INDEX = 106;
 
     // end of messages handled under wakelock
@@ -1626,7 +1626,6 @@
                 new InputDeviceVolumeHelper(
                         mSettings,
                         mContentResolver,
-                        mSettingsLock,
                         System.INPUT_GAIN_INDEX_SETTINGS);
     }
 
@@ -4804,16 +4803,14 @@
     }
 
     static class UpdateAudioModeInfo {
-        UpdateAudioModeInfo(int mode, int pid, String packageName, boolean signal) {
+        UpdateAudioModeInfo(int mode, int pid, String packageName) {
             mMode = mode;
             mPid = pid;
             mPackageName = packageName;
-            mSignal = signal;
         }
         private final int mMode;
         private final int mPid;
         private final String mPackageName;
-        private final boolean mSignal;
 
         int getMode() {
             return mMode;
@@ -4824,9 +4821,6 @@
         String getPackageName() {
             return mPackageName;
         }
-        boolean getSignal() {
-            return mSignal;
-        }
     }
 
     void postUpdateAudioMode(int msgPolicy, int mode, int pid, String packageName,
@@ -4835,8 +4829,8 @@
             if (signal) {
                 mAudioModeResetCount++;
             }
-            sendMsg(mAudioHandler, MSG_UPDATE_AUDIO_MODE, msgPolicy, 0, 0,
-                new UpdateAudioModeInfo(mode, pid, packageName, signal), delay);
+            sendMsg(mAudioHandler, signal ? MSG_UPDATE_AUDIO_MODE_SIGNAL : MSG_UPDATE_AUDIO_MODE,
+                    msgPolicy, 0, 0, new UpdateAudioModeInfo(mode, pid, packageName), delay);
         }
     }
 
@@ -5809,7 +5803,7 @@
             // to persist).
             sendMsg(
                     mAudioHandler,
-                    MSG_SET_INPUT_GAIN_INDEX,
+                    MSG_APPLY_INPUT_GAIN_INDEX,
                     SENDMSG_QUEUE,
                     /*arg1*/ index,
                     /*arg2*/ 0,
@@ -5818,22 +5812,22 @@
         }
     }
 
-    private void setInputGainIndexInt(@NonNull AudioDeviceAttributes ada, int index) {
+    private void onApplyInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
         // TODO(b/364923030): call AudioSystem to apply input gain in native layer.
 
         // Post a persist input gain msg.
         sendMsg(
                 mAudioHandler,
                 MSG_PERSIST_INPUT_GAIN_INDEX,
-                SENDMSG_QUEUE,
-                /*arg1*/ index,
+                SENDMSG_REPLACE,
+                /*arg1*/ 0,
                 /*arg2*/ 0,
                 /*obj*/ ada,
                 PERSIST_DELAY);
     }
 
-    private void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
-        mInputDeviceVolumeHelper.persistInputGainIndex(ada, index);
+    private void onPersistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+        mInputDeviceVolumeHelper.persistInputGainIndex(ada);
     }
 
     /**
@@ -6654,6 +6648,9 @@
                 // connections not started by the application changing the mode when pid changes
                 mDeviceBroker.postSetModeOwner(mode, pid, uid, signal);
             } else {
+                // reset here to avoid sticky out of sync condition (would have been reset
+                // by AudioDeviceBroker processing MSG_L_SET_MODE_OWNER_SIGNAL message)
+                resetAudioModeResetCount();
                 Log.w(TAG, "onUpdateAudioMode: failed to set audio mode to: " + mode);
             }
         }
@@ -10215,12 +10212,12 @@
                     vgs.persistVolumeGroup(msg.arg1);
                     break;
 
-                case MSG_SET_INPUT_GAIN_INDEX:
-                    setInputGainIndexInt((AudioDeviceAttributes) msg.obj, msg.arg1);
+                case MSG_APPLY_INPUT_GAIN_INDEX:
+                    onApplyInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
                     break;
 
                 case MSG_PERSIST_INPUT_GAIN_INDEX:
-                    persistInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
+                    onPersistInputGainIndex((AudioDeviceAttributes) msg.obj);
                     break;
 
                 case MSG_PERSIST_RINGER_MODE:
@@ -10420,10 +10417,11 @@
                     break;
 
                 case MSG_UPDATE_AUDIO_MODE:
+                case MSG_UPDATE_AUDIO_MODE_SIGNAL:
                     synchronized (mDeviceBroker.mSetModeLock) {
                         UpdateAudioModeInfo info = (UpdateAudioModeInfo) msg.obj;
                         onUpdateAudioMode(info.getMode(), info.getPid(), info.getPackageName(),
-                                false /*force*/, info.getSignal());
+                                false /*force*/, msg.what == MSG_UPDATE_AUDIO_MODE_SIGNAL);
                     }
                     break;
 
@@ -11164,6 +11162,8 @@
                     elapsed = java.lang.System.currentTimeMillis() - start;
                     if (elapsed >= AUDIO_MODE_RESET_TIMEOUT_MS) {
                         Log.e(TAG, "Timeout waiting for audio mode reset");
+                        // reset count to avoid sticky out of sync state.
+                        resetAudioModeResetCount();
                         break;
                     }
                 }
@@ -11175,7 +11175,7 @@
         return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
     }
 
-    /** synchronization between setMode(NORMAL) and abandonAudioFocus() frmo Telecom */
+    /** synchronization between setMode(NORMAL) and abandonAudioFocus() from Telecom */
     private static final long AUDIO_MODE_RESET_TIMEOUT_MS = 3000;
 
     private final Object mAudioModeResetLock = new Object();
@@ -11194,6 +11194,13 @@
         }
     }
 
+    private void resetAudioModeResetCount() {
+        synchronized (mAudioModeResetLock) {
+            mAudioModeResetCount = 0;
+            mAudioModeResetLock.notify();
+        }
+    }
+
     /** see {@link AudioManager#abandonAudioFocusForTest(AudioFocusRequest, String)} */
     public int abandonAudioFocusForTest(IAudioFocusDispatcher fd, String clientId,
             AudioAttributes aa, String callingPackageName) {
diff --git a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
index d83dca6..d094629 100644
--- a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
+++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -43,7 +43,6 @@
 
     private final SettingsAdapter mSettings;
     private final ContentResolver mContentResolver;
-    private final Object mSettingsLock;
     private final String mInputGainIndexSettingsName;
 
     // A map between device internal type (e.g. AudioSystem.DEVICE_IN_BUILTIN_MIC) to its input gain
@@ -54,11 +53,9 @@
     InputDeviceVolumeHelper(
             SettingsAdapter settings,
             ContentResolver contentResolver,
-            Object settingsLock,
             String settingsName) {
         mSettings = settings;
         mContentResolver = contentResolver;
-        mSettingsLock = settingsLock;
         mInputGainIndexSettingsName = settingsName;
 
         IntArray internalDeviceTypes = new IntArray();
@@ -82,34 +79,27 @@
         readSettings();
     }
 
-    public void readSettings() {
+    private void readSettings() {
         synchronized (InputDeviceVolumeHelper.class) {
             for (int inputDeviceType : mSupportedDeviceTypes) {
                 // Retrieve current input gain for device. If no input gain stored for current
                 // device, use default input gain.
-                int index;
-                if (!hasValidSettingsName()) {
-                    index = INDEX_DEFAULT;
-                } else {
-                    String name = getSettingNameForDevice(inputDeviceType);
-                    index =
-                            mSettings.getSystemIntForUser(
-                                    mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
-                }
+                String name = getSettingNameForDevice(inputDeviceType);
+                int index = name == null
+                        ? INDEX_DEFAULT
+                        : mSettings.getSystemIntForUser(
+                                mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
 
                 mInputGainIndexMap.put(inputDeviceType, getValidIndex(index));
             }
         }
     }
 
-    public boolean hasValidSettingsName() {
-        return mInputGainIndexSettingsName != null && !mInputGainIndexSettingsName.isEmpty();
-    }
-
-    public @Nullable String getSettingNameForDevice(int inputDeviceType) {
-        if (!hasValidSettingsName()) {
+    private @Nullable String getSettingNameForDevice(int inputDeviceType) {
+        if (mInputGainIndexSettingsName == null || mInputGainIndexSettingsName.isEmpty()) {
             return null;
         }
+
         final String suffix = AudioSystem.getInputDeviceName(inputDeviceType);
         if (suffix.isEmpty()) {
             return mInputGainIndexSettingsName;
@@ -158,29 +148,27 @@
         ensureValidInputDeviceType(inputDeviceType);
 
         int oldIndex;
-        synchronized (mSettingsLock) {
-            synchronized (InputDeviceVolumeHelper.class) {
-                oldIndex = getInputGainIndex(ada);
-                index = getValidIndex(index);
+        synchronized (InputDeviceVolumeHelper.class) {
+            oldIndex = getInputGainIndex(ada);
+            index = getValidIndex(index);
 
-                if (oldIndex == index) {
-                    return false;
-                }
-
-                mInputGainIndexMap.put(inputDeviceType, index);
-                return true;
+            if (oldIndex == index) {
+                return false;
             }
+
+            mInputGainIndexMap.put(inputDeviceType, index);
+            return true;
         }
     }
 
-    public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
+    public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
         int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
-        ensureValidInputDeviceType(inputDeviceType);
-
-        if (hasValidSettingsName()) {
+        String name = getSettingNameForDevice(inputDeviceType);
+        if (name != null) {
+            int index = getInputGainIndex(ada);
             mSettings.putSystemIntForUser(
                     mContentResolver,
-                    getSettingNameForDevice(inputDeviceType),
+                    name,
                     index,
                     UserHandle.USER_CURRENT);
         }
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index a9ada29..e8fa417 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -146,7 +146,7 @@
         final List<Pair<BiometricSensor, Integer>> ineligibleSensors = new ArrayList<>();
 
         final int effectiveUserId;
-        if (Flags.privateSpaceBp()) {
+        if (Flags.effectiveUserBp()) {
             effectiveUserId = userManager.getCredentialOwnerProfile(userId);
         } else {
             effectiveUserId = userId;
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index f49608b..a9bdcce 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -438,3 +438,11 @@
     bug: "361433796"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_get_supported_refresh_rates"
+    namespace: "core_graphics"
+    description: "Flag to use the surfaceflinger rates for getSupportedRefreshRates"
+    bug: "365163968"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/display/state/DisplayStateController.java b/services/core/java/com/android/server/display/state/DisplayStateController.java
index dba6874..0b46e0f 100644
--- a/services/core/java/com/android/server/display/state/DisplayStateController.java
+++ b/services/core/java/com/android/server/display/state/DisplayStateController.java
@@ -61,7 +61,7 @@
         // We might override this below based on other factors.
         // Initialise brightness as invalid.
         int state;
-        int reason = Display.STATE_REASON_DEFAULT_POLICY;
+        int reason = displayPowerRequest.policyReason;
         switch (displayPowerRequest.policy) {
             case DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF:
                 state = Display.STATE_OFF;
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index ae31b33..f4bd402 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -17,10 +17,13 @@
 package com.android.server.input;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.hardware.input.InputGestureData;
 import android.hardware.input.InputManager;
 import android.util.IndentingPrintWriter;
 import android.util.SparseArray;
+import android.view.KeyEvent;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -40,6 +43,10 @@
 final class InputGestureManager {
     private static final String TAG = "InputGestureManager";
 
+    private static final int KEY_GESTURE_META_MASK =
+            KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
+                    | KeyEvent.META_META_ON;
+
     @GuardedBy("mCustomInputGestures")
     private final SparseArray<Map<InputGestureData.Trigger, InputGestureData>>
             mCustomInputGestures = new SparseArray<>();
@@ -96,6 +103,23 @@
         }
     }
 
+    @Nullable
+    public InputGestureData getCustomGestureForKeyEvent(@UserIdInt int userId, KeyEvent event) {
+        final int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+            return null;
+        }
+        synchronized (mCustomInputGestures) {
+            Map<InputGestureData.Trigger, InputGestureData> customGestures =
+                    mCustomInputGestures.get(userId);
+            if (customGestures == null) {
+                return null;
+            }
+            int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
+            return customGestures.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
+        }
+    }
+
     public void dump(IndentingPrintWriter ipw) {
         ipw.println("InputGestureManager:");
         ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index d43ce71..eefa15e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -64,7 +64,6 @@
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.InputDeviceIdentifier;
-import android.hardware.input.InputGestureData;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.InputSettings;
@@ -2313,6 +2312,12 @@
 
     // Native callback.
     @SuppressWarnings("unused")
+    private void notifyTouchpadThreeFingerTap() {
+        mKeyGestureController.handleTouchpadThreeFingerTap();
+    }
+
+    // Native callback.
+    @SuppressWarnings("unused")
     private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
         if (DEBUG) {
             Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues)
@@ -3022,6 +3027,7 @@
 
     private void handleCurrentUserChanged(@UserIdInt int userId) {
         mCurrentUserId = userId;
+        mKeyGestureController.setCurrentUserId(userId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index d1a6d3b..420db90 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -143,6 +143,10 @@
             observer.accept("just booted");
         }
 
+        // TODO(b/365063048): add an entry to mObservers that calls this instead, once we have a
+        //   setting that can be observed.
+        updateTouchpadThreeFingerTapShortcutEnabled();
+
         configureUserActivityPokeInterval();
     }
 
@@ -205,6 +209,11 @@
         mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
     }
 
+    private void updateTouchpadThreeFingerTapShortcutEnabled() {
+        mNative.setTouchpadThreeFingerTapShortcutEnabled(
+                InputSettings.useTouchpadThreeFingerTapShortcut(mContext));
+    }
+
     private void updateShowTouches() {
         mNative.setShowTouches(getBoolean(Settings.System.SHOW_TOUCHES, false));
     }
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 68eaf72..96ef070 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -25,6 +25,7 @@
 import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
 import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
 import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
+import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
 
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
@@ -39,6 +40,7 @@
 import android.database.ContentObserver;
 import android.hardware.input.AidlInputGestureData;
 import android.hardware.input.AidlKeyGestureEvent;
+import android.hardware.input.AppLaunchData;
 import android.hardware.input.IKeyGestureEventListener;
 import android.hardware.input.IKeyGestureHandler;
 import android.hardware.input.InputGestureData;
@@ -90,6 +92,9 @@
 
     // Maximum key gesture events that are tracked and will be available in input dump.
     private static final int MAX_TRACKED_EVENTS = 10;
+    private static final int SHORTCUT_META_MASK =
+            KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON
+                    | KeyEvent.META_SHIFT_ON;
 
     private static final int MSG_NOTIFY_KEY_GESTURE_EVENT = 1;
 
@@ -115,6 +120,10 @@
     private final KeyCombinationManager mKeyCombinationManager;
     private final SettingsObserver mSettingsObserver;
     private final InputGestureManager mInputGestureManager = new InputGestureManager();
+    private static final Object mUserLock = new Object();
+    @UserIdInt
+    @GuardedBy("mUserLock")
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
 
     // Pending actions
     private boolean mPendingMetaAction;
@@ -228,7 +237,7 @@
 
                         @Override
                         public void execute() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
                                     KeyGestureEvent.ACTION_GESTURE_START, 0);
@@ -236,7 +245,7 @@
 
                         @Override
                         public void cancel() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_POWER},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
                                     KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -256,14 +265,14 @@
 
                             @Override
                             public void execute() {
-                                handleKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
+                                handleMultiKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
                                                 KeyEvent.KEYCODE_STEM_PRIMARY},
                                         KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
                                         KeyGestureEvent.ACTION_GESTURE_START, 0);
                             }
                             @Override
                             public void cancel() {
-                                handleKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
+                                handleMultiKeyGesture(new int[]{KeyEvent.KEYCODE_POWER,
                                                 KeyEvent.KEYCODE_STEM_PRIMARY},
                                         KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
                                         KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -284,7 +293,7 @@
 
                     @Override
                     public void execute() {
-                        handleKeyGesture(
+                        handleMultiKeyGesture(
                                 new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP},
                                 KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
                                 KeyGestureEvent.ACTION_GESTURE_START, 0);
@@ -292,7 +301,7 @@
 
                     @Override
                     public void cancel() {
-                        handleKeyGesture(
+                        handleMultiKeyGesture(
                                 new int[]{KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP},
                                 KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -324,7 +333,7 @@
                         if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
                             return;
                         }
-                        handleKeyGesture(
+                        handleMultiKeyGesture(
                                 new int[]{KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER},
                                 gestureType, KeyGestureEvent.ACTION_GESTURE_START, 0);
                     }
@@ -334,7 +343,7 @@
                         if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
                             return;
                         }
-                        handleKeyGesture(
+                        handleMultiKeyGesture(
                                 new int[]{KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_POWER},
                                 gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE,
                                 KeyGestureEvent.FLAG_CANCELLED);
@@ -368,7 +377,7 @@
 
                         @Override
                         public void execute() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
                                     KeyGestureEvent.ACTION_GESTURE_START, 0);
@@ -376,7 +385,7 @@
 
                         @Override
                         public void cancel() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_TV_ACCESSIBILITY_SHORTCUT_CHORD,
                                     KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -405,14 +414,14 @@
 
                         @Override
                         public void execute() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
                                     KeyGestureEvent.ACTION_GESTURE_START, 0);
                         }
                         @Override
                         public void cancel() {
-                            handleKeyGesture(
+                            handleMultiKeyGesture(
                                     new int[]{KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER},
                                     KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
                                     KeyGestureEvent.ACTION_GESTURE_COMPLETE,
@@ -487,7 +496,7 @@
     private boolean interceptSystemKeysAndShortcuts(IBinder focusedToken, KeyEvent event) {
         final int keyCode = event.getKeyCode();
         final int repeatCount = event.getRepeatCount();
-        final int metaState = event.getMetaState();
+        final int metaState = event.getMetaState() & SHORTCUT_META_MASK;
         final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
         final boolean canceled = event.isCanceled();
         final int displayId = event.getDisplayId();
@@ -510,7 +519,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_RECENT_APPS:
@@ -518,7 +527,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
@@ -526,12 +535,13 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
                             KeyGestureEvent.ACTION_GESTURE_START, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 } else if (!down) {
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, canceled ? KeyGestureEvent.FLAG_CANCELLED : 0);
+                            focusedToken, canceled ? KeyGestureEvent.FLAG_CANCELLED : 0,
+                            /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_H:
@@ -540,7 +550,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_I:
@@ -548,7 +558,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_L:
@@ -556,7 +566,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_N:
@@ -566,13 +576,13 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     } else {
                         return handleKeyGesture(deviceId, new int[]{keyCode},
                                 KeyEvent.META_META_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -582,7 +592,7 @@
                             KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_T:
@@ -592,7 +602,7 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -604,7 +614,7 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -616,7 +626,7 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -628,7 +638,7 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -640,7 +650,7 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -652,7 +662,7 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 // fall through
@@ -661,7 +671,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_UP:
@@ -670,7 +680,7 @@
                             KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_DOWN:
@@ -679,7 +689,7 @@
                             KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_DPAD_LEFT:
@@ -689,19 +699,19 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     } else if (event.isAltPressed()) {
                         return handleKeyGesture(deviceId, new int[]{keyCode},
                                 KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     } else {
                         return handleKeyGesture(deviceId, new int[]{keyCode},
                                 KeyEvent.META_META_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -712,13 +722,13 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     } else if (event.isAltPressed()) {
                         return handleKeyGesture(deviceId, new int[]{keyCode},
                                 KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -729,8 +739,51 @@
                                 KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE,
-                                displayId,
-                                focusedToken, /* flags = */0);
+                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_LEFT_BRACKET:
+                if (enableTaskResizingKeyboardShortcuts()) {
+                    if (firstDown && event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_RIGHT_BRACKET:
+                if (enableTaskResizingKeyboardShortcuts()) {
+                    if (firstDown && event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_EQUALS:
+                if (enableTaskResizingKeyboardShortcuts()) {
+                    if (firstDown && event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_MINUS:
+                if (enableTaskResizingKeyboardShortcuts()) {
+                    if (firstDown && event.isAltPressed()) {
+                        return handleKeyGesture(deviceId, new int[]{keyCode},
+                                KeyEvent.META_ALT_ON,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+                                KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                                displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -739,7 +792,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_BRIGHTNESS_UP:
@@ -750,7 +803,7 @@
                                     ? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP
                                     : KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
@@ -758,7 +811,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
@@ -766,7 +819,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
@@ -775,7 +828,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_ALL_APPS:
@@ -783,7 +836,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
@@ -791,7 +844,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_SEARCH:
@@ -799,7 +852,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
 
                 }
                 break;
@@ -810,13 +863,13 @@
                                 new int[]{keyCode}, /* modifierState = */0,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
                         handleKeyGesture(deviceId,
                                 new int[]{keyCode}, /* modifierState = */0,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 return true;
@@ -826,7 +879,7 @@
                             event.isShiftPressed() ? KeyEvent.META_SHIFT_ON : 0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_CAPS_LOCK:
@@ -836,7 +889,8 @@
                     AidlKeyGestureEvent eventToNotify = createKeyGestureEvent(deviceId,
                             new int[]{keyCode}, metaState,
                             KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
-                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, /* flags = */0);
+                            KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, /* flags = */0,
+                            /* appLaunchData = */null);
                     Message msg = Message.obtain(mHandler, MSG_NOTIFY_KEY_GESTURE_EVENT,
                             eventToNotify);
                     mHandler.sendMessage(msg);
@@ -847,7 +901,7 @@
                     handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 return true;
             case KeyEvent.KEYCODE_META_LEFT:
@@ -868,7 +922,7 @@
                                         KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
 
                     } else if (mPendingMetaAction) {
                         mPendingMetaAction = false;
@@ -877,7 +931,7 @@
                                     /* modifierState = */0,
                                     KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                                     KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                    focusedToken, /* flags = */0);
+                                    focusedToken, /* flags = */0, /* appLaunchData = */null);
                         }
                     }
                 }
@@ -888,7 +942,7 @@
                         return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     } else if (!mPendingHideRecentSwitcher) {
                         final int shiftlessModifiers =
                                 event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
@@ -899,7 +953,7 @@
                                     KeyEvent.META_ALT_ON,
                                     KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
                                     KeyGestureEvent.ACTION_GESTURE_START, displayId,
-                                    focusedToken, /* flags = */0);
+                                    focusedToken, /* flags = */0, /* appLaunchData = */null);
                         }
                     }
                 }
@@ -920,7 +974,7 @@
                                 KeyEvent.META_ALT_ON,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
 
                     // Toggle Caps Lock on META-ALT.
@@ -930,7 +984,7 @@
                                         KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0,
                                 KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -949,6 +1003,22 @@
                         + " interceptKeyBeforeQueueing");
                 return true;
         }
+
+        if (firstDown) {
+            InputGestureData customGesture;
+            synchronized (mUserLock) {
+                customGesture = mInputGestureManager.getCustomGestureForKeyEvent(mCurrentUserId,
+                        event);
+            }
+            if (customGesture == null) {
+                return false;
+            }
+            return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+                    customGesture.getAction().keyGestureType(),
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+                    displayId, focusedToken, /* flags = */0,
+                    customGesture.getAction().appLaunchData());
+        }
         return false;
     }
 
@@ -971,7 +1041,7 @@
                                         ? KeyEvent.META_SHIFT_ON : 0),
                                 KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
                                 KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                                focusedToken, /* flags = */0);
+                                focusedToken, /* flags = */0, /* appLaunchData = */null);
                     }
                 }
                 break;
@@ -983,7 +1053,7 @@
                             KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
                             KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_SYSRQ:
@@ -991,7 +1061,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
             case KeyEvent.KEYCODE_ESCAPE:
@@ -999,7 +1069,7 @@
                     return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
                             KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
                             KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
-                            focusedToken, /* flags = */0);
+                            focusedToken, /* flags = */0, /* appLaunchData = */null);
                 }
                 break;
         }
@@ -1007,18 +1077,19 @@
         return false;
     }
 
-    private boolean handleKeyGesture(int[] keycodes,
+    private void handleMultiKeyGesture(int[] keycodes,
             @KeyGestureEvent.KeyGestureType int gestureType, int action, int flags) {
-        return handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, keycodes, /* modifierState= */0,
-                gestureType, action, Display.DEFAULT_DISPLAY, /* focusedToken = */null, flags);
+        handleKeyGesture(KeyCharacterMap.VIRTUAL_KEYBOARD, keycodes, /* modifierState= */0,
+                gestureType, action, Display.DEFAULT_DISPLAY, /* focusedToken = */null, flags,
+                /* appLaunchData = */null);
     }
 
     @VisibleForTesting
     boolean handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
             @KeyGestureEvent.KeyGestureType int gestureType, int action, int displayId,
-            @Nullable IBinder focusedToken, int flags) {
+            @Nullable IBinder focusedToken, int flags, @Nullable AppLaunchData appLaunchData) {
         return handleKeyGesture(createKeyGestureEvent(deviceId, keycodes,
-                modifierState, gestureType, action, displayId, flags), focusedToken);
+                modifierState, gestureType, action, displayId, flags, appLaunchData), focusedToken);
     }
 
     private boolean handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) {
@@ -1050,17 +1121,33 @@
         // TODO(b/358569822): Once we move the gesture detection logic to IMS, we ideally
         //  should not rely on PWM to tell us about the gesture start and end.
         AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState,
-                gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY, 0);
+                gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY,
+                /* flags = */0, /* appLaunchData = */null);
         mHandler.obtainMessage(MSG_NOTIFY_KEY_GESTURE_EVENT, event).sendToTarget();
     }
 
     public void handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
             @KeyGestureEvent.KeyGestureType int gestureType) {
         AidlKeyGestureEvent event = createKeyGestureEvent(deviceId, keycodes, modifierState,
-                gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY, 0);
+                gestureType, KeyGestureEvent.ACTION_GESTURE_COMPLETE, Display.DEFAULT_DISPLAY,
+                /* flags = */0, /* appLaunchData = */null);
         handleKeyGesture(event, null /*focusedToken*/);
     }
 
+    public void handleTouchpadThreeFingerTap() {
+        // TODO(b/365063048): trigger a custom shortcut based on the three-finger tap.
+        if (DEBUG) {
+            Slog.d(TAG, "Three-finger touchpad tap occurred");
+        }
+    }
+
+    @MainThread
+    public void setCurrentUserId(@UserIdInt int userId) {
+        synchronized (mUserLock) {
+            mCurrentUserId = userId;
+        }
+    }
+
     @MainThread
     private void notifyKeyGestureEvent(AidlKeyGestureEvent event) {
         InputDevice device = getInputDevice(event.deviceId);
@@ -1318,7 +1405,7 @@
 
     private AidlKeyGestureEvent createKeyGestureEvent(int deviceId, int[] keycodes,
             int modifierState, @KeyGestureEvent.KeyGestureType int gestureType, int action,
-            int displayId, int flags) {
+            int displayId, int flags, @Nullable AppLaunchData appLaunchData) {
         AidlKeyGestureEvent event = new AidlKeyGestureEvent();
         event.deviceId = deviceId;
         event.keycodes = keycodes;
@@ -1327,6 +1414,18 @@
         event.action = action;
         event.displayId = displayId;
         event.flags = flags;
+        if (appLaunchData != null) {
+            if (appLaunchData instanceof AppLaunchData.CategoryData categoryData) {
+                event.appLaunchCategory = categoryData.getCategory();
+            } else if (appLaunchData instanceof AppLaunchData.RoleData roleData) {
+                event.appLaunchRole = roleData.getRole();
+            } else if (appLaunchData instanceof AppLaunchData.ComponentData componentData) {
+                event.appLaunchPackageName = componentData.getPackageName();
+                event.appLaunchClassName = componentData.getClassName();
+            } else {
+                throw new IllegalArgumentException("AppLaunchData type is invalid!");
+            }
+        }
         return event;
     }
 
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 283fdea..8903c27 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -143,6 +143,8 @@
 
     void setTouchpadRightClickZoneEnabled(boolean enabled);
 
+    void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
     void setShowTouches(boolean enabled);
 
     void setNonInteractiveDisplays(int[] displayIds);
@@ -427,6 +429,9 @@
         public native void setTouchpadRightClickZoneEnabled(boolean enabled);
 
         @Override
+        public native void setTouchpadThreeFingerTapShortcutEnabled(boolean enabled);
+
+        @Override
         public native void setShowTouches(boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 8798a64..c314ab0 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -253,10 +253,10 @@
 
     private static final String MIGRATED_FRP2 = "migrated_frp2";
     private static final String MIGRATED_KEYSTORE_NS = "migrated_keystore_namespace";
-    private static final String MIGRATED_SP_CE_ONLY = "migrated_all_users_to_sp_and_bound_ce";
     private static final String MIGRATED_SP_FULL = "migrated_all_users_to_sp_and_bound_keys";
     private static final String MIGRATED_WEAVER_DISABLED_ON_UNSECURED_USERS =
             "migrated_weaver_disabled_on_unsecured_users";
+    // Note: some other migrated_* strings used to be used and may exist in the database already.
 
     // Duration that LockSettingsService will store the gatekeeper password for. This allows
     // multiple biometric enrollments without prompting the user to enter their password via
@@ -347,6 +347,8 @@
 
     private final StorageManagerInternal mStorageManagerInternal;
 
+    private final Object mGcWorkToken = new Object();
+
     // This class manages life cycle events for encrypted users on File Based Encryption (FBE)
     // devices. The most basic of these is to show/hide notifications about missing features until
     // the user unlocks the account and credential-encrypted storage is available.
@@ -1219,21 +1221,16 @@
                 Slog.i(TAG, "Synthetic password is already not protected by Weaver");
             }
         } else if (sp == null) {
-            Slogf.wtf(TAG, "Failed to unwrap synthetic password for unsecured user %d", userId);
-            return;
+            throw new IllegalStateException(
+                    "Failed to unwrap synthetic password for unsecured user " + userId);
         }
 
         // Call setCeStorageProtection(), to re-encrypt the CE key with the SP if it's currently
-        // encrypted by an empty secret.  Skip this if it was definitely already done as part of the
-        // upgrade to Android 14, since while setCeStorageProtection() is idempotent it does log
-        // some error messages when called again.  Do not skip this if
-        // config_disableWeaverOnUnsecuredUsers=true, since in that case we'd like to recover from
-        // the case where an earlier upgrade to Android 14 incorrectly skipped this step.
-        if (getString(MIGRATED_SP_CE_ONLY, null, 0) == null
-                || isWeaverDisabledOnUnsecuredUsers()) {
-            Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
-            setCeStorageProtection(userId, sp);
-        }
+        // encrypted by an empty secret.  If the CE key is already encrypted by the SP, then this is
+        // a no-op except for some log messages.
+        Slogf.i(TAG, "Encrypting CE key of user %d with synthetic password", userId);
+        setCeStorageProtection(userId, sp);
+
         Slogf.i(TAG, "Initializing Keystore super keys for user %d", userId);
         initKeystoreSuperKeys(userId, sp, /* allowExisting= */ true);
     }
@@ -3637,11 +3634,19 @@
      * release references to the argument.
      */
     private void scheduleGc() {
+        // Cancel any existing GC request first, so that GC requests don't pile up if lockscreen
+        // credential operations are happening very quickly, e.g. as sometimes happens during tests.
+        //
+        // This delays the already-requested GC, but that is fine in practice where lockscreen
+        // operations don't happen very quickly.  And the precise time that the sanitization happens
+        // isn't very important; doing it within a minute can be fine, for example.
+        mHandler.removeCallbacksAndMessages(mGcWorkToken);
+
         mHandler.postDelayed(() -> {
             System.gc();
             System.runFinalization();
             System.gc();
-        }, 2000);
+        }, mGcWorkToken, 2000);
     }
 
     private class DeviceProvisionedObserver extends ContentObserver {
diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java
index 25e1c94..6bc4098 100644
--- a/services/core/java/com/android/server/media/AudioManagerRouteController.java
+++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java
@@ -32,6 +32,7 @@
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.MediaRoute2Info;
+import android.media.audio.Flags;
 import android.media.audiopolicy.AudioProductStrategy;
 import android.os.Handler;
 import android.os.HandlerExecutor;
@@ -711,5 +712,13 @@
                         MediaRoute2Info.TYPE_DOCK,
                         /* defaultRouteId= */ "ROUTE_ID_DOCK_ANALOG",
                         /* nameResource= */ R.string.default_audio_route_name_dock_speakers));
+        if (Flags.enableMultichannelGroupDevice()) {
+            AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.put(
+                    AudioDeviceInfo.TYPE_MULTICHANNEL_GROUP,
+                    new SystemRouteInfo(
+                            MediaRoute2Info.TYPE_MULTICHANNEL_SPEAKER_GROUP,
+                            /* defaultRouteId= */ "ROUTE_ID_MULTICHANNEL_SPEAKER_GROUP",
+                            /* nameResource= */ R.string.default_audio_route_name_external_device));
+        }
     }
 }
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 40a27e8..a45ea1d 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -17,10 +17,20 @@
 package com.android.server.media.quality;
 
 import android.content.Context;
+import android.media.quality.AmbientBacklightSettings;
+import android.media.quality.IAmbientBacklightCallback;
 import android.media.quality.IMediaQualityManager;
+import android.media.quality.IPictureProfileCallback;
+import android.media.quality.ISoundProfileCallback;
+import android.media.quality.ParamCapability;
+import android.media.quality.PictureProfile;
+import android.media.quality.SoundProfile;
 
 import com.android.server.SystemService;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * This service manage picture profile and sound profile for TV setting. Also communicates with the
  * database to save, update the profiles.
@@ -43,5 +53,122 @@
 
     // TODO: Add additional APIs. b/373951081
     private final class BinderService extends IMediaQualityManager.Stub {
+        @Override
+        public PictureProfile createPictureProfile(PictureProfile pp) {
+            // TODO: implement
+            return pp;
+        }
+        @Override
+        public void updatePictureProfile(long id, PictureProfile pp) {
+            // TODO: implement
+        }
+        @Override
+        public void removePictureProfile(long id) {
+            // TODO: implement
+        }
+        @Override
+        public PictureProfile getPictureProfileById(long id) {
+            return null;
+        }
+        @Override
+        public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
+            return new ArrayList<>();
+        }
+        @Override
+        public List<PictureProfile> getAvailablePictureProfiles() {
+            return new ArrayList<>();
+        }
+        @Override
+        public List<PictureProfile> getAllPictureProfiles() {
+            return new ArrayList<>();
+        }
+
+        @Override
+        public SoundProfile createSoundProfile(SoundProfile pp) {
+            // TODO: implement
+            return pp;
+        }
+        @Override
+        public void updateSoundProfile(long id, SoundProfile pp) {
+            // TODO: implement
+        }
+        @Override
+        public void removeSoundProfile(long id) {
+            // TODO: implement
+        }
+        @Override
+        public SoundProfile getSoundProfileById(long id) {
+            return null;
+        }
+        @Override
+        public List<SoundProfile> getSoundProfilesByPackage(String packageName) {
+            return new ArrayList<>();
+        }
+        @Override
+        public List<SoundProfile> getAvailableSoundProfiles() {
+            return new ArrayList<>();
+        }
+        @Override
+        public List<SoundProfile> getAllSoundProfiles() {
+            return new ArrayList<>();
+        }
+
+
+        @Override
+        public void registerPictureProfileCallback(final IPictureProfileCallback callback) {
+        }
+        @Override
+        public void registerSoundProfileCallback(final ISoundProfileCallback callback) {
+        }
+
+        @Override
+        public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) {
+        }
+
+        @Override
+        public void setAmbientBacklightSettings(AmbientBacklightSettings settings) {
+        }
+
+        @Override
+        public void setAmbientBacklightEnabled(boolean enabled) {
+        }
+
+        @Override
+        public List<ParamCapability> getParamCapabilities(List<String> names) {
+            return new ArrayList<>();
+        }
+
+
+        @Override
+        public boolean isSupported() {
+            return false;
+        }
+
+        @Override
+        public void setAutoPictureQualityEnabled(boolean enabled) {
+        }
+
+        @Override
+        public boolean isAutoPictureQualityEnabled() {
+            return false;
+        }
+
+        @Override
+        public void setSuperResolutionEnabled(boolean enabled) {
+        }
+
+        @Override
+        public boolean isSuperResolutionEnabled() {
+            return false;
+        }
+
+        @Override
+        public void setAutoSoundQualityEnabled(boolean enabled) {
+        }
+
+        @Override
+        public boolean isAutoSoundQualityEnabled() {
+            return false;
+        }
     }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 849f236..2c45fc8 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -9363,10 +9363,15 @@
                                             // a group summary or children (complete a group)
                                             mHandler.postDelayed(() -> {
                                                 synchronized (mNotificationLock) {
-                                                    mGroupHelper.onNotificationPostedWithDelay(
-                                                        r, mNotificationList, mSummaryByGroupKey);
+                                                    NotificationRecord record =
+                                                            mNotificationsByKey.get(key);
+                                                    if (record != null) {
+                                                        mGroupHelper.onNotificationPostedWithDelay(
+                                                                record, mNotificationList,
+                                                                mSummaryByGroupKey);
+                                                    }
                                                 }
-                                            }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+                                            }, key, DELAY_FORCE_REGROUP_TIME);
                                         }
                                     }
 
@@ -9412,10 +9417,15 @@
                                     if (notificationForceGrouping()) {
                                         mHandler.postDelayed(() -> {
                                             synchronized (mNotificationLock) {
-                                                mGroupHelper.onNotificationPostedWithDelay(r,
-                                                        mNotificationList, mSummaryByGroupKey);
+                                                NotificationRecord record =
+                                                        mNotificationsByKey.get(key);
+                                                if (record != null) {
+                                                    mGroupHelper.onNotificationPostedWithDelay(
+                                                            record, mNotificationList,
+                                                            mSummaryByGroupKey);
+                                                }
                                             }
-                                        }, r.getKey(), DELAY_FORCE_REGROUP_TIME);
+                                        }, key, DELAY_FORCE_REGROUP_TIME);
                                     }
                                 }
                             }
@@ -10395,7 +10405,7 @@
                 }
                 mListeners.notifyRemovedLocked(r, reason, r.getStats());
                 if (notificationForceGrouping()) {
-                    mHandler.removeCallbacksAndMessages(r.getKey());
+                    mHandler.removeCallbacksAndEqualMessages(r.getKey());
                     mHandler.post(() -> {
                         synchronized (NotificationManagerService.this.mNotificationLock) {
                             mGroupHelper.onNotificationRemoved(r, mNotificationList);
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 84a5f2b..9f4b9f1 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -64,6 +64,9 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -80,6 +83,8 @@
  */
 public final class BroadcastHelper {
     private static final boolean DEBUG_BROADCASTS = false;
+    private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
+            "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
 
     private final UserManagerInternal mUmInternal;
     private final ActivityManagerInternal mAmInternal;
@@ -291,6 +296,57 @@
         return bOptions;
     }
 
+    private ArrayList<String> getAllNotExportedComponents(@NonNull AndroidPackage pkg,
+            @NonNull ArrayList<String> inputComponentNames) {
+        final ArrayList<String> outputNotExportedComponentNames = new ArrayList<>();
+        int remainingComponentCount = inputComponentNames.size();
+        for (ParsedActivity component : pkg.getReceivers()) {
+            if (inputComponentNames.contains(component.getClassName())) {
+                if (!component.isExported()) {
+                    outputNotExportedComponentNames.add(component.getClassName());
+                }
+                remainingComponentCount--;
+                if (remainingComponentCount <= 0) {
+                    return outputNotExportedComponentNames;
+                }
+            }
+        }
+        for (ParsedProvider component : pkg.getProviders()) {
+            if (inputComponentNames.contains(component.getClassName())) {
+                if (!component.isExported()) {
+                    outputNotExportedComponentNames.add(component.getClassName());
+                }
+                remainingComponentCount--;
+                if (remainingComponentCount <= 0) {
+                    return outputNotExportedComponentNames;
+                }
+            }
+        }
+        for (ParsedService component : pkg.getServices()) {
+            if (inputComponentNames.contains(component.getClassName())) {
+                if (!component.isExported()) {
+                    outputNotExportedComponentNames.add(component.getClassName());
+                }
+                remainingComponentCount--;
+                if (remainingComponentCount <= 0) {
+                    return outputNotExportedComponentNames;
+                }
+            }
+        }
+        for (ParsedActivity component : pkg.getActivities()) {
+            if (inputComponentNames.contains(component.getClassName())) {
+                if (!component.isExported()) {
+                    outputNotExportedComponentNames.add(component.getClassName());
+                }
+                remainingComponentCount--;
+                if (remainingComponentCount <= 0) {
+                    return outputNotExportedComponentNames;
+                }
+            }
+        }
+        return outputNotExportedComponentNames;
+    }
+
     private void sendPackageChangedBroadcastInternal(@NonNull String packageName,
             boolean dontKillApp,
             @NonNull ArrayList<String> componentNames,
@@ -298,10 +354,48 @@
             @Nullable String reason,
             @Nullable int[] userIds,
             @Nullable int[] instantUserIds,
-            @Nullable SparseArray<int[]> broadcastAllowList) {
-        sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
-                packageUid, reason, userIds, instantUserIds, broadcastAllowList,
-                null /* targetPackageName */, null /* requiredPermissions */);
+            @Nullable SparseArray<int[]> broadcastAllowList,
+            @NonNull AndroidPackage pkg) {
+        final boolean isForWholeApp = componentNames.contains(packageName);
+        if (isForWholeApp || !android.content.pm.Flags.reduceBroadcastsForComponentStateChanges()) {
+            sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames,
+                    packageUid, reason, userIds, instantUserIds, broadcastAllowList,
+                    null /* targetPackageName */, null /* requiredPermissions */);
+            return;
+        }
+        // Currently only these four components of activity, receiver, provider and service are
+        // considered to send only the broadcast to the system and the application itself when the
+        // component is not exported. In order to avoid losing to send the broadcast for other
+        // components, it gets the not exported components for these four components of activity,
+        // receiver, provider and service and the others are considered the exported components.
+        final ArrayList<String> notExportedComponentNames = getAllNotExportedComponents(pkg,
+                componentNames);
+        final ArrayList<String> exportedComponentNames = (ArrayList<String>) componentNames.clone();
+        exportedComponentNames.removeAll(notExportedComponentNames);
+
+        if (!notExportedComponentNames.isEmpty()) {
+            // Limit sending of the PACKAGE_CHANGED broadcast to only the system and the
+            // application itself when the component is not exported.
+
+            // First, send the PACKAGE_CHANGED broadcast to the system.
+            sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+                    notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+                    broadcastAllowList, "android" /* targetPackageName */,
+                    new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+
+            // Second, send the PACKAGE_CHANGED broadcast to the application itself.
+            sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+                    notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
+                    broadcastAllowList, packageName /* targetPackageName */,
+                    null /* requiredPermissions */);
+        }
+
+        if (!exportedComponentNames.isEmpty()) {
+            sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
+                    exportedComponentNames, packageUid, reason, userIds, instantUserIds,
+                    broadcastAllowList, null /* targetPackageName */,
+                    null /* requiredPermissions */);
+        }
     }
 
     private void sendPackageChangedBroadcastWithPermissions(@NonNull String packageName,
@@ -830,7 +924,7 @@
                                      @NonNull String reason) {
         PackageStateInternal setting = snapshot.getPackageStateInternal(packageName,
                 Process.SYSTEM_UID);
-        if (setting == null) {
+        if (setting == null || setting.getPkg() == null) {
             return;
         }
         final int userId = UserHandle.getUserId(packageUid);
@@ -842,7 +936,7 @@
                 isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds);
         mHandler.post(() -> sendPackageChangedBroadcastInternal(
                 packageName, dontKillApp, componentNames, packageUid, reason, userIds,
-                instantUserIds, broadcastAllowList));
+                instantUserIds, broadcastAllowList, setting.getPkg()));
         mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames,
                 packageUid, reason, userIds, instantUserIds, broadcastAllowList, mHandler);
     }
diff --git a/services/core/java/com/android/server/pm/SaferIntentUtils.java b/services/core/java/com/android/server/pm/SaferIntentUtils.java
index 9a7ba0f..bc36fab 100644
--- a/services/core/java/com/android/server/pm/SaferIntentUtils.java
+++ b/services/core/java/com/android/server/pm/SaferIntentUtils.java
@@ -26,7 +26,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.Overridable;
 import android.content.Intent;
@@ -45,6 +44,7 @@
 import android.util.Slog;
 
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.component.ParsedMainComponentImpl;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.IntentResolver;
 import com.android.server.LocalServices;
@@ -88,22 +88,6 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     private static final long IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS = 229362273;
 
-    /**
-     * Intents sent from apps enabling this feature will stop resolving to components with
-     * non matching intent filters, even when explicitly setting a component name, unless the
-     * target components are in the same app as the calling app.
-     * <p>
-     * When an app registers an exported component in its manifest and adds &lt;intent-filter&gt;s,
-     * the component can be started by any intent - even those that do not match the intent filter.
-     * This has proven to be something that many developers find counterintuitive.
-     * Without checking the intent when the component is started, in some circumstances this can
-     * allow 3P apps to trigger internal-only functionality.
-     */
-    @ChangeId
-    @Overridable
-    @Disabled
-    private static final long ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS = 161252188;
-
     @Nullable
     private static ParsedMainComponent infoToComponent(
             ComponentInfo info, ComponentResolverApi resolver, boolean isReceiver) {
@@ -277,11 +261,6 @@
                 ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
                 : null;
 
-        final boolean enforceMatch = Flags.enforceIntentFilterMatch()
-                && args.isChangeEnabled(ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS);
-        final boolean blockNullAction = Flags.blockNullActionIntents()
-                && args.isChangeEnabled(IntentFilter.BLOCK_NULL_ACTION_INTENTS);
-
         for (int i = resolveInfos.size() - 1; i >= 0; --i) {
             final ComponentInfo info = resolveInfos.get(i).getComponentInfo();
 
@@ -296,57 +275,65 @@
                 continue;
             }
 
-            Boolean match = null;
+            boolean enforceIntentFilter = Flags.enableIntentMatchingFlags();
+            boolean allowNullAction = false;
 
-            if (args.intent.getAction() == null) {
+            if (Flags.enableIntentMatchingFlags()) {
+                int flags = comp.getIntentMatchingFlags();
+                if (flags == 0 || (flags & ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_NONE)
+                        == ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_NONE
+                        || (flags
+                        & ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ENFORCE_INTENT_FILTER)
+                        == 0) {
+                    enforceIntentFilter = false;
+                }
+                if ((flags & ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION)
+                        == ParsedMainComponentImpl.INTENT_MATCHING_FLAGS_ALLOW_NULL_ACTION) {
+                    allowNullAction = true;
+                }
+            }
+
+            boolean hasNullAction = args.intent.getAction() == null;
+            boolean intentMatchesComponent = false;
+
+            for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
+                IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
+                if (IntentResolver.intentMatchesFilter(
+                        intentFilter, args.intent, args.resolvedType)) {
+                    intentMatchesComponent = true;
+                    break;
+                }
+            }
+
+            boolean blockIntent = false;
+            if (enforceIntentFilter) {
+                if ((hasNullAction && !allowNullAction) || !intentMatchesComponent) {
+                    blockIntent = true;
+                }
+            }
+
+            if (hasNullAction) {
                 args.reportEvent(
-                        UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH,
-                        enforceMatch && blockNullAction);
-                if (blockNullAction) {
-                    // Skip intent filter matching if blocking null action
-                    match = false;
-                }
-            }
-
-            if (match == null) {
-                // Check if any intent filter matches
-                for (int j = 0, size = comp.getIntents().size(); j < size; ++j) {
-                    IntentFilter intentFilter = comp.getIntents().get(j).getIntentFilter();
-                    if (IntentResolver.intentMatchesFilter(
-                            intentFilter, args.intent, args.resolvedType)) {
-                        match = true;
-                        break;
-                    }
-                }
-            }
-
-            // At this point, the value `match` has the following states:
-            // null : Intent does not match any intent filter
-            // false: Null action intent detected AND blockNullAction == true
-            // true : The intent matches at least one intent filter
-
-            if (match == null) {
+                        UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__NULL_ACTION_MATCH, blockIntent);
+            } else if (!intentMatchesComponent) {
                 args.reportEvent(
                         UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__EXPLICIT_INTENT_FILTER_UNMATCH,
-                        enforceMatch);
-                match = false;
+                        blockIntent);
             }
 
-            if (!match) {
-                // All non-matching intents has to be marked accordingly
-                if (Flags.enforceIntentFilterMatch()) {
-                    args.intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+            if (Flags.enforceIntentFilterMatch() && (hasNullAction || !intentMatchesComponent)) {
+                args.intent.addExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH);
+            }
+
+            if (blockIntent) {
+                Slog.w(TAG, "Intent does not match component's intent filter: " + args.intent);
+                Slog.w(TAG, "Access blocked: " + comp.getComponentName());
+                if (DEBUG_INTENT_MATCHING) {
+                    Slog.v(TAG, "Component intent filters:");
+                    comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, "  "));
+                    Slog.v(TAG, "-----------------------------");
                 }
-                if (enforceMatch) {
-                    Slog.w(TAG, "Intent does not match component's intent filter: " + args.intent);
-                    Slog.w(TAG, "Access blocked: " + comp.getComponentName());
-                    if (DEBUG_INTENT_MATCHING) {
-                        Slog.v(TAG, "Component intent filters:");
-                        comp.getIntents().forEach(f -> f.getIntentFilter().dump(logPrinter, "  "));
-                        Slog.v(TAG, "-----------------------------");
-                    }
-                    resolveInfos.remove(i);
-                }
+                resolveInfos.remove(i);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 6c03214..7ecfe7f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1222,7 +1222,7 @@
         // Mark the user for removal.
         addRemovingUserIdLocked(ui.id);
         ui.partial = true;
-        ui.flags |= UserInfo.FLAG_DISABLED;
+        addUserInfoFlags(ui, UserInfo.FLAG_DISABLED);
     }
 
     /* Prunes out any partially created or partially removed users. */
@@ -1264,7 +1264,7 @@
                 if (ui.preCreated) {
                     preCreatedUsers.add(ui);
                     addRemovingUserIdLocked(ui.id);
-                    ui.flags |= UserInfo.FLAG_DISABLED;
+                    addUserInfoFlags(ui, UserInfo.FLAG_DISABLED);
                     ui.partial = true;
                 }
             }
@@ -2120,7 +2120,7 @@
                 info = getUserInfoLU(userId);
                 if (info != null && !info.isEnabled()) {
                     wasUserDisabled = true;
-                    info.flags ^= UserInfo.FLAG_DISABLED;
+                    removeUserInfoFlags(info, UserInfo.FLAG_DISABLED);
                     writeUserLP(getUserDataLU(info.id));
                 }
             }
@@ -2130,6 +2130,36 @@
         }
     }
 
+    /**
+     * This method is for monitoring flag changes on users flags and invalidate cache relevant to
+     * the change. The method add flags and invalidateOnUserInfoFlagChange for the flags which
+     * has changed.
+     * @param userInfo of existing user in mUsers list
+     * @param flags to be added to userInfo
+     */
+    private void addUserInfoFlags(UserInfo userInfo, @UserInfoFlag int flags) {
+        int diff = ~userInfo.flags & flags;
+        if (diff > 0) {
+            userInfo.flags |= diff;
+            UserManager.invalidateOnUserInfoFlagChange(diff);
+        }
+    }
+
+    /**
+     * This method is for monitoring flag changes on users flags and invalidate cache relevant to
+     * the change. The method remove flags and invalidateOnUserInfoFlagChange for the flags which
+     * has changed.
+     * @param userInfo of existing user in mUsers list
+     * @param flags to be removed from userInfo
+     */
+    private void removeUserInfoFlags(UserInfo userInfo, @UserInfoFlag int flags) {
+        int diff = userInfo.flags & flags;
+        if (diff > 0) {
+            userInfo.flags ^= diff;
+            UserManager.invalidateOnUserInfoFlagChange(diff);
+        }
+    }
+
     @Override
     public void setUserAdmin(@UserIdInt int userId) {
         checkManageUserAndAcrossUsersFullPermission("set user admin");
@@ -6245,7 +6275,7 @@
                 userData.info.guestToRemove = true;
                 // Mark it as disabled, so that it isn't returned any more when
                 // profiles are queried.
-                userData.info.flags |= UserInfo.FLAG_DISABLED;
+                addUserInfoFlags(userData.info, UserInfo.FLAG_DISABLED);
                 writeUserLP(userData);
             }
         } finally {
@@ -6390,7 +6420,7 @@
                 }
                 // Mark it as disabled, so that it isn't returned any more when
                 // profiles are queried.
-                userData.info.flags |= UserInfo.FLAG_DISABLED;
+                addUserInfoFlags(userData.info, UserInfo.FLAG_DISABLED);
                 writeUserLP(userData);
             }
 
@@ -7789,7 +7819,7 @@
                if (userInfo != null && userInfo.isEphemeral()) {
                     // Do not allow switching back to the ephemeral user again as the user is going
                     // to be deleted.
-                    userInfo.flags |= UserInfo.FLAG_DISABLED;
+                    addUserInfoFlags(userInfo, UserInfo.FLAG_DISABLED);
                     if (userInfo.isGuest()) {
                         // Indicate that the guest will be deleted after it stops.
                         userInfo.guestToRemove = true;
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index 2e8a0c6..a928814 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -42,6 +42,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.LatencyTracker;
+import com.android.server.power.feature.PowerManagerFlags;
 
 /**
  * Used to store power related requests to every display in a
@@ -62,6 +63,8 @@
     private final DisplayManagerInternal mDisplayManagerInternal;
     private final boolean mSupportsSandman;
     private final int mGroupId;
+    private final PowerManagerFlags mFeatureFlags;
+
     /** True if DisplayManagerService has applied all the latest display states that were requested
      *  for this group. */
     private boolean mReady;
@@ -82,10 +85,15 @@
     private long mLastWakeTime;
     /** Timestamp (milliseconds since boot) of the last time the power group was put to sleep. */
     private long mLastSleepTime;
+    /** The last reason that woke the power group. */
+    private @PowerManager.WakeReason int mLastWakeReason = PowerManager.WAKE_REASON_UNKNOWN;
+    /** The last reason that put the power group to sleep. */
+    private @PowerManager.GoToSleepReason int mLastSleepReason =
+            PowerManager.GO_TO_SLEEP_REASON_UNKNOWN;
 
     PowerGroup(int groupId, PowerGroupListener wakefulnessListener, Notifier notifier,
             DisplayManagerInternal displayManagerInternal, int wakefulness, boolean ready,
-            boolean supportsSandman, long eventTime) {
+            boolean supportsSandman, long eventTime, PowerManagerFlags featureFlags) {
         mGroupId = groupId;
         mWakefulnessListener = wakefulnessListener;
         mNotifier = notifier;
@@ -95,10 +103,12 @@
         mSupportsSandman = supportsSandman;
         mLastWakeTime = eventTime;
         mLastSleepTime = eventTime;
+        mFeatureFlags = featureFlags;
     }
 
     PowerGroup(int wakefulness, PowerGroupListener wakefulnessListener, Notifier notifier,
-            DisplayManagerInternal displayManagerInternal, long eventTime) {
+            DisplayManagerInternal displayManagerInternal, long eventTime,
+            PowerManagerFlags featureFlags) {
         mGroupId = Display.DEFAULT_DISPLAY_GROUP;
         mWakefulnessListener = wakefulnessListener;
         mNotifier = notifier;
@@ -108,6 +118,7 @@
         mSupportsSandman = true;
         mLastWakeTime = eventTime;
         mLastSleepTime = eventTime;
+        mFeatureFlags = featureFlags;
     }
 
     long getLastWakeTimeLocked() {
@@ -138,8 +149,14 @@
                 setLastPowerOnTimeLocked(eventTime);
                 setIsPoweringOnLocked(true);
                 mLastWakeTime = eventTime;
+                if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+                    mLastWakeReason = reason;
+                }
             } else if (isInteractive(mWakefulness) && !isInteractive(newWakefulness)) {
                 mLastSleepTime = eventTime;
+                if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+                    mLastSleepReason = reason;
+                }
             }
             mWakefulness = newWakefulness;
             mWakefulnessListener.onWakefulnessChangedLocked(mGroupId, mWakefulness, eventTime,
@@ -393,37 +410,51 @@
         return false;
     }
 
-    @VisibleForTesting
-    int getDesiredScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
+    // TODO: create and use more specific policy reasons, beyond the ones that correlate to
+    // interactivity state
+    private void updateScreenPolicyLocked(boolean quiescent, boolean dozeAfterScreenOff,
             boolean bootCompleted, boolean screenBrightnessBoostInProgress,
             boolean brightWhenDozing) {
         final int wakefulness = getWakefulnessLocked();
         final int wakeLockSummary = getWakeLockSummaryLocked();
-        if (wakefulness == WAKEFULNESS_ASLEEP || quiescent) {
-            return DisplayPowerRequest.POLICY_OFF;
+        int policyReason = Display.STATE_REASON_DEFAULT_POLICY;
+        int policy = Integer.MAX_VALUE; // do not set to real policy to start with.
+        if (quiescent) {
+            policy = DisplayPowerRequest.POLICY_OFF;
+        } else if (wakefulness == WAKEFULNESS_ASLEEP) {
+            policy = DisplayPowerRequest.POLICY_OFF;
+            policyReason = sleepReasonToDisplayStateReason(mLastSleepReason);
         } else if (wakefulness == WAKEFULNESS_DOZING) {
             if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
-                return DisplayPowerRequest.POLICY_DOZE;
-            }
-            if (dozeAfterScreenOff) {
-                return DisplayPowerRequest.POLICY_OFF;
-            }
-            if (brightWhenDozing) {
-                return DisplayPowerRequest.POLICY_BRIGHT;
+                policy = DisplayPowerRequest.POLICY_DOZE;
+            } else if (dozeAfterScreenOff) {
+                policy = DisplayPowerRequest.POLICY_OFF;
+            } else if (brightWhenDozing) {
+                policy = DisplayPowerRequest.POLICY_BRIGHT;
             }
             // Fall through and preserve the current screen policy if not configured to
             // bright when dozing or doze after screen off.  This causes the screen off transition
             // to be skipped.
         }
 
-        if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
-                || !bootCompleted
-                || (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
-                || screenBrightnessBoostInProgress) {
-            return DisplayPowerRequest.POLICY_BRIGHT;
+        if (policy == Integer.MAX_VALUE) { // policy is not set yet.
+            if (isInteractive(wakefulness)) {
+                policyReason = wakeReasonToDisplayStateReason(mLastWakeReason);
+            }
+            if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
+                    || !bootCompleted
+                    || (getUserActivitySummaryLocked() & USER_ACTIVITY_SCREEN_BRIGHT) != 0
+                    || screenBrightnessBoostInProgress) {
+                policy = DisplayPowerRequest.POLICY_BRIGHT;
+            } else {
+                policy = DisplayPowerRequest.POLICY_DIM;
+            }
         }
 
-        return DisplayPowerRequest.POLICY_DIM;
+        if (mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()) {
+            mDisplayPowerRequest.policyReason = policyReason;
+        }
+        mDisplayPowerRequest.policy = policy;
     }
 
     int getPolicyLocked() {
@@ -439,7 +470,7 @@
             boolean dozeAfterScreenOff, boolean bootCompleted,
             boolean screenBrightnessBoostInProgress, boolean waitForNegativeProximity,
             boolean brightWhenDozing) {
-        mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(quiescent, dozeAfterScreenOff,
+        updateScreenPolicyLocked(quiescent, dozeAfterScreenOff,
                 bootCompleted, screenBrightnessBoostInProgress, brightWhenDozing);
         mDisplayPowerRequest.screenBrightnessOverride = screenBrightnessOverride;
         mDisplayPowerRequest.screenBrightnessOverrideTag = overrideTag;
@@ -478,6 +509,33 @@
         return ready;
     }
 
+    /** Determines the respective display state reason for a given PowerManager WakeReason. */
+    private static int wakeReasonToDisplayStateReason(@PowerManager.WakeReason int wakeReason) {
+        switch (wakeReason) {
+            case PowerManager.WAKE_REASON_POWER_BUTTON:
+            case PowerManager.WAKE_REASON_WAKE_KEY:
+                return Display.STATE_REASON_KEY;
+            case PowerManager.WAKE_REASON_WAKE_MOTION:
+                return Display.STATE_REASON_MOTION;
+            case PowerManager.WAKE_REASON_TILT:
+                return Display.STATE_REASON_TILT;
+            default:
+                return Display.STATE_REASON_DEFAULT_POLICY;
+        }
+    }
+
+    /** Determines the respective display state reason for a given PowerManager GoToSleepReason. */
+    private static int sleepReasonToDisplayStateReason(
+            @PowerManager.GoToSleepReason int sleepReason) {
+        switch (sleepReason) {
+            case PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON:
+            case PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON:
+                return Display.STATE_REASON_KEY;
+            default:
+                return Display.STATE_REASON_DEFAULT_POLICY;
+        }
+    }
+
     protected interface PowerGroupListener {
         /**
          * Informs the recipient about a wakefulness change of a {@link PowerGroup}.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 65f2241..3a5afac 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -790,7 +790,8 @@
                         WAKEFULNESS_AWAKE,
                         /* ready= */ false,
                         supportsSandman,
-                        mClock.uptimeMillis());
+                        mClock.uptimeMillis(),
+                        mFeatureFlags);
                 mPowerGroups.append(groupId, powerGroup);
                 onPowerGroupEventLocked(DISPLAY_GROUP_ADDED, powerGroup);
             }
@@ -1375,7 +1376,8 @@
 
             mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP,
                     new PowerGroup(WAKEFULNESS_AWAKE, mPowerGroupWakefulnessChangeListener,
-                            mNotifier, mDisplayManagerInternal, mClock.uptimeMillis()));
+                            mNotifier, mDisplayManagerInternal, mClock.uptimeMillis(),
+                            mFeatureFlags));
             DisplayGroupPowerChangeListener displayGroupPowerChangeListener =
                     new DisplayGroupPowerChangeListener();
             mDisplayManagerInternal.registerDisplayGroupListener(displayGroupPowerChangeListener);
@@ -3738,14 +3740,6 @@
     }
 
     @VisibleForTesting
-    @GuardedBy("mLock")
-    int getDesiredScreenPolicyLocked(int groupId) {
-        return mPowerGroups.get(groupId).getDesiredScreenPolicyLocked(sQuiescent,
-                mDozeAfterScreenOff, mBootCompleted,
-                mScreenBrightnessBoostInProgress, mBrightWhenDozingConfig);
-    }
-
-    @VisibleForTesting
     int getDreamsBatteryLevelDrain() {
         return mDreamsBatteryLevelDrain;
     }
@@ -4588,7 +4582,8 @@
                     WAKEFULNESS_AWAKE,
                     /* ready= */ false,
                     /* supportsSandman= */ false,
-                    mClock.uptimeMillis());
+                    mClock.uptimeMillis(),
+                    mFeatureFlags);
             mPowerGroups.append(displayGroupId, powerGroup);
         }
         mDirty |= DIRTY_DISPLAY_GROUP_WAKEFULNESS;
diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
index db57d11..4ddf0c0 100644
--- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
+++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java
@@ -50,6 +50,11 @@
     private final FlagState mFrameworkWakelockInfo =
             new FlagState(Flags.FLAG_FRAMEWORK_WAKELOCK_INFO, Flags::frameworkWakelockInfo);
 
+    private final FlagState mPolicyReasonInDisplayPowerRequest = new FlagState(
+            Flags.FLAG_POLICY_REASON_IN_DISPLAY_POWER_REQUEST,
+            Flags::policyReasonInDisplayPowerRequest
+    );
+
     /** Returns whether early-screen-timeout-detector is enabled on not. */
     public boolean isEarlyScreenTimeoutDetectorEnabled() {
         return mEarlyScreenTimeoutDetectorFlagState.isEnabled();
@@ -77,6 +82,13 @@
     }
 
     /**
+     * @return Whether the wakefulness reason is populated in DisplayPowerRequest.
+     */
+    public boolean isPolicyReasonInDisplayPowerRequestEnabled() {
+        return mPolicyReasonInDisplayPowerRequest.isEnabled();
+    }
+
+    /**
      * dumps all flagstates
      * @param pw printWriter
      */
diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig
index 8bb69ba..e27f8bb 100644
--- a/services/core/java/com/android/server/power/feature/power_flags.aconfig
+++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig
@@ -33,3 +33,11 @@
     description: "Feature flag to enable statsd pulling of FrameworkWakelockInfo atoms"
     bug: "352602149"
 }
+
+flag {
+    name: "policy_reason_in_display_power_request"
+    namespace: "wear_frameworks"
+    description: "Whether the policy reason is populted in DisplayPowerRequest."
+    bug: "364349703"
+    is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index c33ed55..1260eee 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -33,6 +33,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.provider.Settings;
+import android.security.advancedprotection.AdvancedProtectionFeature;
 import android.security.advancedprotection.IAdvancedProtectionCallback;
 import android.security.advancedprotection.IAdvancedProtectionService;
 import android.util.ArrayMap;
@@ -44,9 +45,11 @@
 import com.android.server.SystemService;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
 
 import java.io.FileDescriptor;
 import java.util.ArrayList;
+import java.util.List;
 
 /** @hide */
 public class AdvancedProtectionService extends IAdvancedProtectionService.Stub  {
@@ -58,10 +61,12 @@
     private final Handler mHandler;
     private final AdvancedProtectionStore mStore;
 
-    // Features owned by the service - their code will be executed when state changes
+    // Features living with the service - their code will be executed when state changes
     private final ArrayList<AdvancedProtectionHook> mHooks = new ArrayList<>();
     // External features - they will be called on state change
     private final ArrayMap<IBinder, IAdvancedProtectionCallback> mCallbacks = new ArrayMap<>();
+    // For tracking only - not called on state change
+    private final ArrayList<AdvancedProtectionProvider> mProviders = new ArrayList<>();
 
     private AdvancedProtectionService(@NonNull Context context) {
         super(PermissionEnforcer.fromContext(context));
@@ -71,13 +76,17 @@
     }
 
     private void initFeatures(boolean enabled) {
+        // Empty until features are added.
+        // Examples:
+        // mHooks.add(new SideloadingAdvancedProtectionHook(mContext, enabled));
+        // mProviders.add(new WifiAdvancedProtectionProvider());
     }
 
     // Only for tests
     @VisibleForTesting
     AdvancedProtectionService(@NonNull Context context, @NonNull AdvancedProtectionStore store,
             @NonNull Looper looper, @NonNull PermissionEnforcer permissionEnforcer,
-            @Nullable AdvancedProtectionHook hook) {
+            @Nullable AdvancedProtectionHook hook, @Nullable AdvancedProtectionProvider provider) {
         super(permissionEnforcer);
         mContext = context;
         mStore = store;
@@ -85,6 +94,10 @@
         if (hook != null) {
             mHooks.add(hook);
         }
+
+        if (provider != null) {
+            mProviders.add(provider);
+        }
     }
 
     @Override
@@ -146,6 +159,25 @@
     }
 
     @Override
+    @EnforcePermission(Manifest.permission.SET_ADVANCED_PROTECTION_MODE)
+    public List<AdvancedProtectionFeature> getAdvancedProtectionFeatures() {
+        getAdvancedProtectionFeatures_enforcePermission();
+        List<AdvancedProtectionFeature> features = new ArrayList<>();
+        for (int i = 0; i < mProviders.size(); i++) {
+            features.addAll(mProviders.get(i).getFeatures());
+        }
+
+        for (int i = 0; i < mHooks.size(); i++) {
+            AdvancedProtectionHook hook = mHooks.get(i);
+            if (hook.isAvailable()) {
+                features.add(hook.getFeature());
+            }
+        }
+
+        return features;
+    }
+
+    @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, @NonNull String[] args, ShellCallback callback,
             @NonNull ResultReceiver resultReceiver) {
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java
index b2acc51..f82db96 100644
--- a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java
+++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionHook.java
@@ -18,11 +18,15 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.security.advancedprotection.AdvancedProtectionFeature;
 
 /** @hide */
 public abstract class AdvancedProtectionHook {
     /** Called on boot phase PHASE_SYSTEM_SERVICES_READY */
     public AdvancedProtectionHook(@NonNull Context context, boolean enabled) {}
+    /** The feature this hook provides */
+    @NonNull
+    public abstract AdvancedProtectionFeature getFeature();
     /** Whether this feature is relevant on this device. If false, onAdvancedProtectionChanged will
      * not be called, and the feature will not be displayed in the onboarding UX. */
     public abstract boolean isAvailable();
diff --git a/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
new file mode 100644
index 0000000..ed451f1
--- /dev/null
+++ b/services/core/java/com/android/server/security/advancedprotection/features/AdvancedProtectionProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.security.advancedprotection.features;
+
+import android.security.advancedprotection.AdvancedProtectionFeature;
+
+import java.util.List;
+
+/** @hide */
+public abstract class AdvancedProtectionProvider {
+    /** The list of features provided */
+    public abstract List<AdvancedProtectionFeature> getFeatures();
+}
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 457196b..465ac2f 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -21,6 +21,7 @@
 import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -1896,8 +1897,11 @@
             }
         }
 
+        @EnforcePermission(Manifest.permission.ACCESS_FINE_LOCATION)
         @Override
         public boolean isInSignificantPlace() {
+            super.isInSignificantPlace_enforcePermission();
+
             if (android.security.Flags.significantPlaces()) {
                 mSignificantPlaceServiceWatcher.runOnBinder(
                         binder -> ISignificantPlaceProvider.Stub.asInterface(binder)
diff --git a/services/core/java/com/android/server/vcn/VcnContext.java b/services/core/java/com/android/server/vcn/VcnContext.java
index a492a72..6ce8685 100644
--- a/services/core/java/com/android/server/vcn/VcnContext.java
+++ b/services/core/java/com/android/server/vcn/VcnContext.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
-import android.net.IpSecTransformState;
 import android.net.vcn.FeatureFlags;
 import android.net.vcn.FeatureFlagsImpl;
 import android.os.Looper;
@@ -70,19 +69,6 @@
         return mIsInTestMode;
     }
 
-    public boolean isFlagIpSecTransformStateEnabled() {
-        // TODO: b/328844044: Ideally this code should gate the behavior by checking the
-        // android.net.platform.flags.ipsec_transform_state flag but that flag is not accessible
-        // right now. We should either update the code when the flag is accessible or remove the
-        // legacy behavior after VIC SDK finalization
-        try {
-            new IpSecTransformState.Builder();
-            return true;
-        } catch (Exception e) {
-            return false;
-        }
-    }
-
     @NonNull
     public FeatureFlags getFeatureFlags() {
         return mFeatureFlags;
diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
index 189b608..2d3bc84 100644
--- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
+++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java
@@ -1912,8 +1912,7 @@
                 // Transforms do not need to be persisted; the IkeSession will keep them alive
                 mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform);
 
-                if (direction == IpSecManager.DIRECTION_IN
-                        && mVcnContext.isFlagIpSecTransformStateEnabled()) {
+                if (direction == IpSecManager.DIRECTION_IN) {
                     mUnderlyingNetworkController.updateInboundTransform(mUnderlying, transform);
                 }
 
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 6f1e15b..16ab51e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -148,12 +148,6 @@
 
         Objects.requireNonNull(deps, "Missing deps");
 
-        if (!vcnContext.isFlagIpSecTransformStateEnabled()) {
-            // Caller error
-            logWtf("ipsecTransformState flag disabled");
-            throw new IllegalAccessException("ipsecTransformState flag disabled");
-        }
-
         mHandler = new Handler(getVcnContext().getLooper());
 
         mPowerManager = getVcnContext().getContext().getSystemService(PowerManager.class);
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
index 0b9b677..3eeeece 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkController.java
@@ -204,10 +204,8 @@
         List<NetworkCallback> oldCellCallbacks = new ArrayList<>(mCellBringupCallbacks);
         mCellBringupCallbacks.clear();
 
-        if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
-            for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
-                evaluator.close();
-            }
+        for (UnderlyingNetworkEvaluator evaluator : mUnderlyingNetworkRecords.values()) {
+            evaluator.close();
         }
 
         mUnderlyingNetworkRecords.clear();
@@ -429,10 +427,7 @@
         if (oldSnapshot
                 .getAllSubIdsInGroup(mSubscriptionGroup)
                 .equals(newSnapshot.getAllSubIdsInGroup(mSubscriptionGroup))) {
-
-            if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
-                reevaluateNetworks();
-            }
+            reevaluateNetworks();
             return;
         }
         registerOrUpdateNetworkRequests();
@@ -445,11 +440,6 @@
      */
     public void updateInboundTransform(
             @NonNull UnderlyingNetworkRecord currentNetwork, @NonNull IpSecTransform transform) {
-        if (!mVcnContext.isFlagIpSecTransformStateEnabled()) {
-            logWtf("#updateInboundTransform: unexpected call; flags missing");
-            return;
-        }
-
         Objects.requireNonNull(currentNetwork, "currentNetwork is null");
         Objects.requireNonNull(transform, "transform is null");
 
@@ -572,10 +562,7 @@
 
         @Override
         public void onLost(@NonNull Network network) {
-            if (mVcnContext.isFlagIpSecTransformStateEnabled()) {
-                mUnderlyingNetworkRecords.get(network).close();
-            }
-
+            mUnderlyingNetworkRecords.get(network).close();
             mUnderlyingNetworkRecords.remove(network);
 
             reevaluateNetworks();
@@ -648,11 +635,6 @@
     class NetworkEvaluatorCallbackImpl implements NetworkEvaluatorCallback {
         @Override
         public void onEvaluationResultChanged() {
-            if (!mVcnContext.isFlagIpSecTransformStateEnabled()) {
-                logWtf("#onEvaluationResultChanged: unexpected call; flags missing");
-                return;
-            }
-
             mVcnContext.ensureRunningOnLooperThread();
             reevaluateNetworks();
         }
diff --git a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
index 448a7eb..08be11e 100644
--- a/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
+++ b/services/core/java/com/android/server/vcn/routeselection/UnderlyingNetworkEvaluator.java
@@ -102,17 +102,15 @@
         updatePriorityClass(
                 underlyingNetworkTemplates, subscriptionGroup, lastSnapshot, carrierConfig);
 
-        if (isIpSecPacketLossDetectorEnabled()) {
-            try {
-                mMetricMonitors.add(
-                        mDependencies.newIpSecPacketLossDetector(
-                                mVcnContext,
-                                mNetworkRecordBuilder.getNetwork(),
-                                carrierConfig,
-                                new MetricMonitorCallbackImpl()));
-            } catch (IllegalAccessException e) {
-                // No action. Do not add anything to mMetricMonitors
-            }
+        try {
+            mMetricMonitors.add(
+                    mDependencies.newIpSecPacketLossDetector(
+                            mVcnContext,
+                            mNetworkRecordBuilder.getNetwork(),
+                            carrierConfig,
+                            new MetricMonitorCallbackImpl()));
+        } catch (IllegalAccessException e) {
+            // No action. Do not add anything to mMetricMonitors
         }
     }
 
@@ -188,22 +186,12 @@
         }
     }
 
-    private boolean isIpSecPacketLossDetectorEnabled() {
-        return isIpSecPacketLossDetectorEnabled(mVcnContext);
-    }
-
-    private static boolean isIpSecPacketLossDetectorEnabled(VcnContext vcnContext) {
-        return vcnContext.isFlagIpSecTransformStateEnabled();
-    }
-
     /** Get the comparator for UnderlyingNetworkEvaluator */
     public static Comparator<UnderlyingNetworkEvaluator> getComparator(VcnContext vcnContext) {
         return (left, right) -> {
-            if (isIpSecPacketLossDetectorEnabled(vcnContext)) {
-                if (left.mIsPenalized != right.mIsPenalized) {
-                    // A penalized network should have lower priority which means a larger index
-                    return left.mIsPenalized ? 1 : -1;
-                }
+            if (left.mIsPenalized != right.mIsPenalized) {
+                // A penalized network should have lower priority which means a larger index
+                return left.mIsPenalized ? 1 : -1;
             }
 
             final int leftIndex = left.mPriorityClass;
diff --git a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
index d0d6071..32a3227 100644
--- a/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
+++ b/services/core/java/com/android/server/vibrator/ComposePwleV2VibratorStep.java
@@ -20,6 +20,7 @@
 import android.os.Trace;
 import android.os.VibrationEffect;
 import android.os.vibrator.Flags;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.PwleSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
@@ -57,7 +58,7 @@
             // Load the next PwleSegments to create a single composePwleV2 call to the vibrator,
             // limited to the vibrator's maximum envelope effect size.
             int limit = controller.getVibratorInfo().getMaxEnvelopeEffectSize();
-            List<PwleSegment> pwles = unrollPwleSegments(effect, segmentIndex, limit);
+            List<PwlePoint> pwles = unrollPwleSegments(effect, segmentIndex, limit);
 
             if (pwles.isEmpty()) {
                 Slog.w(VibrationThread.TAG, "Ignoring wrong segment for a ComposeEnvelopeStep: "
@@ -70,7 +71,7 @@
                 Slog.d(VibrationThread.TAG, "Compose " + pwles + " PWLEs on vibrator "
                         + controller.getVibratorInfo().getId());
             }
-            PwleSegment[] pwlesArray = pwles.toArray(new PwleSegment[pwles.size()]);
+            PwlePoint[] pwlesArray = pwles.toArray(new PwlePoint[pwles.size()]);
             long vibratorOnResult = controller.on(pwlesArray, getVibration().id);
             handleVibratorOnResult(vibratorOnResult);
             getVibration().stats.reportComposePwle(vibratorOnResult, pwlesArray);
@@ -82,9 +83,9 @@
         }
     }
 
-    private List<PwleSegment> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex,
+    private List<PwlePoint> unrollPwleSegments(VibrationEffect.Composed effect, int startIndex,
             int limit) {
-        List<PwleSegment> segments = new ArrayList<>(limit);
+        List<PwlePoint> pwlePoints = new ArrayList<>(limit);
         float bestBreakAmplitude = 1;
         int bestBreakPosition = limit; // Exclusive index.
 
@@ -93,7 +94,7 @@
 
         // Loop once after reaching the limit to see if breaking it will really be necessary, then
         // apply the best break position found, otherwise return the full list as it fits the limit.
-        for (int i = startIndex; segments.size() <= limit; i++) {
+        for (int i = startIndex; pwlePoints.size() < limit; i++) {
             if (i == segmentCount) {
                 if (repeatIndex >= 0) {
                     i = repeatIndex;
@@ -104,12 +105,20 @@
             }
             VibrationEffectSegment segment = effect.getSegments().get(i);
             if (segment instanceof PwleSegment pwleSegment) {
-                segments.add(pwleSegment);
+                if (pwlePoints.isEmpty()) {
+                    // The initial state is defined by the starting amplitude and frequency of the
+                    // first PwleSegment. The time parameter is set to zero to indicate this is
+                    // the initial condition without any ramp up time.
+                    pwlePoints.add(new PwlePoint(pwleSegment.getStartAmplitude(),
+                            pwleSegment.getStartFrequencyHz(), /*timeMillis=*/ 0));
+                }
+                pwlePoints.add(new PwlePoint(pwleSegment.getEndAmplitude(),
+                        pwleSegment.getEndFrequencyHz(), (int) pwleSegment.getDuration()));
 
-                if (isBetterBreakPosition(segments, bestBreakAmplitude, limit)) {
+                if (isBetterBreakPosition(pwlePoints, bestBreakAmplitude, limit)) {
                     // Mark this position as the best one so far to break a long waveform.
                     bestBreakAmplitude = pwleSegment.getEndAmplitude();
-                    bestBreakPosition = segments.size(); // Break after this pwle ends.
+                    bestBreakPosition = pwlePoints.size(); // Break after this pwle ends.
                 }
             } else {
                 // First non-pwle segment, stop collecting pwles.
@@ -117,21 +126,21 @@
             }
         }
 
-        return segments.size() > limit
+        return pwlePoints.size() > limit
                 // Remove excessive segments, using the best breaking position recorded.
-                ? segments.subList(0, bestBreakPosition)
+                ? pwlePoints.subList(0, bestBreakPosition)
                 // Return all collected pwle segments.
-                : segments;
+                : pwlePoints;
     }
 
     /**
      * Returns true if the current segment list represents a better break position for a PWLE,
      * given the current amplitude being used for breaking it at a smaller size and the size limit.
      */
-    private boolean isBetterBreakPosition(List<PwleSegment> segments,
+    private boolean isBetterBreakPosition(List<PwlePoint> segments,
             float currentBestBreakAmplitude, int limit) {
-        PwleSegment lastSegment = segments.get(segments.size() - 1);
-        float breakAmplitudeCandidate = lastSegment.getEndAmplitude();
+        PwlePoint lastSegment = segments.get(segments.size() - 1);
+        float breakAmplitudeCandidate = lastSegment.getAmplitude();
         int breakPositionCandidate = segments.size();
 
         if (breakPositionCandidate > limit) {
diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java
index bc4dbe7..de423f0 100644
--- a/services/core/java/com/android/server/vibrator/VibrationStats.java
+++ b/services/core/java/com/android/server/vibrator/VibrationStats.java
@@ -22,7 +22,7 @@
 import android.os.SystemClock;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -294,18 +294,22 @@
     }
 
     /** Report a call to vibrator method to trigger a vibration as a PWLE. */
-    void reportComposePwle(long halResult, PwleSegment[] segments) {
+    void reportComposePwle(long halResult, PwlePoint[] pwlePoints) {
         mVibratorComposePwleCount++;
-        mVibrationPwleTotalSize += segments.length;
+        mVibrationPwleTotalSize += pwlePoints.length;
 
         if (halResult > 0) {
             // If HAL result is positive then it represents the actual duration of the vibration.
             // Remove the zero-amplitude segments to update the total time the vibrator was ON.
-            for (PwleSegment ramp : segments) {
-                if ((ramp.getStartAmplitude() == 0) && (ramp.getEndAmplitude() == 0)) {
-                    halResult -= ramp.getDuration();
+            for (int i = 0; i < pwlePoints.length - 1; i++) {
+                PwlePoint current = pwlePoints[i];
+                PwlePoint next = pwlePoints[i + 1];
+
+                if (current.getAmplitude() == 0 && next.getAmplitude() == 0) {
+                    halResult -= next.getTimeMillis();
                 }
             }
+
             if (halResult > 0) {
                 mVibratorOnTotalDurationMillis += (int) halResult;
             }
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index f78bff8..acb31ce 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -30,7 +30,7 @@
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
@@ -415,21 +415,21 @@
     }
 
     /**
-     * Plays a composition of pwle v2 primitives, using {@code vibrationId} for completion callback
+     * Plays a composition of pwle v2 points, using {@code vibrationId} for completion callback
      * to {@link OnVibrationCompleteListener}.
      *
      * <p>This will affect the state of {@link #isVibrating()}.
      *
      * @return The duration of the effect playing, or 0 if unsupported.
      */
-    public long on(PwleSegment[] primitives, long vibrationId) {
+    public long on(PwlePoint[] pwlePoints, long vibrationId) {
         Trace.traceBegin(TRACE_TAG_VIBRATOR, "VibratorController#on (PWLE v2)");
         try {
             if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2)) {
                 return 0;
             }
             synchronized (mLock) {
-                long duration = mNativeWrapper.composePwleV2(primitives, vibrationId);
+                long duration = mNativeWrapper.composePwleV2(pwlePoints, vibrationId);
                 if (duration > 0) {
                     mCurrentAmplitude = -1;
                     updateStateAndNotifyListenersLocked(VibratorState.VIBRATING);
@@ -562,7 +562,7 @@
         private static native long performPwleEffect(long nativePtr, RampSegment[] effect,
                 int braking, long vibrationId);
 
-        private static native long performPwleV2Effect(long nativePtr, PwleSegment[] effect,
+        private static native long performPwleV2Effect(long nativePtr, PwlePoint[] effect,
                 long vibrationId);
 
         private static native void setExternalControl(long nativePtr, boolean enabled);
@@ -631,9 +631,9 @@
             return performPwleEffect(mNativePtr, primitives, braking, vibrationId);
         }
 
-        /** Turns vibrator on to perform PWLE effect composed of given primitives. */
-        public long composePwleV2(PwleSegment[] primitives, long vibrationId) {
-            return performPwleV2Effect(mNativePtr, primitives, vibrationId);
+        /** Turns vibrator on to perform PWLE effect composed of given points. */
+        public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
+            return performPwleV2Effect(mNativePtr, pwlePoints, vibrationId);
         }
 
         /** Enabled the device vibrator to be controlled by another service. */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java
index c8d5a03..f09b0a1c6 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperData.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wallpaper;
 
+import static android.app.Flags.liveWallpaperContentHandling;
 import static android.app.WallpaperManager.FLAG_LOCK;
 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
 
@@ -25,6 +26,7 @@
 import static com.android.server.wallpaper.WallpaperUtils.WALLPAPER_LOCK_ORIG;
 import static com.android.server.wallpaper.WallpaperUtils.getWallpaperDir;
 
+import android.annotation.NonNull;
 import android.app.IWallpaperManagerCallback;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager.ScreenOrientation;
@@ -149,6 +151,7 @@
         UNKNOWN,
         CONNECT_LOCKED,
         CONNECTION_TRY_TO_REBIND,
+        FALLBACK_DEFAULT_MISSING,
         INITIALIZE_FALLBACK,
         PACKAGE_UPDATE_FINISHED,
         RESTORE_SETTINGS_LIVE_FAILURE,
@@ -183,7 +186,7 @@
     int mOrientationWhenSet = ORIENTATION_UNKNOWN;
 
     /** Description of the current wallpaper */
-    private WallpaperDescription mDescription;
+    private WallpaperDescription mDescription = new WallpaperDescription.Builder().build();
 
     WallpaperData(int userId, @SetWallpaperFlags int wallpaperType) {
         this.userId = userId;
@@ -212,6 +215,9 @@
         this.primaryColors = source.primaryColors;
         this.mWallpaperDimAmount = source.mWallpaperDimAmount;
         this.connection = source.connection;
+        if (liveWallpaperContentHandling()) {
+            this.setDescription(source.getDescription());
+        }
         if (this.connection != null) {
             this.connection.mWallpaper = this;
         }
@@ -236,19 +242,37 @@
         return result;
     }
 
-    ComponentName getComponent() {
-        return mWallpaperComponent;
+    @NonNull ComponentName getComponent() {
+        if (liveWallpaperContentHandling()) {
+            return mDescription.getComponent();
+        } else {
+            return mWallpaperComponent;
+        }
     }
 
-    void setComponent(ComponentName componentName) {
+    void setComponent(@NonNull ComponentName componentName) {
+        if (liveWallpaperContentHandling()) {
+            throw new IllegalStateException(
+                    "Use \"setDescription\" when content handling is enabled");
+        }
         this.mWallpaperComponent = componentName;
     }
 
-    WallpaperDescription getDescription() {
+    @NonNull WallpaperDescription getDescription() {
         return mDescription;
     }
 
-    void setDescription(WallpaperDescription description) {
+    void setDescription(@NonNull WallpaperDescription description) {
+        if (!liveWallpaperContentHandling()) {
+            throw new IllegalStateException(
+                    "Use \"setContent\" when content handling is disabled");
+        }
+        if (description == null) {
+            throw new IllegalArgumentException("WallpaperDescription must not be null");
+        }
+        if (description.getComponent() == null) {
+            throw new IllegalArgumentException("WallpaperDescription component must not be null");
+        }
         this.mDescription = description;
     }
 
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
index cf76bf0..17a254a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java
@@ -180,15 +180,8 @@
             success = true;
         } catch (FileNotFoundException e) {
             Slog.w(TAG, "no current wallpaper -- first boot?");
-        } catch (NullPointerException e) {
-            Slog.w(TAG, "failed parsing " + file + " " + e);
-        } catch (NumberFormatException e) {
-            Slog.w(TAG, "failed parsing " + file + " " + e);
-        } catch (XmlPullParserException e) {
-            Slog.w(TAG, "failed parsing " + file + " " + e);
-        } catch (IOException e) {
-            Slog.w(TAG, "failed parsing " + file + " " + e);
-        } catch (IndexOutOfBoundsException e) {
+        } catch (NullPointerException | NumberFormatException | XmlPullParserException
+                 | IOException | IndexOutOfBoundsException e) {
             Slog.w(TAG, "failed parsing " + file + " " + e);
         }
         IoUtils.closeQuietly(stream);
@@ -256,12 +249,13 @@
                     }
 
                     ComponentName comp = parseComponentName(parser);
-                    if (removeNextWallpaperComponent()) {
-                        wallpaperToParse.setComponent(comp);
-                    } else {
-                        wallpaperToParse.nextWallpaperComponent = comp;
+                    if (!liveWallpaperContentHandling()) {
+                        if (removeNextWallpaperComponent()) {
+                            wallpaperToParse.setComponent(comp);
+                        } else {
+                            wallpaperToParse.nextWallpaperComponent = comp;
+                        }
                     }
-
                     if (multiCrop()) {
                         parseWallpaperAttributes(parser, wallpaperToParse, keepDimensionHints);
                     }
@@ -745,9 +739,9 @@
 
     private static List<Pair<Integer, String>> screenDimensionPairs() {
         return List.of(
-                new Pair<>(WallpaperManager.PORTRAIT, "Portrait"),
-                new Pair<>(WallpaperManager.LANDSCAPE, "Landscape"),
-                new Pair<>(WallpaperManager.SQUARE_PORTRAIT, "SquarePortrait"),
-                new Pair<>(WallpaperManager.SQUARE_LANDSCAPE, "SquareLandscape"));
+                new Pair<>(WallpaperManager.ORIENTATION_PORTRAIT, "Portrait"),
+                new Pair<>(WallpaperManager.ORIENTATION_LANDSCAPE, "Landscape"),
+                new Pair<>(WallpaperManager.ORIENTATION_SQUARE_PORTRAIT, "SquarePortrait"),
+                new Pair<>(WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE, "SquareLandscape"));
     }
 }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index da9a676..5cff37a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -21,6 +21,7 @@
 import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.Flags.fixWallpaperChanged;
+import static android.app.Flags.liveWallpaperContentHandling;
 import static android.app.Flags.removeNextWallpaperComponent;
 import static android.app.WallpaperManager.COMMAND_REAPPLY;
 import static android.app.WallpaperManager.FLAG_LOCK;
@@ -66,6 +67,8 @@
 import android.app.WallpaperManager;
 import android.app.WallpaperManager.SetWallpaperFlags;
 import android.app.admin.DevicePolicyManagerInternal;
+import android.app.wallpaper.WallpaperDescription;
+import android.app.wallpaper.WallpaperInstance;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -849,16 +852,24 @@
             final DisplayData wpdData =
                     mWallpaperDisplayHelper.getDisplayDataOrCreate(mDisplayId);
             try {
-                connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
-                        wpdData.mWidth, wpdData.mHeight,
-                        wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo);
+                if (liveWallpaperContentHandling()) {
+                    connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
+                            wpdData.mWidth, wpdData.mHeight,
+                            wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo,
+                            wallpaper.getDescription());
+                } else {
+                    connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
+                            wpdData.mWidth, wpdData.mHeight,
+                            wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo,
+                            /* description= */ null);
+                }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed attaching wallpaper on display", e);
                 if (wallpaper != null && !wallpaper.wallpaperUpdating
                         && connection.getConnectedEngineSize() == 0) {
                     wallpaper.mBindSource = BindSource.CONNECT_LOCKED;
-                    bindWallpaperComponentLocked(null /* componentName */, false /* force */,
-                            false /* fromUser */, wallpaper, null /* reply */);
+                    bindWallpaperComponentLocked(null, false /* force */, false /* fromUser */,
+                            wallpaper, null /* reply */);
                 }
             }
             t.traceEnd();
@@ -1324,7 +1335,11 @@
                             if (DEBUG) {
                                 Slog.v(TAG, "static system+lock to system success");
                             }
-                            lockWp.setComponent(mOriginalSystem.getComponent());
+                            if (liveWallpaperContentHandling()) {
+                                lockWp.setDescription(mOriginalSystem.getDescription());
+                            } else {
+                                lockWp.setComponent(mOriginalSystem.getComponent());
+                            }
                             lockWp.connection = mOriginalSystem.connection;
                             lockWp.connection.mWallpaper = lockWp;
                             mOriginalSystem.mWhich = FLAG_LOCK;
@@ -1391,7 +1406,7 @@
                             Slog.i(TAG, "Wallpaper " + wpService + " update has finished");
                         }
                         wallpaper.wallpaperUpdating = false;
-                        clearWallpaperComponentLocked(wallpaper);
+                        detachWallpaperLocked(wallpaper);
                         wallpaper.mBindSource = BindSource.PACKAGE_UPDATE_FINISHED;
                         if (!bindWallpaperComponentLocked(wpService, false, false,
                                 wallpaper, null)) {
@@ -1905,6 +1920,23 @@
             if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false;
             if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false;
 
+            if (liveWallpaperContentHandling()) {
+                final WallpaperDescription description = wallpaper.getDescription();
+                if (!bindWallpaperDescriptionLocked(description, true, false, wallpaper, reply)) {
+                    // We failed to bind the desired wallpaper, but that might
+                    // happen if the wallpaper isn't direct-boot aware
+                    ServiceInfo si = null;
+                    try {
+                        si = mIPackageManager.getServiceInfo(description.getComponent(),
+                                PackageManager.MATCH_DIRECT_BOOT_UNAWARE, wallpaper.userId);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failure starting previous wallpaper; clearing", e);
+                    }
+                    onSwitchWallpaperFailLocked(wallpaper, reply, si);
+                }
+                return;
+            }
+
             final ComponentName cname;
             if (removeNextWallpaperComponent()) {
                 cname = wallpaper.getComponent();
@@ -2001,9 +2033,6 @@
         try {
             if (userId != mCurrentUserId && !hasCrossUserPermission()) return;
 
-            final ComponentName component;
-            final int finalWhich;
-
             // Clear any previous ImageWallpaper related fields
             List<WallpaperData> toClear = new ArrayList<>();
             if ((which & FLAG_LOCK) > 0 && lockWallpaper != null) toClear.add(lockWallpaper);
@@ -2017,19 +2046,34 @@
                 }
             }
 
-            // lock only case: set the system wallpaper component to both screens
-            if (which == FLAG_LOCK) {
-                component = wallpaper.getComponent();
-                finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+            final WallpaperDescription description;
+            final int finalWhich;
+
+            if (liveWallpaperContentHandling()) {
+                if (which == FLAG_LOCK) {
+                    // lock only case: set the system wallpaper component to both screens
+                    description = wallpaper.getDescription();
+                    finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+                } else {
+                    description = new WallpaperDescription.Builder().build();
+                    finalWhich = which;
+                }
             } else {
-                component = null;
-                finalWhich = which;
+                if (which == FLAG_LOCK) {
+                    // lock only case: set the system wallpaper component to both screens
+                    description = new WallpaperDescription.Builder().setComponent(
+                            wallpaper.getComponent()).build();
+                    finalWhich = FLAG_LOCK | FLAG_SYSTEM;
+                } else {
+                    description = new WallpaperDescription.Builder().build();
+                    finalWhich = which;
+                }
             }
 
             // except for the lock case (for which we keep the system wallpaper as-is), force rebind
             boolean force = which != FLAG_LOCK;
-            boolean success = withCleanCallingIdentity(() -> setWallpaperComponentInternal(
-                    component, finalWhich, userId, force, fromForeground, reply));
+            boolean success = withCleanCallingIdentity(() -> setWallpaperDescriptionInternal(
+                    description, finalWhich, userId, force, fromForeground, reply));
             if (success) return;
         } catch (IllegalArgumentException e1) {
             e = e1;
@@ -2040,7 +2084,10 @@
         // let's not let it crash the system and just live with no
         // wallpaper.
         Slog.e(TAG, "Default wallpaper component not found!", e);
-        withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper));
+        withCleanCallingIdentity(() -> {
+            wallpaper.mBindSource = BindSource.FALLBACK_DEFAULT_MISSING;
+            bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper, reply);
+        });
         if (reply != null) {
             try {
                 reply.sendResult(null);
@@ -2412,6 +2459,9 @@
 
     @Override
     public WallpaperInfo getWallpaperInfoWithFlags(@SetWallpaperFlags int which, int userId) {
+        if (liveWallpaperContentHandling()) {
+            return getWallpaperInstance(which, userId, false).getInfo();
+        }
 
         userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
                 Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
@@ -2425,13 +2475,45 @@
             WallpaperInfo info = wallpaper.connection.mInfo;
             if (hasPermission(READ_WALLPAPER_INTERNAL)
                     || mPackageManagerInternal.canQueryPackage(
-                            Binder.getCallingUid(), info.getComponent().getPackageName())) {
+                    Binder.getCallingUid(), info.getComponent().getPackageName())) {
                 return info;
             }
         }
         return null;
     }
 
+    @NonNull
+    @Override
+    public WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId) {
+        return getWallpaperInstance(which, userId, true);
+    }
+
+    private WallpaperInstance getWallpaperInstance(@SetWallpaperFlags int which, int userId,
+            boolean requireReadWallpaper) {
+        final WallpaperInstance defaultInstance = new WallpaperInstance(null,
+                new WallpaperDescription.Builder().build());
+        userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, true, "getWallpaperInfo", null);
+        synchronized (mLock) {
+            WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId)
+                    : mWallpaperMap.get(userId);
+            if (wallpaper == null
+                    || wallpaper.connection == null
+                    || wallpaper.connection.mInfo == null) {
+                return defaultInstance;
+            }
+
+            WallpaperInfo info = wallpaper.connection.mInfo;
+            boolean canQueryPackage = mPackageManagerInternal.canQueryPackage(
+                    Binder.getCallingUid(), info.getComponent().getPackageName());
+            if (hasPermission(READ_WALLPAPER_INTERNAL)
+                    || (canQueryPackage && !requireReadWallpaper)) {
+                return new WallpaperInstance(info, wallpaper.getDescription());
+            }
+        }
+        return defaultInstance;
+    }
+
     @Override
     public ParcelFileDescriptor getWallpaperInfoFile(int userId) {
         synchronized (mLock) {
@@ -3159,11 +3241,11 @@
     }
 
     @Override
-    public void setWallpaperComponentChecked(ComponentName name, String callingPackage,
-            @SetWallpaperFlags int which, int userId) {
+    public void setWallpaperComponentChecked(WallpaperDescription description,
+            String callingPackage, @SetWallpaperFlags int which, int userId) {
 
         if (isWallpaperSupported(callingPackage) && isSetWallpaperAllowed(callingPackage)) {
-            setWallpaperComponent(name, callingPackage, which, userId);
+            setWallpaperDescription(description, callingPackage, which, userId);
         }
     }
 
@@ -3176,12 +3258,23 @@
     @VisibleForTesting
     boolean setWallpaperComponent(ComponentName name, String callingPackage,
             @SetWallpaperFlags int which, int userId) {
-        boolean fromForeground = isFromForegroundApp(callingPackage);
-        return setWallpaperComponentInternal(name, which, userId, false, fromForeground, null);
+        return setWallpaperDescription(
+                new WallpaperDescription.Builder().setComponent(name).build(), callingPackage,
+                which, userId);
     }
 
-    private boolean setWallpaperComponentInternal(ComponentName name,  @SetWallpaperFlags int which,
-            int userIdIn, boolean force, boolean fromForeground, IRemoteCallback reply) {
+    @VisibleForTesting
+    boolean setWallpaperDescription(WallpaperDescription description, String callingPackage,
+            @SetWallpaperFlags int which, int userId) {
+        boolean fromForeground = isFromForegroundApp(callingPackage);
+        return setWallpaperDescriptionInternal(description, which, userId, false, fromForeground,
+                null);
+    }
+
+    private boolean setWallpaperDescriptionInternal(@NonNull WallpaperDescription description,
+            @SetWallpaperFlags int which, int userIdIn, boolean force, boolean fromForeground,
+            IRemoteCallback reply) {
+        ComponentName name = description.getComponent();
         if (DEBUG) {
             Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name);
         }
@@ -3191,11 +3284,11 @@
         checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
 
         boolean shouldNotifyColors = false;
+        final boolean bindSuccess;
 
         // If the lockscreen wallpaper is set to the same as the home screen, notify that the
         // lockscreen wallpaper colors changed, even if we don't bind a new wallpaper engine.
         boolean shouldNotifyLockscreenColors = false;
-        boolean bindSuccess;
         final WallpaperData newWallpaper;
 
         synchronized (mLock) {
@@ -3226,8 +3319,14 @@
                 final WallpaperDestinationChangeHandler
                         liveSync = new WallpaperDestinationChangeHandler(
                         newWallpaper);
-                boolean same = changingToSame(name, newWallpaper.connection,
-                        newWallpaper.getComponent());
+                boolean same;
+                if (liveWallpaperContentHandling()) {
+                    same = changingToSame(description, newWallpaper.connection,
+                            newWallpaper.getDescription());
+                } else {
+                    same = changingToSame(name, newWallpaper.connection,
+                            newWallpaper.getComponent());
+                }
 
                 /*
                  * If we have a shared system+lock wallpaper, and we reapply the same wallpaper
@@ -3238,8 +3337,13 @@
 
                 newWallpaper.mBindSource =
                         (name == null) ? BindSource.SET_LIVE_TO_CLEAR : BindSource.SET_LIVE;
-                bindSuccess = bindWallpaperComponentLocked(name, /* force */
-                        forceRebind, /* fromUser */ true, newWallpaper, reply);
+                if (liveWallpaperContentHandling()) {
+                    bindSuccess = bindWallpaperDescriptionLocked(description, forceRebind,
+                            /* fromUser */ true, newWallpaper, reply);
+                } else {
+                    bindSuccess = bindWallpaperComponentLocked(name, forceRebind,
+                            /* fromUser */ true, newWallpaper, reply);
+                }
                 if (bindSuccess) {
                     if (!same || (offloadColorExtraction() && forceRebind)) {
                         newWallpaper.primaryColors = null;
@@ -3335,6 +3439,24 @@
         return false;
     }
 
+    private boolean changingToSame(WallpaperDescription newDescription,
+            WallpaperConnection currentConnection, WallpaperDescription currentDescription) {
+        if (currentConnection == null) {
+            return false;
+        }
+        if (isDefaultComponent(newDescription.getComponent()) && isDefaultComponent(
+                currentDescription.getComponent())) {
+            if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
+            // Still using default wallpaper.
+            return true;
+        } else if (newDescription.equals(currentDescription)) {
+            // Changing to same wallpaper.
+            if (DEBUG) Slog.v(TAG, "same wallpaper");
+            return true;
+        }
+        return false;
+    }
+
     /*
      * Attempt to bind the wallpaper given by `componentName`, returning true on success otherwise
      * false.
@@ -3352,12 +3474,29 @@
      */
     boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
             boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
+        return bindWallpaperDescriptionLocked(
+                new WallpaperDescription.Builder().setComponent(componentName).build(), force,
+                fromUser, wallpaper, reply);
+    }
+
+    // When `liveWallpaperContentHandling()` is false this acts exactly like the version which takes
+    // a ComponentName argument did: it uses the ComponentName from `description`, it binds the
+    // service given by that component, and updates WallpaperData with that component on success.
+    boolean bindWallpaperDescriptionLocked(WallpaperDescription description, boolean force,
+            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
+        ComponentName componentName = description.getComponent();
         if (DEBUG_LIVE) {
             Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
         }
-        // Has the component changed?
-        if (!force && changingToSame(componentName, wallpaper.connection,
-                wallpaper.getComponent())) {
+        boolean skipBinding;
+        if (liveWallpaperContentHandling()) {
+            skipBinding = !force && changingToSame(description, wallpaper.connection,
+                    wallpaper.getDescription());
+        } else {
+            skipBinding = !force && changingToSame(componentName, wallpaper.connection,
+                    wallpaper.getComponent());
+        }
+        if (skipBinding) {
             try {
                 if (DEBUG_LIVE) {
                     Slog.v(TAG, "Changing to the same component, ignoring");
@@ -3374,6 +3513,9 @@
         try {
             if (componentName == null) {
                 componentName = mDefaultWallpaperComponent;
+                if (liveWallpaperContentHandling()) {
+                    description = description.toBuilder().setComponent(componentName).build();
+                }
             }
             int serviceUserId = wallpaper.userId;
             ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
@@ -3494,7 +3636,11 @@
                 return false;
             }
             maybeDetachLastWallpapers(wallpaper);
-            wallpaper.setComponent(componentName);
+            if (liveWallpaperContentHandling()) {
+                wallpaper.setDescription(description);
+            } else {
+                wallpaper.setComponent(componentName);
+            }
             wallpaper.connection = newConn;
             newConn.mReply = reply;
             updateCurrentWallpapers(wallpaper);
@@ -3618,11 +3764,6 @@
                 });
     }
 
-    private void clearWallpaperComponentLocked(WallpaperData wallpaper) {
-        wallpaper.setComponent(null);
-        detachWallpaperLocked(wallpaper);
-    }
-
     private void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
         t.traceBegin("WPMS.attachServiceLocked");
@@ -3858,6 +3999,7 @@
     }
 
     // Called by SystemBackupAgent after files are restored to disk.
+    // TODO(b/373875373) Remove this method
     public void settingsRestored() {
         // Verify caller is the system
         if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
@@ -3876,6 +4018,14 @@
             ComponentName componentName =
                     removeNextWallpaperComponent() ? wallpaper.getComponent()
                             : wallpaper.nextWallpaperComponent;
+
+            if (liveWallpaperContentHandling()) {
+                // Per b/373875373 this method should be removed, so we just set wallpapers to
+                // default.
+                bindWallpaperDescriptionLocked(new WallpaperDescription.Builder().build(), false,
+                        false, wallpaper, null);
+                return;
+            }
             if (componentName != null && !componentName.equals(mImageWallpaper)) {
                 wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS;
                 if (!bindWallpaperComponentLocked(componentName, false, false,
@@ -3894,15 +4044,20 @@
                     if (DEBUG) Slog.v(TAG, "settingsRestored: name is empty");
                     success = true;
                 } else {
-                    if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource");
+                    if (DEBUG) {
+                        Slog.v(TAG, "settingsRestored: attempting to restore named resource");
+                    }
                     success = mWallpaperDataParser.restoreNamedResourceLocked(wallpaper);
                 }
-                if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success
-                        + " id=" + wallpaper.wallpaperId);
+                if (DEBUG) {
+                    Slog.v(TAG, "settingsRestored: success=" + success + " id="
+                            + wallpaper.wallpaperId);
+                }
                 if (success) {
-                    mWallpaperCropper.generateCrop(wallpaper); // based on the new image + metadata
+                    mWallpaperCropper.generateCrop(
+                            wallpaper); // based on the new image + metadata
                     wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_STATIC;
-                    bindWallpaperComponentLocked(componentName, true, false, wallpaper, null);
+                    bindWallpaperComponentLocked(null, true, false, wallpaper, null);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 744c3da6..e1fa884 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -78,8 +78,13 @@
         mWebViewUpdatedReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
+                    final String action = intent.getAction();
+                    if (action == null) {
+                        return;
+                    }
+
                     int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
-                    switch (intent.getAction()) {
+                    switch (action) {
                         case Intent.ACTION_PACKAGE_REMOVED:
                             // When a package is replaced we will receive two intents, one
                             // representing the removal of the old package and one representing the
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a71b10f..6964300 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -629,6 +629,9 @@
     // The locusId associated with this activity, if set.
     private LocusId mLocusId;
 
+    // Whether the activity is requesting to limit the system's educational dialogs
+    public boolean mShouldLimitSystemEducationDialogs;
+
     // Whether the activity was launched from a bubble.
     private boolean mLaunchedFromBubble;
 
@@ -7300,6 +7303,16 @@
         return mLocusId;
     }
 
+    void setLimitSystemEducationDialogs(boolean limitSystemEducationDialogs) {
+        if (mShouldLimitSystemEducationDialogs == limitSystemEducationDialogs) return;
+        mShouldLimitSystemEducationDialogs = limitSystemEducationDialogs;
+        final Task task = getTask();
+        if (task != null) {
+            final boolean force = isVisibleRequested() && this == task.getTopNonFinishingActivity();
+            getTask().dispatchTaskInfoChangedIfNeeded(force);
+        }
+    }
+
     public void reportScreenCaptured() {
         if (mCaptureCallbacks != null) {
             final int n = mCaptureCallbacks.beginBroadcast();
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index fa5beca..ea6506a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -40,7 +40,6 @@
     @ChangeId
     static final long ENABLE_TOUCH_OPAQUE_ACTIVITIES = 194480991L;
 
-    // TODO(b/369605358) Update EnabledSince when SDK 36 version code is available.
     /**
      * If the app's target SDK is 36+, pass-through touches from a cross-uid overlaying activity is
      * blocked by default. The activity may opt in to receive pass-through touches using
@@ -52,7 +51,7 @@
      * @see ActivityOptions#setAllowPassThroughOnTouchOutside
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.BAKLAVA)
     static final long ENABLE_OVERLAY_TOUCH_PASS_THROUGH_OPT_IN_ENFORCEMENT = 358129114L;
 
     private final ActivityRecord mActivityRecord;
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 6e97f8a..3f24da9 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -478,7 +478,7 @@
                                 intentGrants.merge(creatorIntentGrants);
                             }
                         } catch (SecurityException securityException) {
-                            ActivityStarter.logAndThrowExceptionForIntentRedirect(
+                            ActivityStarter.logAndThrowExceptionForIntentRedirect(mService.mContext,
                                     "Creator URI Grant Caused Exception.", intent, creatorUid,
                                     creatorPackage, filterCallingUid, callingPackage,
                                     securityException);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index e15968d..05a96d9 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -55,6 +55,7 @@
 import static android.content.pm.ActivityInfo.launchModeToString;
 import static android.os.Process.INVALID_UID;
 import static android.security.Flags.preventIntentRedirectAbortOrThrowException;
+import static android.security.Flags.preventIntentRedirectShowToast;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_NONE;
 import static android.view.WindowManager.TRANSIT_OPEN;
@@ -105,6 +106,7 @@
 import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.Overridable;
+import android.content.Context;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -128,12 +130,14 @@
 import android.text.TextUtils;
 import android.util.Pools.SynchronizedPool;
 import android.util.Slog;
+import android.widget.Toast;
 import android.window.RemoteTransition;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.protolog.ProtoLog;
+import com.android.server.UiThread;
 import com.android.server.am.ActivityManagerService.IntentCreatorToken;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.pm.InstantAppResolver;
@@ -614,7 +618,7 @@
             // Check if the Intent was redirected
             if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN)
                     != 0) {
-                ActivityStarter.logAndThrowExceptionForIntentRedirect(
+                logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
                         "Unparceled intent does not have a creator token set.", intent,
                         intentCreatorUid, intentCreatorPackage, resolvedCallingUid,
                         resolvedCallingPackage, null);
@@ -650,7 +654,7 @@
                                 intentGrants.merge(creatorIntentGrants);
                             }
                         } catch (SecurityException securityException) {
-                            ActivityStarter.logAndThrowExceptionForIntentRedirect(
+                            logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
                                     "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
                                     intentCreatorPackage, resolvedCallingUid,
                                     resolvedCallingPackage, securityException);
@@ -674,7 +678,7 @@
                                 intentGrants.merge(creatorIntentGrants);
                             }
                         } catch (SecurityException securityException) {
-                            ActivityStarter.logAndThrowExceptionForIntentRedirect(
+                            logAndThrowExceptionForIntentRedirect(supervisor.mService.mContext,
                                     "Creator URI Grant Caused Exception.", intent, intentCreatorUid,
                                     intentCreatorPackage, resolvedCallingUid,
                                     resolvedCallingPackage, securityException);
@@ -1250,27 +1254,27 @@
                         requestCode, 0, intentCreatorUid, intentCreatorPackage, "",
                         request.ignoreTargetSecurity, inTask != null, null, resultRecord,
                         resultRootTask)) {
-                    abort = logAndAbortForIntentRedirect(
+                    abort = logAndAbortForIntentRedirect(mService.mContext,
                             "Creator checkStartAnyActivityPermission Caused abortion.",
                             intent, intentCreatorUid, intentCreatorPackage, callingUid,
                             callingPackage);
                 }
             } catch (SecurityException e) {
-                logAndThrowExceptionForIntentRedirect(
+                logAndThrowExceptionForIntentRedirect(mService.mContext,
                         "Creator checkStartAnyActivityPermission Caused Exception.",
                         intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage,
                         e);
             }
             if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid,
                     0, resolvedType, aInfo.applicationInfo)) {
-                abort = logAndAbortForIntentRedirect(
+                abort = logAndAbortForIntentRedirect(mService.mContext,
                         "Creator IntentFirewall.checkStartActivity Caused abortion.",
                         intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
             }
 
             if (!mService.getPermissionPolicyInternal().checkStartActivity(intent,
                     intentCreatorUid, intentCreatorPackage)) {
-                abort = logAndAbortForIntentRedirect(
+                abort = logAndAbortForIntentRedirect(mService.mContext,
                         "Creator PermissionPolicyService.checkStartActivity Caused abortion.",
                         intent, intentCreatorUid, intentCreatorPackage, callingUid, callingPackage);
             }
@@ -3596,25 +3600,38 @@
         pw.println(mInTaskFragment);
     }
 
-    static void logAndThrowExceptionForIntentRedirect(@NonNull String message,
-            @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
-            int callingUid, @Nullable String callingPackage,
+    static void logAndThrowExceptionForIntentRedirect(@NonNull Context context,
+            @NonNull String message, @NonNull Intent intent, int intentCreatorUid,
+            @Nullable String intentCreatorPackage, int callingUid, @Nullable String callingPackage,
             @Nullable SecurityException originalException) {
         String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
                 intentCreatorPackage, callingUid, callingPackage);
         Slog.wtf(TAG, msg);
+        if (preventIntentRedirectShowToast()) {
+            UiThread.getHandler().post(
+                    () -> Toast.makeText(context,
+                            "Activity launch blocked. go/report-bug-intentRedir to report a bug",
+                            Toast.LENGTH_LONG).show());
+        }
         if (preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
                 ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid)) {
             throw new SecurityException(msg, originalException);
         }
     }
 
-    private static boolean logAndAbortForIntentRedirect(@NonNull String message,
-            @NonNull Intent intent, int intentCreatorUid, @Nullable String intentCreatorPackage,
-            int callingUid, @Nullable String callingPackage) {
+    private static boolean logAndAbortForIntentRedirect(@NonNull Context context,
+            @NonNull String message, @NonNull Intent intent, int intentCreatorUid,
+            @Nullable String intentCreatorPackage, int callingUid,
+            @Nullable String callingPackage) {
         String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid,
                 intentCreatorPackage, callingUid, callingPackage);
         Slog.wtf(TAG, msg);
+        if (preventIntentRedirectShowToast()) {
+            UiThread.getHandler().post(
+                    () -> Toast.makeText(context,
+                            "Activity launch blocked. go/report-bug-intentRedir to report a bug",
+                            Toast.LENGTH_LONG).show());
+        }
         return preventIntentRedirectAbortOrThrowException() && CompatChanges.isChangeEnabled(
                 ENABLE_PREVENT_INTENT_REDIRECT_TAKE_ACTION, callingUid);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 0745fec..198e14a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1854,6 +1854,8 @@
         }
         assertPackageMatchesCallingUid(callingPackage);
 
+        mAmInternal.addCreatorToken(intent, callingPackage);
+
         final ActivityOptions activityOptions = ActivityOptions.makeBasic();
         activityOptions.setLaunchTaskId(taskId);
         // Pass in the system UID to allow setting launch taskId with MANAGE_GAME_ACTIVITY.
@@ -3913,6 +3915,17 @@
     }
 
     @Override
+    public void setLimitSystemEducationDialogs(
+            IBinder appToken, boolean limitSystemEducationDialogs) {
+        synchronized (mGlobalLock) {
+            final ActivityRecord r = ActivityRecord.isInRootTaskLocked(appToken);
+            if (r != null) {
+                r.setLimitSystemEducationDialogs(limitSystemEducationDialogs);
+            }
+        }
+    }
+
+    @Override
     public boolean updateConfiguration(Configuration values) {
         mAmInternal.enforceCallingPermission(CHANGE_CONFIGURATION, "updateConfiguration()");
 
diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
index f069dcd..d0d3d43 100644
--- a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java
@@ -149,7 +149,7 @@
             @NonNull Configuration newParentConfig) {
         mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy()
                 .findOpaqueNotFinishingActivityBelow()
-                .map(activityRecord -> mSizeCompatScale)
+                .map(ar -> Math.min(1.0f, ar.getCompatScale()))
                 .orElseGet(() -> calculateSizeCompatScale(
                         resolvedAppBounds, containerAppBounds, newParentConfig));
     }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 2fe023e..4ed8b09 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -26,6 +26,8 @@
 import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_FINISH_AND_REMOVE_TASK;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
 
 import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BACK_PREVIEW;
 import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS;
@@ -60,6 +62,7 @@
 import android.window.IBackAnimationFinishedCallback;
 import android.window.IWindowlessStartingSurfaceCallback;
 import android.window.OnBackInvokedCallbackInfo;
+import android.window.SystemOverrideOnBackInvokedCallback;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -226,10 +229,19 @@
                     && callbackInfo.isAnimationCallback());
             mNavigationMonitor.startMonitor(window, navigationObserver);
 
+            int requestOverride = callbackInfo.getOverrideBehavior();
             ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
                             + "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s",
                     currentTask, currentActivity, callbackInfo, window);
-
+            if (requestOverride == OVERRIDE_FINISH_AND_REMOVE_TASK) {
+                final ActivityRecord rootR = currentTask != null ? currentTask.getRootActivity()
+                        : null;
+                if (currentActivity != null && rootR != currentActivity) {
+                    // The top activity is not root activity, the activity cannot remove task when
+                    // finishAndRemoveTask called.
+                    requestOverride = OVERRIDE_UNDEFINED;
+                }
+            }
             // Clear the pointer down outside focus if any.
             mWindowManagerService.clearPointerDownOutsideFocusRunnable();
 
@@ -274,7 +286,8 @@
             } else if (hasTranslucentActivity(currentActivity, prevActivities)) {
                 // skip if one of participant activity is translucent
                 backType = BackNavigationInfo.TYPE_CALLBACK;
-            } else if (prevActivities.size() > 0) {
+            } else if (prevActivities.size() > 0
+                    && requestOverride == SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED) {
                 if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities))
                         && isAllActivitiesCreated(prevActivities)) {
                     // We have another Activity in the same currentTask to go to
@@ -1025,6 +1038,12 @@
             return;
         }
 
+        if (mWindowManagerService.mRoot.mTransitionController.isCollecting()) {
+            Slog.v(TAG, "Skip predictive back transition, another transition is collecting");
+            cancelPendingAnimation();
+            return;
+        }
+
         // Ensure the final animation targets which hidden by transition could be visible.
         for (int i = 0; i < targets.size(); i++) {
             final WindowContainer wc = targets.get(i).mContainer;
@@ -1053,6 +1072,7 @@
             Slog.e(TAG, "Remote animation gone", e);
         }
         mPendingAnimationBuilder = null;
+        mNavigationMonitor.stopMonitorTransition();
     }
 
     /**
@@ -1550,6 +1570,9 @@
             }
 
             void createStartingSurface(@Nullable TaskSnapshot snapshot) {
+                if (Flags.deferPredictiveAnimationIfNoSnapshot() && snapshot == null) {
+                    return;
+                }
                 if (mAdaptors[0].mSwitchType == DIALOG_CLOSE) {
                     return;
                 }
@@ -1851,7 +1874,7 @@
                     tc.requestStartTransition(prepareOpen,
                             null /*startTask */, null /* remoteTransition */,
                             null /* displayChange */);
-                    prepareOpen.setReady(makeVisibles.get(0), true);
+                    prepareOpen.setReady(mCloseTarget, true);
                     return prepareOpen;
                 } else if (mSnapshot == null) {
                     return setLaunchBehind(visibleOpenActivities);
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 1933408..ec171c5 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -49,7 +49,6 @@
 import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
 import static com.android.window.flags.Flags.balImprovedMetrics;
 import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
-import static com.android.window.flags.Flags.balRequireOptInSameUid;
 import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
 import static com.android.window.flags.Flags.balShowToastsBlocked;
 import static com.android.window.flags.Flags.balStrictModeRo;
@@ -359,7 +358,7 @@
             } else if (mIsCallForResult) {
                 mAutoOptInReason = AUTO_OPT_IN_CALL_FOR_RESULT;
                 mAutoOptInCaller = false;
-            } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
+            } else if (callingUid == realCallingUid) {
                 mAutoOptInReason = AUTO_OPT_IN_SAME_UID;
                 mAutoOptInCaller = false;
             } else if (realCallerBackgroundActivityStartMode
@@ -604,7 +603,6 @@
                     .append(balImproveRealCallerVisibilityCheck());
             sb.append("; balRequireOptInByPendingIntentCreator: ")
                     .append(balRequireOptInByPendingIntentCreator());
-            sb.append("; balRequireOptInSameUid: ").append(balRequireOptInSameUid());
             sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
                     .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
             sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
@@ -1164,8 +1162,9 @@
      * or {@link #BAL_BLOCK} if the launch should be blocked
      */
     BalVerdict checkBackgroundActivityStartAllowedByRealCallerInBackground(BalState state) {
-        if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
-                == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+        boolean allowAlways = state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS;
+        if (allowAlways
                 && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) {
             return new BalVerdict(BAL_ALLOW_PERMISSION,
                     /*background*/ false,
@@ -1177,8 +1176,7 @@
                 + state.mRealCallingPid + ", " + state.mRealCallingPackage + ") "
                 + balStartModeToString(
                 state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
-        if (state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
-                == MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
+        if (allowAlways
                 && mService.hasSystemAlertWindowPermission(state.mRealCallingUid,
                 state.mRealCallingPid, state.mRealCallingPackage)) {
             Slog.w(
@@ -1192,7 +1190,7 @@
 
         // if the realCallingUid is a persistent system process, abort if the IntentSender
         // wasn't allowed to start an activity
-        if (state.mAllowBalExemptionForSystemProcess
+        if ((allowAlways || state.mAllowBalExemptionForSystemProcess)
                 && state.mIsRealCallingUidPersistentSystemProcess) {
             return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID,
                     /*background*/ false,
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index c7d57fe..ee07d2e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1735,9 +1735,9 @@
         }
 
         // Show IME over the keyguard if the target allows it.
-        final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible()
-                && win.mIsImWindow && (imeTarget.canShowWhenLocked()
-                        || !imeTarget.canBeHiddenByKeyguard());
+        final boolean showImeOverKeyguard =
+                imeTarget != null && imeTarget.isOnScreen() && win.mIsImWindow && (
+                        imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard());
         if (showImeOverKeyguard) {
             return false;
         }
@@ -3053,7 +3053,7 @@
             @InsetsType int insetsType) {
         for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = insetsState.sourceAt(i);
-            if ((source.getType() & insetsType) == 0 || !source.isVisible()) {
+            if ((source.getType() & insetsType) == 0) {
                 continue;
             }
             if (Rect.intersects(bounds, source.getFrame())) {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e9c6e93..71adb80 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -100,13 +100,13 @@
             // isLeashReadyForDispatching (used to dispatch the leash of the control) is
             // depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here
             // again, so that the control with leash can be eventually dispatched
-            if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending) {
+            if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending) {
                 mGivenInsetsReady = true;
                 ImeTracker.forLogging().onProgress(mStatsToken,
                         ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
                 mStateController.notifyControlChanged(mControlTarget, this);
                 setImeShowing(true);
-            } else if (wasServerVisible && mServerVisible && mGivenInsetsReady
+            } else if (wasServerVisible && isServerVisible() && mGivenInsetsReady
                     && givenInsetsPending) {
                 // If the server visibility didn't change (still visible), and mGivenInsetsReady
                 // is set, we won't call into notifyControlChanged. Therefore, we can reset the
@@ -114,7 +114,7 @@
                 ImeTracker.forLogging().onCancelled(mStatsToken,
                         ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
                 mStatsToken = null;
-            } else if (wasServerVisible && !mServerVisible) {
+            } else if (wasServerVisible && !isServerVisible()) {
                 setImeShowing(false);
             }
         }
@@ -134,11 +134,15 @@
     @Override
     protected boolean isLeashReadyForDispatching() {
         if (android.view.inputmethod.Flags.refactorInsetsController()) {
+            // We should only dispatch the leash, if the following conditions are fulfilled:
+            // 1. parent isLeashReadyForDispatching, 2. mGivenInsetsReady (means there are no
+            // givenInsetsPending), 3. the IME surface is drawn, 4. either the IME is
+            // serverVisible (the unfrozen state)
             final WindowState ws =
                     mWindowContainer != null ? mWindowContainer.asWindowState() : null;
             final boolean isDrawn = ws != null && ws.isDrawn();
             return super.isLeashReadyForDispatching()
-                    && mServerVisible && isDrawn && mGivenInsetsReady;
+                    && isServerVisible() && isDrawn && mGivenInsetsReady;
         } else {
             return super.isLeashReadyForDispatching();
         }
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1d4d6eb..7276007 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -730,6 +730,10 @@
         return mFakeControlTarget;
     }
 
+    boolean isServerVisible() {
+        return mServerVisible;
+    }
+
     boolean isClientVisible() {
         return mClientVisible;
     }
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index 52994c7..3ee2e60 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -152,7 +152,10 @@
                 if (mOpenActivities.isEmpty()) {
                     return false;
                 }
-                if (Flags.alwaysCaptureActivitySnapshot()) {
+                // TODO (b/362183912) always capture activity snapshot will cause performance
+                //  regression, remove flag after ramp up
+                if (!Flags.deferPredictiveAnimationIfNoSnapshot()
+                        && Flags.alwaysCaptureActivitySnapshot()) {
                     return true;
                 }
                 for (int i = mOpenActivities.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b14dd3f..7473acc 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3423,6 +3423,8 @@
                 ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
         info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays;
         info.mTopActivityLocusId = top != null ? top.getLocusId() : null;
+        info.isTopActivityLimitSystemEducationDialogs = top != null
+              ? top.mShouldLimitSystemEducationDialogs : false;
         final Task parentTask = getParent() != null ? getParent().asTask() : null;
         info.parentTaskId = parentTask != null && parentTask.mCreatedByOrganizer
                 ? parentTask.mTaskId
@@ -3464,7 +3466,8 @@
             return null;
         }
         final Rect windowFrame = mainWindow.getFrame();
-        if (top.getBounds().equals(windowFrame)) {
+        final Rect parentFrame = mainWindow.getParentFrame();
+        if (parentFrame.equals(windowFrame)) {
             return null;
         }
         return windowFrame;
@@ -3915,9 +3918,13 @@
             sb.append(" aI=");
             sb.append(affinityIntent.getComponent().flattenToShortString());
         }
-        sb.append(" isResizeable=").append(isResizeable());
-        sb.append(" minWidth=").append(mMinWidth);
-        sb.append(" minHeight=").append(mMinHeight);
+        if (!isResizeable()) {
+            sb.append(" nonResizable");
+        }
+        if (mMinWidth != INVALID_MIN_SIZE || mMinHeight != INVALID_MIN_SIZE) {
+            sb.append(" minWidth=").append(mMinWidth);
+            sb.append(" minHeight=").append(mMinHeight);
+        }
         sb.append('}');
         return stringName = sb.toString();
     }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index fc46600..454e431 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -237,15 +237,16 @@
     private final ArraySet<WindowToken> mVisibleAtTransitionEndTokens = new ArraySet<>();
 
     /**
-     * Set of transient activities (lifecycle initially tied to this transition) and their
+     * Map of transient activities (lifecycle initially tied to this transition) to their
      * restore-below tasks.
      */
     private ArrayMap<ActivityRecord, Task> mTransientLaunches = null;
 
     /**
      * The tasks that may be occluded by the transient activity. Assume the task stack is
-     * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), then A is the restore-below
-     * task, and [B, C] are the transient-hide tasks.
+     * [Home, A(opaque), B(opaque), C(translucent)] (bottom to top), and Home is started in a
+     * transient-launch activity, then A is the restore-below task, and [B, C] are the
+     * transient-hide tasks.
      */
     private ArrayList<Task> mTransientHideTasks;
 
@@ -401,18 +402,26 @@
         mTransientLaunches.put(activity, restoreBelow);
         setTransientLaunchToChanges(activity);
 
-        final Task transientRootTask = activity.getRootTask();
+        final int restoreBelowTaskId = restoreBelow != null ? restoreBelow.mTaskId : -1;
+        ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
+                + "transient-launch restoreBelowTaskId=%d", mSyncId, activity, restoreBelowTaskId);
+
+        final Task transientLaunchRootTask = activity.getRootTask();
         final WindowContainer<?> parent = restoreBelow != null ? restoreBelow.getParent()
-                : (transientRootTask != null ? transientRootTask.getParent() : null);
+                : (transientLaunchRootTask != null ? transientLaunchRootTask.getParent() : null);
         if (parent != null) {
             // Collect all visible tasks which can be occluded by the transient activity to
             // make sure they are in the participants so their visibilities can be updated when
             // finishing transition.
+            // Note: This currently assumes that the parent is a DA containing the full set of
+            //       visible tasks
             parent.forAllTasks(t -> {
                 // Skip transient-launch task
-                if (t == transientRootTask) return false;
+                if (t == transientLaunchRootTask) return false;
                 if (t.isVisibleRequested() && !t.isAlwaysOnTop()) {
                     if (t.isRootTask()) {
+                        ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS,
+                                "  transient hide: taskId=%d", t.mTaskId);
                         mTransientHideTasks.add(t);
                     }
                     if (t.isLeafTask()) {
@@ -442,9 +451,6 @@
             // the gesture threshold.
             activity.getTask().setCanAffectSystemUiFlags(false);
         }
-
-        ProtoLog.v(WmProtoLogGroups.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
-                + "transient-launch", mSyncId, activity);
     }
 
     /** @return whether `wc` is a descendent of a transient-hide window. */
@@ -462,6 +468,8 @@
     /** Returns {@code true} if the task should keep visible if this is a transient transition. */
     boolean isTransientVisible(@NonNull Task task) {
         if (mTransientLaunches == null) return false;
+
+        // Check if all the transient-launch activities are occluded
         int occludedCount = 0;
         final int numTransient = mTransientLaunches.size();
         for (int i = numTransient - 1; i >= 0; --i) {
@@ -486,6 +494,8 @@
             // Let transient-hide activities pause before transition is finished.
             return false;
         }
+
+        // If this task is currently transient-hide, then keep it visible
         return isInTransientHide(task);
     }
 
@@ -608,7 +618,8 @@
     }
 
     /**
-     * Only set flag to the parent tasks and activity itself.
+     * Sets the FLAG_TRANSIENT_LAUNCH flag to all changes associated with the given activity
+     * container and parent tasks.
      */
     private void setTransientLaunchToChanges(@NonNull WindowContainer wc) {
         for (WindowContainer curr = wc; curr != null && mChanges.containsKey(curr);
@@ -774,7 +785,7 @@
         // Only look at tasks, taskfragments, or activities
         if (wc.asTaskFragment() == null && wc.asActivityRecord() == null) return;
         if (!isInTransientHide(wc)) return;
-        info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+        info.mFlags |= ChangeInfo.FLAG_TRANSIENT_HIDE;
     }
 
     private void recordDisplay(DisplayContent dc) {
@@ -2448,6 +2459,13 @@
         sb.append(" type=" + transitTypeToString(mType));
         sb.append(" flags=0x" + Integer.toHexString(mFlags));
         sb.append(" overrideAnimOptions=" + mOverrideOptions);
+        if (!mChanges.isEmpty()) {
+            sb.append(" c=[");
+            for (int i = 0; i < mChanges.size(); i++) {
+                sb.append("\n").append("   ").append(mChanges.valueAt(i).toString());
+            }
+            sb.append("\n]\n");
+        }
         sb.append('}');
         return sb.toString();
     }
@@ -3396,8 +3414,14 @@
          * seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
          */
         private static final int FLAG_SEAMLESS_ROTATION = 1;
+        /**
+         * Identifies the associated WindowContainer as a transient-launch task or activity.
+         */
         private static final int FLAG_TRANSIENT_LAUNCH = 2;
-        private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4;
+        /**
+         * Identifies the associated WindowContainer as a transient-hide task or activity.
+         */
+        private static final int FLAG_TRANSIENT_HIDE = 4;
 
         /** This container explicitly requested no-animation (usually Activity level). */
         private static final int FLAG_CHANGE_NO_ANIMATION = 0x8;
@@ -3429,7 +3453,7 @@
                 FLAG_NONE,
                 FLAG_SEAMLESS_ROTATION,
                 FLAG_TRANSIENT_LAUNCH,
-                FLAG_ABOVE_TRANSIENT_LAUNCH,
+                FLAG_TRANSIENT_HIDE,
                 FLAG_CHANGE_NO_ANIMATION,
                 FLAG_CHANGE_YES_ANIMATION,
                 FLAG_CHANGE_MOVED_TO_TOP,
@@ -3438,7 +3462,7 @@
                 FLAG_BELOW_BACK_GESTURE_ANIMATION
         })
         @Retention(RetentionPolicy.SOURCE)
-        @interface Flag {}
+        @interface ChangeInfoFlag {}
 
         @NonNull final WindowContainer mContainer;
         /**
@@ -3467,7 +3491,7 @@
         @ActivityInfo.Config int mKnownConfigChanges;
 
         /** Extra information about this change. */
-        @Flag int mFlags = FLAG_NONE;
+        @ChangeInfoFlag int mFlags = FLAG_NONE;
 
         /** Snapshot surface and luma, if relevant. */
         SurfaceControl mSnapshot;
@@ -3502,14 +3526,20 @@
 
         @Override
         public String toString() {
-            return mContainer.toString();
+            StringBuilder sb = new StringBuilder(64);
+            sb.append("ChangeInfo{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" container=").append(mContainer);
+            sb.append(" flags=0x").append(Integer.toHexString(mFlags));
+            sb.append('}');
+            return sb.toString();
         }
 
         boolean hasChanged() {
             final boolean currVisible = mContainer.isVisibleRequested();
             // the task including transient launch must promote to root task
             if (currVisible && ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
-                    || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0)
+                    || (mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0)
                     || (mFlags & ChangeInfo.FLAG_BACK_GESTURE_ANIMATION) != 0
                     || (mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) {
                 return true;
@@ -3530,7 +3560,7 @@
 
         @TransitionInfo.TransitionMode
         int getTransitMode(@NonNull WindowContainer wc) {
-            if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
+            if ((mFlags & ChangeInfo.FLAG_TRANSIENT_HIDE) != 0) {
                 return mExistenceChanged ? TRANSIT_CLOSE : TRANSIT_TO_BACK;
             }
             if ((mFlags & ChangeInfo.FLAG_BELOW_BACK_GESTURE_ANIMATION) != 0) {
diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java
index 85a118d..edd9924 100644
--- a/services/core/java/com/android/server/wm/TransparentPolicy.java
+++ b/services/core/java/com/android/server/wm/TransparentPolicy.java
@@ -196,10 +196,11 @@
 
     // We evaluate the case when the policy should not be applied.
     private boolean shouldSkipTransparentPolicy(@Nullable ActivityRecord opaqueActivity) {
-        if (opaqueActivity == null || opaqueActivity.isEmbedded()) {
+        if (opaqueActivity == null || opaqueActivity.isEmbedded()
+                || !opaqueActivity.areBoundsLetterboxed()) {
             // We skip letterboxing if the translucent activity doesn't have any
             // opaque activities beneath or the activity below is embedded which
-            // never has letterbox.
+            // never has letterbox or the activity is not letterboxed at all.
             return true;
         }
         final AppCompatSizeCompatModePolicy scmPolicy = mActivityRecord.mAppCompatController
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6c92ae6..e0c473d 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1117,7 +1117,9 @@
      */
     void onDisplayChanged(DisplayContent dc) {
         if (mDisplayContent != null && mDisplayContent != dc) {
-            mTransitionController.collect(this);
+            if (asWindowState() == null) {
+                mTransitionController.collect(this);
+            }
             // Cancel any change transition queued-up for this container on the old display when
             // this container is moved from the old display.
             mDisplayContent.mClosingChangingContainers.remove(this);
@@ -2119,7 +2121,8 @@
     }
 
     /**
-     * For all tasks at or below this container call the callback.
+     * Calls the given {@param callback} for all tasks in depth-first top-down z-order at or below
+     * this container.
      *
      * @param callback Calls the {@link ToBooleanFunction#apply} method for each task found and
      *                 stops the search if {@link ToBooleanFunction#apply} returns {@code true}.
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 166d74b..dac8f69 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -813,6 +813,10 @@
             mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */);
             if (deferResume) {
                 mService.mTaskSupervisor.endDeferResume();
+                // Transient launching the Recents via HIERARCHY_OP_TYPE_PENDING_INTENT directly
+                // resume the Recents activity with no TRANSACT_EFFECTS_LIFECYCLE. Explicitly
+                // checks if the top resumed activity should be updated after defer-resume ended.
+                mService.mTaskSupervisor.updateTopResumedActivityIfNeeded("endWCT");
             }
             mService.continueWindowLayout();
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 585e617..d01e29b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -258,6 +258,7 @@
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.RefreshRatePolicy.FrameRateVote;
 import com.android.server.wm.SurfaceAnimator.AnimationType;
+import com.android.server.wm.utils.RegionUtils;
 import com.android.window.flags.Flags;
 
 import dalvik.annotation.optimization.NeverCompile;
@@ -2327,7 +2328,8 @@
             if (mStartingData != null && mStartingData.mAssociatedTask == null
                     && mTempConfiguration.windowConfiguration.getRotation()
                             == selfConfiguration.windowConfiguration.getRotation()
-                    && !mTempConfiguration.windowConfiguration.getBounds().equals(getBounds())) {
+                    && !RegionUtils.sizeEquals(
+                            mTempConfiguration.windowConfiguration.getBounds(), getBounds())) {
                 mStartingData.mResizedFromTransfer = true;
                 // Lock the starting window to task, so it won't resize from transfer anymore.
                 mActivityRecord.associateStartingWindowWithTaskIfNeeded();
diff --git a/services/core/java/com/android/server/wm/utils/RegionUtils.java b/services/core/java/com/android/server/wm/utils/RegionUtils.java
index ff23145..6c5da17 100644
--- a/services/core/java/com/android/server/wm/utils/RegionUtils.java
+++ b/services/core/java/com/android/server/wm/utils/RegionUtils.java
@@ -92,4 +92,9 @@
         }
         return area;
     }
+
+    /** Returns whether the sizes between the two Rects are equal. */
+    public static boolean sizeEquals(Rect a, Rect b) {
+        return a.width() == b.width() && a.height() == b.height();
+    }
 }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 416e60f..e4ac826 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -110,6 +110,7 @@
     jmethodID notifyInputDevicesChanged;
     jmethodID notifyTouchpadHardwareState;
     jmethodID notifyTouchpadGestureInfo;
+    jmethodID notifyTouchpadThreeFingerTap;
     jmethodID notifySwitch;
     jmethodID notifyInputChannelBroken;
     jmethodID notifyNoFocusedWindowAnr;
@@ -345,6 +346,7 @@
     void setTouchpadTapDraggingEnabled(bool enabled);
     void setShouldNotifyTouchpadHardwareState(bool enabled);
     void setTouchpadRightClickZoneEnabled(bool enabled);
+    void setTouchpadThreeFingerTapShortcutEnabled(bool enabled);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
     void setNonInteractiveDisplays(const std::set<ui::LogicalDisplayId>& displayIds);
@@ -370,6 +372,7 @@
     void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
                                      int32_t deviceId) override;
     void notifyTouchpadGestureInfo(enum GestureType type, int32_t deviceId) override;
+    void notifyTouchpadThreeFingerTap() override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
             const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -510,6 +513,10 @@
         // into context (a.k.a. "right") clicks.
         bool touchpadRightClickZoneEnabled{false};
 
+        // True to use three-finger tap as a customizable shortcut; false to use it as a
+        // middle-click.
+        bool touchpadThreeFingerTapShortcutEnabled{false};
+
         // True if a pointer icon should be shown for stylus pointers.
         bool stylusPointerIconEnabled{false};
 
@@ -780,6 +787,8 @@
         outConfig->touchpadTapDraggingEnabled = mLocked.touchpadTapDraggingEnabled;
         outConfig->shouldNotifyTouchpadHardwareState = mLocked.shouldNotifyTouchpadHardwareState;
         outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
+        outConfig->touchpadThreeFingerTapShortcutEnabled =
+                mLocked.touchpadThreeFingerTapShortcutEnabled;
 
         outConfig->disabledDevices = mLocked.disabledInputDevices;
 
@@ -1034,6 +1043,13 @@
     checkAndClearExceptionFromCallback(env, "notifyTouchpadGestureInfo");
 }
 
+void NativeInputManager::notifyTouchpadThreeFingerTap() {
+    ATRACE_CALL();
+    JNIEnv* env = jniEnv();
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyTouchpadThreeFingerTap);
+    checkAndClearExceptionFromCallback(env, "notifyTouchpadThreeFingerTap");
+}
+
 std::shared_ptr<KeyCharacterMap> NativeInputManager::getKeyboardLayoutOverlay(
         const InputDeviceIdentifier& identifier,
         const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) {
@@ -1495,6 +1511,22 @@
             InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
 }
 
+void NativeInputManager::setTouchpadThreeFingerTapShortcutEnabled(bool enabled) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.touchpadThreeFingerTapShortcutEnabled == enabled) {
+            return;
+        }
+
+        ALOGI("Setting touchpad three finger tap shortcut to %s.", toString(enabled));
+        mLocked.touchpadThreeFingerTapShortcutEnabled = enabled;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
 void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) {
     bool refresh = false;
 
@@ -2437,6 +2469,11 @@
     im->setTouchpadRightClickZoneEnabled(enabled);
 }
 
+static void nativeSetTouchpadThreeFingerTapShortcutEnabled(JNIEnv* env, jobject nativeImplObj,
+                                                           jboolean enabled) {
+    getNativeInputManager(env, nativeImplObj)->setTouchpadThreeFingerTapShortcutEnabled(enabled);
+}
+
 static void nativeSetShowTouches(JNIEnv* env, jobject nativeImplObj, jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
 
@@ -3119,6 +3156,8 @@
         {"setShouldNotifyTouchpadHardwareState", "(Z)V",
          (void*)nativeSetShouldNotifyTouchpadHardwareState},
         {"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
+        {"setTouchpadThreeFingerTapShortcutEnabled", "(Z)V",
+         (void*)nativeSetTouchpadThreeFingerTapShortcutEnabled},
         {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
         {"setNonInteractiveDisplays", "([I)V", (void*)nativeSetNonInteractiveDisplays},
         {"reloadCalibration", "()V", (void*)nativeReloadCalibration},
@@ -3229,6 +3268,8 @@
     GET_METHOD_ID(gServiceClassInfo.notifyTouchpadGestureInfo, clazz, "notifyTouchpadGestureInfo",
                   "(II)V")
 
+    GET_METHOD_ID(gServiceClassInfo.notifyTouchpadThreeFingerTap, clazz,
+                  "notifyTouchpadThreeFingerTap", "()V")
     GET_METHOD_ID(gServiceClassInfo.notifySwitch, clazz,
             "notifySwitch", "(JII)V");
 
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index 59dbf28..0ecc0a8 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -74,12 +74,10 @@
     jfieldID duration;
 } sRampClassInfo;
 static struct {
-    jfieldID startAmplitude;
-    jfieldID endAmplitude;
-    jfieldID startFrequencyHz;
-    jfieldID endFrequencyHz;
-    jfieldID duration;
-} sPwleClassInfo;
+    jfieldID amplitude;
+    jfieldID frequencyHz;
+    jfieldID timeMillis;
+} sPwlePointClassInfo;
 
 static_assert(static_cast<uint8_t>(V1_0::EffectStrength::LIGHT) ==
               static_cast<uint8_t>(Aidl::EffectStrength::LIGHT));
@@ -191,10 +189,11 @@
 
 static Aidl::PwleV2Primitive pwleV2PrimitiveFromJavaPrimitive(JNIEnv* env, jobject pwleObj) {
     Aidl::PwleV2Primitive pwle;
-    pwle.amplitude = static_cast<float>(env->GetFloatField(pwleObj, sPwleClassInfo.endAmplitude));
+    pwle.amplitude = static_cast<float>(env->GetFloatField(pwleObj, sPwlePointClassInfo.amplitude));
     pwle.frequencyHz =
-            static_cast<float>(env->GetFloatField(pwleObj, sPwleClassInfo.endFrequencyHz));
-    pwle.timeMillis = static_cast<int32_t>(env->GetIntField(pwleObj, sPwleClassInfo.duration));
+            static_cast<float>(env->GetFloatField(pwleObj, sPwlePointClassInfo.frequencyHz));
+    pwle.timeMillis =
+            static_cast<int32_t>(env->GetIntField(pwleObj, sPwlePointClassInfo.timeMillis));
     return pwle;
 }
 
@@ -620,7 +619,7 @@
          (void*)vibratorPerformComposedEffect},
         {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
          (void*)vibratorPerformPwleEffect},
-        {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwleSegment;J)J",
+        {"performPwleV2Effect", "(J[Landroid/os/vibrator/PwlePoint;J)J",
          (void*)vibratorPerformPwleV2Effect},
         {"setExternalControl", "(JZ)V", (void*)vibratorSetExternalControl},
         {"alwaysOnEnable", "(JJJJ)V", (void*)vibratorAlwaysOnEnable},
@@ -647,12 +646,10 @@
     sRampClassInfo.endFrequencyHz = GetFieldIDOrDie(env, rampClass, "mEndFrequencyHz", "F");
     sRampClassInfo.duration = GetFieldIDOrDie(env, rampClass, "mDuration", "I");
 
-    jclass pwleClass = FindClassOrDie(env, "android/os/vibrator/PwleSegment");
-    sPwleClassInfo.startAmplitude = GetFieldIDOrDie(env, pwleClass, "mStartAmplitude", "F");
-    sPwleClassInfo.endAmplitude = GetFieldIDOrDie(env, pwleClass, "mEndAmplitude", "F");
-    sPwleClassInfo.startFrequencyHz = GetFieldIDOrDie(env, pwleClass, "mStartFrequencyHz", "F");
-    sPwleClassInfo.endFrequencyHz = GetFieldIDOrDie(env, pwleClass, "mEndFrequencyHz", "F");
-    sPwleClassInfo.duration = GetFieldIDOrDie(env, pwleClass, "mDuration", "I");
+    jclass pwlePointClass = FindClassOrDie(env, "android/os/vibrator/PwlePoint");
+    sPwlePointClassInfo.amplitude = GetFieldIDOrDie(env, pwlePointClass, "mAmplitude", "F");
+    sPwlePointClassInfo.frequencyHz = GetFieldIDOrDie(env, pwlePointClass, "mFrequencyHz", "F");
+    sPwlePointClassInfo.timeMillis = GetFieldIDOrDie(env, pwlePointClass, "mTimeMillis", "I");
 
     jclass frequencyProfileLegacyClass =
             FindClassOrDie(env, "android/os/VibratorInfo$FrequencyProfileLegacy");
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index f439770..69714f3 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -288,6 +288,7 @@
         AndroidPackage::isUpdatableSystem,
         AndroidPackage::getEmergencyInstaller,
         AndroidPackage::isAllowCrossUidActivitySwitchFromBelow,
+        AndroidPackage::getIntentMatchingFlags,
     )
 
     override fun extraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 349b831..8782b1a 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -55,7 +55,8 @@
         ParsedActivity::getUiOptions,
         ParsedActivity::isSupportsSizeChanges,
         ParsedActivity::getRequiredDisplayCategory,
-        ParsedActivity::getRequireContentUriPermissionFromCaller
+        ParsedActivity::getRequireContentUriPermissionFromCaller,
+        ParsedActivity::getIntentMatchingFlags,
     )
 
     override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
index 1e84470..3e34979 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -38,6 +38,7 @@
         ParsedProvider::isForceUriPermissions,
         ParsedProvider::isMultiProcess,
         ParsedProvider::getInitOrder,
+        ParsedProvider::getIntentMatchingFlags,
     )
 
     override fun mainComponentSubclassExtraParams() = listOf(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
index 79d5a4f..694db47 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
@@ -30,5 +30,6 @@
     override val mainComponentSubclassBaseParams = listOf(
         ParsedService::getForegroundServiceType,
         ParsedService::getPermission,
+        ParsedService::getIntentMatchingFlags,
     )
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
index de53266..fc4cc25 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/state/DisplayStateControllerTest.java
@@ -63,11 +63,12 @@
                 DisplayManagerInternal.DisplayPowerRequest.class);
 
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
                         displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
         assertTrue(Display.STATE_OFF == stateAndReason.first);
-        assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+        assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
         verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
                 Display.STATE_OFF);
         assertEquals(true, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -105,11 +106,12 @@
                 DisplayManagerInternal.DisplayPowerRequest.class);
 
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
                         displayPowerRequest, !DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
         assertTrue(Display.STATE_OFF == stateAndReason.first);
-        assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+        assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
         verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
                 Display.STATE_ON);
         assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -123,11 +125,12 @@
                 DisplayManagerInternal.DisplayPowerRequest.class);
 
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.policyReason = Display.STATE_REASON_MOTION;
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
                         displayPowerRequest, DISPLAY_ENABLED, DISPLAY_IN_TRANSITION);
         assertTrue(Display.STATE_OFF == stateAndReason.first);
-        assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
+        assertTrue(Display.STATE_REASON_MOTION == stateAndReason.second);
         verify(mDisplayPowerProximityStateController).updateProximityState(displayPowerRequest,
                 Display.STATE_ON);
         assertEquals(false, mDisplayStateController.shouldPerformScreenOffTransition());
@@ -141,6 +144,7 @@
                 DisplayManagerInternal.DisplayPowerRequest.class);
 
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.policyReason = Display.STATE_REASON_DEFAULT_POLICY;
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
                         displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
@@ -156,6 +160,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 new DisplayManagerInternal.DisplayPowerRequest();
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        displayPowerRequest.policyReason = Display.STATE_REASON_DRAW_WAKE_LOCK;
         mDisplayStateController.overrideDozeScreenState(
                 Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_OFFLOAD);
 
@@ -172,8 +177,9 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 new DisplayManagerInternal.DisplayPowerRequest();
         displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        displayPowerRequest.policyReason = Display.STATE_REASON_DEFAULT_POLICY;
         mDisplayStateController.overrideDozeScreenState(
-                Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DEFAULT_POLICY);
+                Display.STATE_DOZE_SUSPEND, Display.STATE_REASON_DRAW_WAKE_LOCK);
 
         Pair<Integer, Integer> stateAndReason =
                 mDisplayStateController.updateDisplayState(
@@ -183,6 +189,53 @@
         assertTrue(Display.STATE_REASON_DEFAULT_POLICY == stateAndReason.second);
     }
 
+    @Test
+    public void policyOff_usespolicyReasonFromRequest() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                new DisplayManagerInternal.DisplayPowerRequest();
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        displayPowerRequest.policyReason = Display.STATE_REASON_KEY;
+
+        Pair<Integer, Integer> stateAndReason =
+                mDisplayStateController.updateDisplayState(
+                        displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+        assertTrue(Display.STATE_OFF == stateAndReason.first);
+        assertTrue(Display.STATE_REASON_KEY == stateAndReason.second);
+    }
+
+    @Test
+    public void policyBright_usespolicyReasonFromRequest() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                new DisplayManagerInternal.DisplayPowerRequest();
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+        displayPowerRequest.policyReason = Display.STATE_REASON_DREAM_MANAGER;
+
+        Pair<Integer, Integer> stateAndReason =
+                mDisplayStateController.updateDisplayState(
+                        displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+        assertTrue(Display.STATE_ON == stateAndReason.first);
+        assertTrue(Display.STATE_REASON_DREAM_MANAGER == stateAndReason.second);
+    }
+
+    @Test
+    public void policyRequestHasDozeScreenState_usesPolicyDozeScreenStateReason() {
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                new DisplayManagerInternal.DisplayPowerRequest();
+        displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+        displayPowerRequest.policyReason = Display.STATE_REASON_MOTION;
+        displayPowerRequest.dozeScreenState = Display.STATE_ON;
+        displayPowerRequest.dozeScreenStateReason = Display.STATE_REASON_OFFLOAD;
+
+        Pair<Integer, Integer> stateAndReason =
+                mDisplayStateController.updateDisplayState(
+                        displayPowerRequest, DISPLAY_ENABLED, !DISPLAY_IN_TRANSITION);
+
+        assertTrue(Display.STATE_ON == stateAndReason.first);
+        assertTrue(Display.STATE_REASON_OFFLOAD == stateAndReason.second);
+    }
+
     private void validDisplayState(int policy, int displayState, boolean isEnabled,
             boolean isInTransition) {
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index b005358..4a131558 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -60,6 +60,7 @@
 import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
 import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
 import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
 import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
 import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
 import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -129,6 +130,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
 
 /**
  * Test class for {@link OomAdjuster}.
@@ -899,8 +901,25 @@
 
     @SuppressWarnings("GuardedBy")
     @Test
+    public void testUpdateOomAdj_DoPending_PreviousApp() {
+        testUpdateOomAdj_PreviousApp(apps -> {
+            for (ProcessRecord app : apps) {
+                mProcessStateController.enqueueUpdateTarget(app);
+            }
+            mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE);
+        });
+    }
+
+    @SuppressWarnings("GuardedBy")
+    @Test
     public void testUpdateOomAdj_DoAll_PreviousApp() {
-        final int numberOfApps = 15;
+        testUpdateOomAdj_PreviousApp(apps -> {
+            mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
+        });
+    }
+
+    private void testUpdateOomAdj_PreviousApp(Consumer<ProcessRecord[]> updater) {
+        final int numberOfApps = 105;
         final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
         for (int i = 0; i < numberOfApps; i++) {
             apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
@@ -911,10 +930,11 @@
         }
         setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
         setProcessesToLru(apps);
-        mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
-
+        updater.accept(apps);
         for (int i = 0; i < numberOfApps; i++) {
-            assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+            final int mruIndex = numberOfApps - i - 1;
+            final int expectedAdj = Math.min(PREVIOUS_APP_ADJ + mruIndex, PREVIOUS_APP_MAX_ADJ);
+            assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
                     SCHED_GROUP_BACKGROUND, "previous");
         }
 
@@ -3184,7 +3204,8 @@
         setProcessesToLru(app1, app2);
         mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
 
-        assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+        assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY,
+                PREVIOUS_APP_ADJ + (Flags.oomadjusterPrevLaddering() ? 1 : 0),
                 SCHED_GROUP_BACKGROUND, "recent-provider");
         assertProcStates(app2, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
                 SCHED_GROUP_BACKGROUND, "recent-provider");
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
index 4fac647..809b7bb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS
@@ -2,3 +2,4 @@
 
 per-file ApplicationStartInfoTest.java = yforta@google.com, carmenjackson@google.com, jji@google.com
 per-file CachedAppOptimizerTest.java = file:/PERFORMANCE_OWNERS
+per-file BaseBroadcastQueueTest.java = file:/BROADCASTS_OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
new file mode 100644
index 0000000..8c66fd0
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobServiceContextTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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.job;
+
+import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppGlobals;
+import android.app.job.JobParameters;
+import android.content.Context;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.job.JobServiceContext.JobCallback;
+import com.android.server.job.controllers.JobStatus;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+import java.time.Clock;
+import java.time.Duration;
+import java.time.ZoneOffset;
+
+public class JobServiceContextTest {
+    private static final String TAG = JobServiceContextTest.class.getSimpleName();
+    @ClassRule
+    public static final SetFlagsRule.ClassRule mSetFlagsClassRule = new SetFlagsRule.ClassRule();
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = mSetFlagsClassRule.createSetFlagsRule();
+    @Mock
+    private JobSchedulerService mMockJobSchedulerService;
+    @Mock
+    private JobConcurrencyManager mMockConcurrencyManager;
+    @Mock
+    private JobNotificationCoordinator mMockNotificationCoordinator;
+    @Mock
+    private IBatteryStats.Stub mMockBatteryStats;
+    @Mock
+    private JobPackageTracker mMockJobPackageTracker;
+    @Mock
+    private Looper mMockLooper;
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private JobStatus mMockJobStatus;
+    @Mock
+    private JobParameters mMockJobParameters;
+    @Mock
+    private JobCallback mMockJobCallback;
+    private MockitoSession mMockingSession;
+    private JobServiceContext mJobServiceContext;
+    private Object mLock;
+
+    @Before
+    public void setUp() throws Exception {
+        mMockingSession =
+                mockitoSession()
+                        .initMocks(this)
+                        .mockStatic(AppGlobals.class)
+                        .strictness(Strictness.LENIENT)
+                        .startMocking();
+        JobSchedulerService.sElapsedRealtimeClock =
+                Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
+        doReturn(mock(PowerManager.class)).when(mMockContext).getSystemService(PowerManager.class);
+        doReturn(mMockContext).when(mMockJobSchedulerService).getContext();
+        mLock = new Object();
+        doReturn(mLock).when(mMockJobSchedulerService).getLock();
+        mJobServiceContext =
+                new JobServiceContext(
+                        mMockJobSchedulerService,
+                        mMockConcurrencyManager,
+                        mMockNotificationCoordinator,
+                        mMockBatteryStats,
+                        mMockJobPackageTracker,
+                        mMockLooper);
+        spyOn(mJobServiceContext);
+        mJobServiceContext.setJobParamsLockedForTest(mMockJobParameters);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    private Clock getAdvancedClock(Clock clock, long incrementMs) {
+        return Clock.offset(clock, Duration.ofMillis(incrementMs));
+    }
+
+    private void advanceElapsedClock(long incrementMs) {
+        JobSchedulerService.sElapsedRealtimeClock =
+                getAdvancedClock(JobSchedulerService.sElapsedRealtimeClock, incrementMs);
+    }
+
+    /**
+     * Test that Abandoned jobs that are timed out are stopped with the correct stop reason
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_TimeoutAbandonedJob() {
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+        mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        doReturn(true).when(mMockJobStatus).isAbandoned();
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+        mJobServiceContext.handleOpTimeoutLocked();
+
+        String stopMessage = captor.getValue();
+        assertEquals("timeout while executing and maybe abandoned", stopMessage);
+        verify(mMockJobParameters)
+                .setStopReason(
+                        JobParameters.STOP_REASON_TIMEOUT_ABANDONED,
+                        JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED,
+                        "client timed out and maybe abandoned");
+    }
+
+    /**
+     * Test that non-abandoned jobs that are timed out are stopped with the correct stop reason
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_TimeoutNoAbandonedJob() {
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+        mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        doReturn(false).when(mMockJobStatus).isAbandoned();
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+        mJobServiceContext.handleOpTimeoutLocked();
+
+        String stopMessage = captor.getValue();
+        assertEquals("timeout while executing", stopMessage);
+        verify(mMockJobParameters)
+                .setStopReason(
+                        JobParameters.STOP_REASON_TIMEOUT,
+                        JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+                        "client timed out");
+    }
+
+    /**
+     * Test that abandoned jobs that are timed out while the flag is disabled
+     * are stopped with the correct stop reason
+     */
+    @Test
+    @DisableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_TimeoutAbandonedJob_flagHandleAbandonedJobsDisabled() {
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+        ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+        doNothing().when(mJobServiceContext).sendStopMessageLocked(captor.capture());
+
+        advanceElapsedClock(30 * MINUTE_IN_MILLIS); // 30 minutes
+        mJobServiceContext.setPendingStopReasonLockedForTest(JobParameters.STOP_REASON_UNDEFINED);
+
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        doReturn(true).when(mMockJobStatus).isAbandoned();
+        mJobServiceContext.mVerb = JobServiceContext.VERB_EXECUTING;
+
+        mJobServiceContext.handleOpTimeoutLocked();
+
+        String stopMessage = captor.getValue();
+        assertEquals("timeout while executing", stopMessage);
+        verify(mMockJobParameters)
+                .setStopReason(
+                        JobParameters.STOP_REASON_TIMEOUT,
+                        JobParameters.INTERNAL_STOP_REASON_TIMEOUT,
+                        "client timed out");
+    }
+
+    /**
+     * Test that the JobStatus is marked as abandoned when the JobServiceContext
+     * receives a MSG_HANDLE_ABANDONED_JOB message
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_HandleAbandonedJob() {
+        final int jobId = 123;
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+        doReturn(jobId).when(mMockJobStatus).getJobId();
+
+        mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+        verify(mMockJobStatus).setAbandoned(true);
+    }
+
+    /**
+     * Test that the JobStatus is not marked as abandoned when the
+     * JobServiceContext receives a MSG_HANDLE_ABANDONED_JOB message and the
+     * JobServiceContext is not running a job
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_HandleAbandonedJob_notRunningJob() {
+        final int jobId = 123;
+        mJobServiceContext.setRunningJobLockedForTest(null);
+        mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+
+        mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+        verify(mMockJobStatus, never()).setAbandoned(true);
+    }
+
+    /**
+     * Test that the JobStatus is not marked as abandoned when the
+     * JobServiceContext receives a MSG_HANDLE_ABANDONED_JOB message and the
+     * JobServiceContext is running a job with a different jobId
+     */
+    @Test
+    @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
+    public void testJobServiceContext_HandleAbandonedJob_differentJobId() {
+        final int jobId = 123;
+        final int differentJobId = 456;
+        mJobServiceContext.setRunningJobLockedForTest(mMockJobStatus);
+        mJobServiceContext.setRunningCallbackLockedForTest(mMockJobCallback);
+        doReturn(differentJobId).when(mMockJobStatus).getJobId();
+
+        mJobServiceContext.doHandleAbandonedJob(mMockJobCallback, jobId);
+
+        verify(mMockJobStatus, never()).setAbandoned(true);
+    }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
index f1bf86f..b53f6fb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java
@@ -16,11 +16,11 @@
 
 package com.android.server.wallpaper;
 
-import static android.app.WallpaperManager.LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_LANDSCAPE;
 import static android.app.WallpaperManager.ORIENTATION_UNKNOWN;
-import static android.app.WallpaperManager.PORTRAIT;
-import static android.app.WallpaperManager.SQUARE_LANDSCAPE;
-import static android.app.WallpaperManager.SQUARE_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_PORTRAIT;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_LANDSCAPE;
+import static android.app.WallpaperManager.ORIENTATION_SQUARE_PORTRAIT;
 import static android.app.WallpaperManager.getOrientation;
 import static android.app.WallpaperManager.getRotatedOrientation;
 
@@ -406,15 +406,16 @@
         setUpWithDisplays(STANDARD_DISPLAY);
         Point bitmapSize = new Point(800, 1000);
         SparseArray<Rect> suggestedCrops = new SparseArray<>();
-        suggestedCrops.put(PORTRAIT, new Rect(0, 0, 400, 800));
-        for (int otherOrientation: List.of(LANDSCAPE, SQUARE_LANDSCAPE, SQUARE_PORTRAIT)) {
+        suggestedCrops.put(ORIENTATION_PORTRAIT, new Rect(0, 0, 400, 800));
+        for (int otherOrientation: List.of(ORIENTATION_LANDSCAPE, ORIENTATION_SQUARE_LANDSCAPE,
+                ORIENTATION_SQUARE_PORTRAIT)) {
             suggestedCrops.put(otherOrientation, new Rect(0, 0, 10, 10));
         }
 
         for (boolean rtl : List.of(false, true)) {
             assertThat(mWallpaperCropper.getCrop(
                     new Point(300, 800), bitmapSize, suggestedCrops, rtl))
-                    .isEqualTo(suggestedCrops.get(PORTRAIT));
+                    .isEqualTo(suggestedCrops.get(ORIENTATION_PORTRAIT));
             assertThat(mWallpaperCropper.getCrop(
                     new Point(500, 800), bitmapSize, suggestedCrops, rtl))
                     .isEqualTo(new Rect(0, 0, 500, 800));
@@ -440,8 +441,8 @@
         Point landscape = new Point(PORTRAIT_ONE.y, PORTRAIT_ONE.x);
         Point squarePortrait = SQUARE_PORTRAIT_ONE;
         Point squareLandscape = new Point(SQUARE_PORTRAIT_ONE.y, SQUARE_PORTRAIT_ONE.y);
-        suggestedCrops.put(PORTRAIT, centerOf(bitmapRect, portrait));
-        suggestedCrops.put(SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
+        suggestedCrops.put(ORIENTATION_PORTRAIT, centerOf(bitmapRect, portrait));
+        suggestedCrops.put(ORIENTATION_SQUARE_LANDSCAPE, centerOf(bitmapRect, squareLandscape));
         for (boolean rtl : List.of(false, true)) {
             assertThat(mWallpaperCropper.getCrop(
                     landscape, bitmapSize, suggestedCrops, rtl))
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 1ea3674..db323f1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -46,6 +46,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 
@@ -535,7 +536,8 @@
         doReturn(wallpaper).when(mService).getWallpaperSafeLocked(wallpaper.userId, FLAG_SYSTEM);
         doNothing().when(mService).switchWallpaper(any(), any());
         doReturn(true).when(mService)
-                .bindWallpaperComponentLocked(any(), anyBoolean(), anyBoolean(), any(), any());
+                .bindWallpaperComponentLocked(isA(ComponentName.class), anyBoolean(), anyBoolean(),
+                        any(), any());
         doNothing().when(mService).saveSettingsLocked(wallpaper.userId);
         spyOn(mService.mWallpaperCropper);
         doNothing().when(mService.mWallpaperCropper).generateCrop(wallpaper);
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index 473c8c5..648da65 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -27,10 +27,13 @@
 import static android.os.PowerManager.GO_TO_SLEEP_REASON_TIMEOUT;
 import static android.os.PowerManager.WAKE_REASON_GESTURE;
 import static android.os.PowerManager.WAKE_REASON_PLUGGED_IN;
+import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
 import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
 import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DOZING;
 import static android.os.PowerManagerInternal.WAKEFULNESS_DREAMING;
+import static android.view.Display.STATE_REASON_DEFAULT_POLICY;
+import static android.view.Display.STATE_REASON_MOTION;
 
 import static com.android.server.power.PowerManagerService.USER_ACTIVITY_SCREEN_BRIGHT;
 import static com.android.server.power.PowerManagerService.WAKE_LOCK_DOZE;
@@ -44,6 +47,7 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.hardware.display.DisplayManagerInternal;
 import android.os.PowerManager;
@@ -53,6 +57,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.util.LatencyTracker;
+import com.android.server.power.feature.PowerManagerFlags;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -79,18 +84,23 @@
     private static final float BRIGHTNESS = 0.99f;
     private static final float BRIGHTNESS_DOZE = 0.5f;
 
+    private static final LatencyTracker LATENCY_TRACKER = LatencyTracker.getInstance(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
     private PowerGroup mPowerGroup;
     @Mock private PowerGroup.PowerGroupListener mWakefulnessCallbackMock;
     @Mock private Notifier mNotifier;
     @Mock private DisplayManagerInternal mDisplayManagerInternal;
+    @Mock private PowerManagerFlags mFeatureFlags;
 
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        when(mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()).thenReturn(true);
         mPowerGroup = new PowerGroup(GROUP_ID, mWakefulnessCallbackMock, mNotifier,
                 mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
-                /* supportsSandman= */ true, TIMESTAMP_CREATE);
+                /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
     }
 
     @Test
@@ -101,10 +111,8 @@
                 eq(UID), /* opUid= */anyInt(), /* opPackageName= */ isNull(), /* details= */
                 isNull());
         String details = "wake PowerGroup1";
-        LatencyTracker latencyTracker = LatencyTracker.getInstance(
-                InstrumentationRegistry.getInstrumentation().getContext());
         mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_PLUGGED_IN, details, UID,
-                /* opPackageName= */ null, /* opUid= */ 0, latencyTracker);
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
         verify(mWakefulnessCallbackMock).onWakefulnessChangedLocked(eq(GROUP_ID),
                 eq(WAKEFULNESS_AWAKE), eq(TIMESTAMP2), eq(WAKE_REASON_PLUGGED_IN), eq(UID),
                 /* opUid= */ anyInt(), /* opPackageName= */ isNull(), eq(details));
@@ -247,6 +255,10 @@
 
     @Test
     public void testUpdateWhileAwake_UpdatesDisplayPowerRequest() {
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
         final boolean batterySaverEnabled = true;
         float brightnessFactor = 0.7f;
         PowerSaveState powerSaveState = new PowerSaveState.Builder()
@@ -274,6 +286,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DIM);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_MOTION);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.screenBrightnessOverrideTag.toString()).isEqualTo(tag);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(false);
@@ -288,6 +301,55 @@
     }
 
     @Test
+    public void testWakefulnessReasonInDisplayPowerRequestDisabled_wakefulnessReasonNotPopulated() {
+        when(mFeatureFlags.isPolicyReasonInDisplayPowerRequestEnabled()).thenReturn(false);
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ "my/tag",
+                /* useProximitySensor= */ false,
+                /* boostScreenBrightness= */ false,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
+                /* overrideDrawWakeLock= */ false,
+                new PowerSaveState.Builder().build(),
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
+
+        mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_PLUGGED_IN, "details", UID,
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+        mPowerGroup.updateLocked(/* screenBrightnessOverride= */ BRIGHTNESS,
+                /* overrideTag= */ "my/tag",
+                /* useProximitySensor= */ false,
+                /* boostScreenBrightness= */ false,
+                /* dozeScreenStateOverride= */ Display.STATE_ON,
+                /* dozeScreenStateReason= */ Display.STATE_REASON_DEFAULT_POLICY,
+                /* dozeScreenBrightness= */ BRIGHTNESS_DOZE,
+                /* useNormalBrightnessForDoze= */ false,
+                /* overrideDrawWakeLock= */ false,
+                new PowerSaveState.Builder().build(),
+                /* quiescent= */ false,
+                /* dozeAfterScreenOff= */ false,
+                /* bootCompleted= */ true,
+                /* screenBrightnessBoostInProgress= */ false,
+                /* waitForNegativeProximity= */ false,
+                /* brightWhenDozing= */ false);
+        displayPowerRequest = mPowerGroup.mDisplayPowerRequest;
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
+    }
+
+    @Test
     public void testUpdateWhileDozing_UpdatesDisplayPowerRequest() {
         final boolean useNormalBrightnessForDoze = false;
         final boolean batterySaverEnabled = false;
@@ -319,6 +381,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -363,6 +426,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_DOZE);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -405,6 +469,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -419,6 +484,10 @@
 
     @Test
     public void testUpdateQuiescent() {
+        mPowerGroup.dozeLocked(TIMESTAMP1, UID, GO_TO_SLEEP_REASON_APPLICATION);
+        mPowerGroup.wakeUpLocked(TIMESTAMP2, WAKE_REASON_WAKE_MOTION, "details", UID,
+                /* opPackageName= */ null, /* opUid= */ 0, LATENCY_TRACKER);
+
         final boolean batterySaverEnabled = false;
         float brightnessFactor = 0.3f;
         PowerSaveState powerSaveState = new PowerSaveState.Builder()
@@ -446,6 +515,11 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+        // Note how the reason is STATE_REASON_DEFAULT_POLICY, instead of STATE_REASON_MOTION.
+        // This is because - although there was a wake up request from a motion, the quiescent state
+        // preceded and forced the policy to be OFF, so we ignore the reason associated with the
+        // wake up request.
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -487,6 +561,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_OFF);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -529,6 +604,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -569,6 +645,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -610,6 +687,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
@@ -650,6 +728,7 @@
         DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
                 mPowerGroup.mDisplayPowerRequest;
         assertThat(displayPowerRequest.policy).isEqualTo(POLICY_BRIGHT);
+        assertThat(displayPowerRequest.policyReason).isEqualTo(STATE_REASON_DEFAULT_POLICY);
         assertThat(displayPowerRequest.screenBrightnessOverride).isWithin(PRECISION).of(BRIGHTNESS);
         assertThat(displayPowerRequest.useProximitySensor).isEqualTo(true);
         assertThat(displayPowerRequest.boostScreenBrightness).isEqualTo(true);
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index e9e21de..b10200d 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -70,7 +70,6 @@
 import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.hardware.display.DisplayManagerInternal;
-import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.hardware.power.Boost;
 import android.hardware.power.Mode;
 import android.os.BatteryManager;
@@ -1894,15 +1893,6 @@
     }
 
     @Test
-    public void testBoot_DesiredScreenPolicyShouldBeBright() {
-        createService();
-        startSystem();
-
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
     public void testQuiescentBoot_ShouldBeAsleep() {
         when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
         createService();
@@ -1914,40 +1904,6 @@
     }
 
     @Test
-    public void testQuiescentBoot_DesiredScreenPolicyShouldBeOff() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_OFF);
-    }
-
-    @Test
-    public void testQuiescentBoot_WakeUp_DesiredScreenPolicyShouldBeBright() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-        forceAwake();
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
-    public void testQuiescentBoot_WakeKeyBeforeBootCompleted_AwakeAfterBootCompleted() {
-        when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), any())).thenReturn("1");
-        createService();
-        startSystem();
-
-        mService.getBinderServiceInstance().wakeUp(mClock.now(),
-                PowerManager.WAKE_REASON_UNKNOWN, "testing IPowerManager.wakeUp()", "pkg.name");
-
-        mService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
-        assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
-        assertThat(mService.getDesiredScreenPolicyLocked(Display.DEFAULT_DISPLAY_GROUP)).isEqualTo(
-                DisplayPowerRequest.POLICY_BRIGHT);
-    }
-
-    @Test
     public void testIsAmbientDisplayAvailable_available() {
         createService();
         when(mAmbientDisplayConfigurationMock.ambientDisplayAvailable()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 510dd4d..cf628ad 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -315,7 +315,7 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_PRIVATE_SPACE_BP)
+    @RequiresFlagsEnabled(Flags.FLAG_EFFECTIVE_USER_BP)
     public void testCredentialOwnerIdAsUserId() throws Exception {
         when(mUserManager.getCredentialOwnerProfile(USER_ID)).thenReturn(OWNER_ID);
 
diff --git a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
index e97b48c..24bf6ca 100644
--- a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.security.advancedprotection;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -28,15 +30,20 @@
 import android.os.test.FakePermissionEnforcer;
 import android.os.test.TestLooper;
 import android.provider.Settings;
+import android.security.advancedprotection.AdvancedProtectionFeature;
 import android.security.advancedprotection.IAdvancedProtectionCallback;
 
+import androidx.annotation.NonNull;
+
 import com.android.server.security.advancedprotection.features.AdvancedProtectionHook;
+import com.android.server.security.advancedprotection.features.AdvancedProtectionProvider;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 @SuppressLint("VisibleForTests")
@@ -47,6 +54,7 @@
     private Context mContext;
     private AdvancedProtectionService.AdvancedProtectionStore mStore;
     private TestLooper mLooper;
+    AdvancedProtectionFeature mFeature = new AdvancedProtectionFeature("test-id");
 
     @Before
     public void setup() throws Settings.SettingNotFoundException {
@@ -70,8 +78,9 @@
         };
 
         mLooper = new TestLooper();
+
         mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
-                mPermissionEnforcer, null);
+                mPermissionEnforcer, null, null);
     }
 
     @Test
@@ -93,6 +102,12 @@
         AtomicBoolean callbackCaptor = new AtomicBoolean(false);
         AdvancedProtectionHook hook =
                 new AdvancedProtectionHook(mContext, true) {
+                    @NonNull
+                    @Override
+                    public AdvancedProtectionFeature getFeature() {
+                        return mFeature;
+                    }
+
                     @Override
                     public boolean isAvailable() {
                         return true;
@@ -105,7 +120,7 @@
                 };
 
         mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
-                mPermissionEnforcer, hook);
+                mPermissionEnforcer, hook, null);
         mService.setAdvancedProtectionEnabled(true);
         mLooper.dispatchNext();
 
@@ -117,6 +132,12 @@
         AtomicBoolean callbackCalledCaptor = new AtomicBoolean(false);
         AdvancedProtectionHook hook =
                 new AdvancedProtectionHook(mContext, true) {
+                    @NonNull
+                    @Override
+                    public AdvancedProtectionFeature getFeature() {
+                        return mFeature;
+                    }
+
                     @Override
                     public boolean isAvailable() {
                         return false;
@@ -129,7 +150,8 @@
                 };
 
         mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
-                mPermissionEnforcer, hook);
+                mPermissionEnforcer, hook, null);
+
         mService.setAdvancedProtectionEnabled(true);
         mLooper.dispatchNext();
         assertFalse(callbackCalledCaptor.get());
@@ -140,6 +162,12 @@
         AtomicBoolean callbackCalledCaptor = new AtomicBoolean(false);
         AdvancedProtectionHook hook =
                 new AdvancedProtectionHook(mContext, true) {
+                    @NonNull
+                    @Override
+                    public AdvancedProtectionFeature getFeature() {
+                        return mFeature;
+                    }
+
                     @Override
                     public boolean isAvailable() {
                         return true;
@@ -152,7 +180,7 @@
                 };
 
         mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
-                mPermissionEnforcer, hook);
+                mPermissionEnforcer, hook, null);
         mService.setAdvancedProtectionEnabled(true);
         mLooper.dispatchNext();
         assertTrue(callbackCalledCaptor.get());
@@ -208,6 +236,66 @@
         assertFalse(callbackCalledCaptor.get());
     }
 
+    @Test
+    public void testGetFeatures() {
+        AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature("id-1");
+        AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature("id-2");
+        AdvancedProtectionHook hook = new AdvancedProtectionHook(mContext, true) {
+            @NonNull
+            @Override
+            public AdvancedProtectionFeature getFeature() {
+                return feature1;
+            }
+
+            @Override
+            public boolean isAvailable() {
+                return true;
+            }
+        };
+
+        AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
+            @Override
+            public List<AdvancedProtectionFeature> getFeatures() {
+                return List.of(feature2);
+            }
+        };
+
+        mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+                mPermissionEnforcer, hook, provider);
+        List<AdvancedProtectionFeature> features = mService.getAdvancedProtectionFeatures();
+        assertThat(features, containsInAnyOrder(feature1, feature2));
+    }
+
+    @Test
+    public void testGetFeatures_featureNotAvailable() {
+        AdvancedProtectionFeature feature1 = new AdvancedProtectionFeature("id-1");
+        AdvancedProtectionFeature feature2 = new AdvancedProtectionFeature("id-2");
+        AdvancedProtectionHook hook = new AdvancedProtectionHook(mContext, true) {
+            @NonNull
+            @Override
+            public AdvancedProtectionFeature getFeature() {
+                return feature1;
+            }
+
+            @Override
+            public boolean isAvailable() {
+                return false;
+            }
+        };
+
+        AdvancedProtectionProvider provider = new AdvancedProtectionProvider() {
+            @Override
+            public List<AdvancedProtectionFeature> getFeatures() {
+                return List.of(feature2);
+            }
+        };
+
+        mService = new AdvancedProtectionService(mContext, mStore, mLooper.getLooper(),
+                mPermissionEnforcer, hook, provider);
+        List<AdvancedProtectionFeature> features = mService.getAdvancedProtectionFeatures();
+        assertThat(features, containsInAnyOrder(feature2));
+    }
+
 
     @Test
     public void testSetProtection_withoutPermission() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ac021e1..cbfdc5f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2895,6 +2895,44 @@
     }
 
     @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+            android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+    public void testRemoveScheduledForceGroup_onNotificationCanceled() throws Exception {
+        NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "tag", null,
+                false);
+        when(mGroupHelper.onNotificationPosted(any(), anyBoolean())).thenReturn(false);
+        mService.addEnqueuedNotification(r);
+        NotificationManagerService.PostNotificationRunnable runnable =
+                mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
+                r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
+        runnable.run();
+        waitForIdle();
+
+        // Post an update to the notification
+        NotificationRecord r_update =
+                generateNotificationRecord(mTestNotificationChannel, 0, "tag", null, false);
+        mService.addEnqueuedNotification(r_update);
+        runnable = mService.new PostNotificationRunnable(r_update.getKey(),
+                r_update.getSbn().getPackageName(), r_update.getUid(),
+                mPostNotificationTrackerFactory.newTracker(null));
+        runnable.run();
+        waitForIdle();
+
+        // Cancel the notification
+        mBinderService.cancelNotificationWithTag(r.getSbn().getPackageName(),
+                r.getSbn().getPackageName(), r.getSbn().getTag(),
+                r.getSbn().getId(), r.getSbn().getUserId());
+        waitForIdle();
+
+        mTestableLooper.moveTimeForward(DELAY_FORCE_REGROUP_TIME);
+        waitForIdle();
+
+        // Check that onNotificationPostedWithDelay was canceled
+        verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+        verify(mGroupHelper, never()).onNotificationPostedWithDelay(any(), any(), any());
+    }
+
+    @Test
     @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
     public void testEnqueueNotification_forceGrouped_clearsSummaryFlag() throws Exception {
         final String originalGroupName = "originalGroup";
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 8aa8a84..0933590 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -59,7 +59,7 @@
 import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
@@ -888,7 +888,7 @@
         fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
 
         VibrationEffect effect = VibrationEffect.startWaveformEnvelope()
-                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30)
                 .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20)
                 .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
@@ -902,20 +902,47 @@
         verifyCallbacksTriggered(vibration, Status.FINISHED);
         assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
         assertEquals(Arrays.asList(
-                        expectedPwle(/* startAmplitude= */ 0.0f, /* endAmplitude= */ 0.0f,
-                                /* startFrequencyHz= */ 60f, /* endFrequencyHz= */ 60f,
-                                /* duration= */ 20),
-                        expectedPwle(/* startAmplitude= */ 0.0f, /* endAmplitude= */ 0.3f,
-                                /* startFrequencyHz= */ 60f, /* endFrequencyHz= */ 100f,
-                                /* duration= */ 30),
-                        expectedPwle(/* startAmplitude= */ 0.3f, /* endAmplitude= */ 0.4f,
-                                /* startFrequencyHz= */ 100f, /* endFrequencyHz= */ 120f,
-                                /* duration= */ 20),
-                        expectedPwle(/* startAmplitude= */ 0.4f, /* endAmplitude= */ 0.0f,
-                                /* startFrequencyHz= */ 120f, /* endFrequencyHz= */ 120f,
-                                /* duration= */ 30)
-                ),
-                fakeVibrator.getEffectSegments(vibration.id));
+                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 0),
+                expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
+                expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
+                expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
+                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+        ), fakeVibrator.getEffectPwlePoints(vibration.id));
+
+    }
+
+    @Test
+    @EnableFlags(android.os.vibrator.Flags.FLAG_NORMALIZED_PWLE_EFFECTS)
+    public void vibrate_singleVibratorPwle_withInitialFrequency_runsComposePwleV2() {
+        FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(VIBRATOR_ID);
+        fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_PWLE_EFFECTS_V2);
+        fakeVibrator.setResonantFrequency(150);
+        fakeVibrator.setFrequenciesHz(new float[]{30f, 50f, 100f, 120f, 150f});
+        fakeVibrator.setOutputAccelerationsGs(new float[]{0.3f, 0.5f, 1.0f, 0.8f, 0.6f});
+        fakeVibrator.setMaxEnvelopeEffectSize(10);
+        fakeVibrator.setMinEnvelopeEffectControlPointDurationMillis(20);
+
+        VibrationEffect effect = VibrationEffect.startWaveformEnvelope(/*initialFrequency=*/ 30)
+                .addControlPoint(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30)
+                .addControlPoint(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20)
+                .addControlPoint(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+                .build();
+        HalVibration vibration = startThreadAndDispatcher(effect);
+        waitForCompletion();
+
+        verify(mManagerHooks).noteVibratorOn(eq(UID), eq(100L));
+        verify(mManagerHooks).noteVibratorOff(eq(UID));
+        verify(mControllerCallbacks).onComplete(eq(VIBRATOR_ID), eq(vibration.id));
+        verifyCallbacksTriggered(vibration, Status.FINISHED);
+        assertFalse(mControllers.get(VIBRATOR_ID).isVibrating());
+        assertEquals(Arrays.asList(
+                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 30f, /*timeMillis=*/ 0),
+                expectedPwle(/*amplitude=*/ 0.1f, /*frequencyHz=*/ 60f, /*timeMillis=*/ 20),
+                expectedPwle(/*amplitude=*/ 0.3f, /*frequencyHz=*/ 100f, /*timeMillis=*/ 30),
+                expectedPwle(/*amplitude=*/ 0.4f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 20),
+                expectedPwle(/*amplitude=*/ 0.0f, /*frequencyHz=*/ 120f, /*timeMillis=*/ 30)
+        ), fakeVibrator.getEffectPwlePoints(vibration.id));
 
     }
 
@@ -2013,10 +2040,8 @@
                 duration);
     }
 
-    private VibrationEffectSegment expectedPwle(float startAmplitude, float endAmplitude,
-            float startFrequencyHz, float endFrequencyHz, int duration) {
-        return new PwleSegment(startAmplitude, endAmplitude, startFrequencyHz, endFrequencyHz,
-                duration);
+    private PwlePoint expectedPwle(float amplitude, float frequencyHz, int timeMillis) {
+        return new PwlePoint(amplitude, frequencyHz, timeMillis);
     }
 
     private List<Float> expectedAmplitudes(int... amplitudes) {
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
index bc8db3b..0978f48 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerTest.java
@@ -44,7 +44,7 @@
 import android.os.test.TestLooper;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 
 import androidx.test.InstrumentationRegistry;
@@ -274,9 +274,9 @@
         when(mNativeWrapperMock.composePwleV2(any(), anyLong())).thenReturn(15L);
         VibratorController controller = createController();
 
-        PwleSegment[] primitives = new PwleSegment[]{
-                new PwleSegment(/* startAmplitude= */ 0, /* endAmplitude= */ 1,
-                        /* startFrequencyHz= */ 100, /* endFrequencyHz= */ 200, /* duration= */ 10)
+        PwlePoint[] primitives = new PwlePoint[]{
+                new PwlePoint(/*amplitude=*/ 0, /*frequencyHz=*/ 100, /*timeMillis=*/ 0),
+                new PwlePoint(/*amplitude=*/ 1, /*frequencyHz=*/ 200, /*timeMillis=*/ 10)
         };
         assertEquals(15L, controller.on(primitives, 12));
         assertTrue(controller.isVibrating());
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 2c3e9b2..4dc59c2 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -26,7 +26,7 @@
 import android.os.VibratorInfo;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
-import android.os.vibrator.PwleSegment;
+import android.os.vibrator.PwlePoint;
 import android.os.vibrator.RampSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationEffectSegment;
@@ -50,6 +50,7 @@
     private final Map<Long, PrebakedSegment> mEnabledAlwaysOnEffects = new HashMap<>();
     private final Map<Long, List<VibrationEffectSegment>> mEffectSegments = new TreeMap<>();
     private final Map<Long, List<VibrationEffect.VendorEffect>> mVendorEffects = new TreeMap<>();
+    private final Map<Long, List<PwlePoint>> mEffectPwlePoints = new TreeMap<>();
     private final Map<Long, List<Integer>> mBraking = new HashMap<>();
     private final List<Float> mAmplitudes = new ArrayList<>();
     private final List<Boolean> mExternalControlStates = new ArrayList<>();
@@ -91,6 +92,10 @@
         mVendorEffects.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(vendorEffect);
     }
 
+    void recordEffectPwlePoint(long vibrationId, PwlePoint pwlePoint) {
+        mEffectPwlePoints.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(pwlePoint);
+    }
+
     void recordBraking(long vibrationId, int braking) {
         mBraking.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(braking);
     }
@@ -195,11 +200,11 @@
         }
 
         @Override
-        public long composePwleV2(PwleSegment[] primitives, long vibrationId) {
+        public long composePwleV2(PwlePoint[] pwlePoints, long vibrationId) {
             long duration = 0;
-            for (PwleSegment primitive: primitives) {
-                duration += primitive.getDuration();
-                recordEffectSegment(vibrationId, primitive);
+            for (PwlePoint pwlePoint: pwlePoints) {
+                duration += pwlePoint.getTimeMillis();
+                recordEffectPwlePoint(vibrationId, pwlePoint);
             }
             applyLatency(mOnLatency);
             scheduleListener(duration, vibrationId);
@@ -482,6 +487,28 @@
         return result;
     }
 
+    /** Return list of {@link PwlePoint} played by this controller, in order. */
+    public List<PwlePoint> getEffectPwlePoints(long vibrationId) {
+        if (mEffectPwlePoints.containsKey(vibrationId)) {
+            return new ArrayList<>(mEffectPwlePoints.get(vibrationId));
+        } else {
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * Returns a list of all vibrations' {@link PwlePoint}s, for external-use where vibration
+     * IDs aren't exposed.
+     */
+    public List<PwlePoint> getAllEffectPwlePoints() {
+        // Returns segments in order of vibrationId, which increases over time. TreeMap gives order.
+        ArrayList<PwlePoint> result = new ArrayList<>();
+        for (List<PwlePoint> subList : mEffectPwlePoints.values()) {
+            result.addAll(subList);
+        }
+        return result;
+    }
+
     /** Return list of states set for external control to the fake vibrator hardware. */
     public List<Boolean> getExternalControlStates() {
         return mExternalControlStates;
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index b8cfa7c..00c9691 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -259,6 +259,10 @@
         doReturn(embedded).when(mActivityStack.top()).isEmbedded();
     }
 
+    void setTopActivityHasLetterboxedBounds(boolean letterboxed) {
+        doReturn(letterboxed).when(mActivityStack.top()).areBoundsLetterboxed();
+    }
+
     void setTopActivityVisible(boolean isVisible) {
         doReturn(isVisible).when(mActivityStack.top()).isVisible();
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 20dcdde..d6be915 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -24,7 +24,9 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
 import static android.window.BackNavigationInfo.typeToString;
+import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -103,6 +105,13 @@
 
     @Before
     public void setUp() throws Exception {
+        final TransitionController transitionController = mAtm.getTransitionController();
+        final Transition fakeTransition = new Transition(TRANSIT_PREPARE_BACK_NAVIGATION,
+                0 /* flag */, transitionController, transitionController.mSyncEngine);
+        spyOn(transitionController);
+        doReturn(fakeTransition).when(transitionController)
+                .createTransition(anyInt(), anyInt());
+
         final BackNavigationController original = new BackNavigationController();
         original.setWindowManager(mWm);
         mBackNavigationController = Mockito.spy(original);
@@ -111,6 +120,7 @@
         LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
         mBackAnimationAdapter = mock(BackAnimationAdapter.class);
         doReturn(true).when(mBackAnimationAdapter).isAnimatable(anyInt());
+        Mockito.doNothing().when(mBackNavigationController).startAnimation();
         mNavigationMonitor = mock(BackNavigationController.NavigationMonitor.class);
         mRootHomeTask = initHomeActivity();
     }
@@ -446,7 +456,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                        /* isAnimationCallback = */ false));
+                        /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
 
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -467,7 +477,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                        /* isAnimationCallback = */ true));
+                        /* isAnimationCallback = */ true, OVERRIDE_UNDEFINED));
 
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
@@ -608,7 +618,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                        /* isAnimationCallback = */ false));
+                        /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
 
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertThat(backNavigationInfo).isNull();
@@ -722,7 +732,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_SYSTEM,
-                        /* isAnimationCallback = */ false));
+                        /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
         return callback;
     }
 
@@ -732,7 +742,7 @@
                 new OnBackInvokedCallbackInfo(
                         callback,
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT,
-                        /* isAnimationCallback = */ false));
+                        /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED));
         return callback;
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
index 5187f87..42752c3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java
@@ -217,6 +217,19 @@
         });
     }
 
+    @Test
+    public void testNotApplyStrategyToTranslucentActivitiesOverNotLetterboxedActivities() {
+        runTestScenario((robot) -> {
+            robot.transparentActivity((ta) -> {
+                ta.activity().setTopActivityHasLetterboxedBounds(false);
+                ta.launchTransparentActivityInTask();
+
+                ta.checkTopActivityTransparentPolicyStartNotInvoked();
+                ta.checkTopActivityTransparentPolicyStateIsRunning(/* running */ false);
+            });
+        });
+    }
+
     @EnableFlags(com.android.window.flags.Flags.FLAG_RESPECT_NON_TOP_VISIBLE_FIXED_ORIENTATION)
     @Test
     public void testNotRunStrategyToTranslucentActivitiesIfRespectOrientation() {
@@ -388,6 +401,7 @@
             mTransparentActivityRobot = new AppCompatTransparentActivityRobot(activity());
             // We always create at least an opaque activity in a Task
             activity().createNewTaskWithBaseActivity();
+            activity().setTopActivityHasLetterboxedBounds(true);
         }
 
         @Override
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 6535b9b..0808cc3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -11415,6 +11415,7 @@
         sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});
         sDefaults.putInt(KEY_WEAR_CONNECTIVITY_BT_TO_CELL_DELAY_MS_INT, -1);
         sDefaults.putInt(KEY_WEAR_CONNECTIVITY_EXTEND_BT_TO_CELL_DELAY_ON_WIFI_MS_INT, -1);
+        sDefaults.putInt(KEY_SATELLITE_SOS_MAX_DATAGRAM_SIZE, 255);
     }
 
     /**
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index e332d0f..be02232 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -26,6 +26,7 @@
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
@@ -39,6 +40,7 @@
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyRegistryManager;
 
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.ITelephony;
@@ -61,13 +63,19 @@
 import java.util.stream.Collectors;
 
 /**
- * Manages satellite operations such as provisioning, pointing, messaging, location sharing, etc.
- * To get the object, call {@link Context#getSystemService(String)}.
+ * Manages satellite states such as monitoring enabled state and operations such as provisioning,
+ * pointing, messaging, location sharing, etc.
  *
- * @hide
+ * <p>To get the object, call {@link Context#getSystemService(String)} with
+ * {@link Context#SATELLITE_SERVICE}.
+ *
+ * <p>SatelliteManager is intended for use on devices with feature
+ * {@link PackageManager#FEATURE_TELEPHONY_SATELLITE}. On devices without the feature, the behavior
+ * is not reliable.
  */
+@SystemService(Context.SATELLITE_SERVICE)
+@FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
 @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SATELLITE)
-@SystemApi
 public final class SatelliteManager {
     private static final String TAG = "SatelliteManager";
 
@@ -103,6 +111,8 @@
      */
     @Nullable private final Context mContext;
 
+    private TelephonyRegistryManager mTelephonyRegistryMgr;
+
     /**
      * Create an instance of the SatelliteManager.
      *
@@ -736,6 +746,65 @@
             "android.telephony.METADATA_SATELLITE_MANUAL_CONNECT_P2P_SUPPORT";
 
     /**
+     * Registers a {@link SatelliteStateChangeListener} to receive callbacks when the satellite
+     * state may have changed.
+     *
+     * <p>The callback method is immediately triggered with latest state on invoking this method if
+     * the state change has been notified before.
+     *
+     * @param executor The {@link Executor} where the {@code listener} will be invoked
+     * @param listener The listener to monitor the satellite state change
+     *
+     * @see SatelliteStateChangeListener
+     * @see TelephonyManager#hasCarrierPrivileges()
+     */
+    @FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+    @RequiresPermission(anyOf = {android.Manifest.permission.READ_BASIC_PHONE_STATE,
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PHONE_STATE,
+            "carrier privileges"})
+    public void registerStateChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull SatelliteStateChangeListener listener) {
+        if (mContext == null) {
+            throw new IllegalStateException("Telephony service is null");
+        }
+
+        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (mTelephonyRegistryMgr == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        mTelephonyRegistryMgr.addSatelliteStateChangeListener(executor, listener);
+    }
+
+    /**
+     * Unregisters the {@link SatelliteStateChangeListener} previously registered with
+     * {@link #registerStateChangeListener(Executor, SatelliteStateChangeListener)}.
+     *
+     * <p>It will be a no-op if the {@code listener} is not currently registered.
+     *
+     * @param listener The listener to unregister
+     *
+     * @see SatelliteStateChangeListener
+     * @see TelephonyManager#hasCarrierPrivileges()
+     */
+    @FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+    @RequiresPermission(anyOf = {android.Manifest.permission.READ_BASIC_PHONE_STATE,
+            android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+            android.Manifest.permission.READ_PHONE_STATE,
+            "carrier privileges"})
+    public void unregisterStateChangeListener(@NonNull SatelliteStateChangeListener listener) {
+        if (mContext == null) {
+            throw new IllegalStateException("Telephony service is null");
+        }
+
+        mTelephonyRegistryMgr = mContext.getSystemService(TelephonyRegistryManager.class);
+        if (mTelephonyRegistryMgr == null) {
+            throw new IllegalStateException("Telephony registry service is null");
+        }
+        mTelephonyRegistryMgr.removeSatelliteStateChangeListener(listener);
+    }
+
+    /**
      * Request to enable or disable the satellite modem and demo mode.
      * If satellite modem and cellular modem cannot work concurrently,
      * then this will disable the cellular modem if satellite modem is enabled,
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateChangeListener.java b/telephony/java/android/telephony/satellite/SatelliteStateChangeListener.java
new file mode 100644
index 0000000..3aa910d
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteStateChangeListener.java
@@ -0,0 +1,51 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A listener interface to monitor satellite state change events.
+ *
+ * <p>Call
+ * {@link SatelliteManager#registerStateChangeListener(Executor, SatelliteStateChangeListener)}
+ * to monitor. Call
+ * {@link SatelliteManager#unregisterStateChangeListener(SatelliteStateChangeListener)} to cancel.
+ *
+ * @see SatelliteManager#registerStateChangeListener(Executor, SatelliteStateChangeListener)
+ * @see SatelliteManager#unregisterStateChangeListener(SatelliteStateChangeListener)
+ */
+@FlaggedApi(Flags.FLAG_SATELLITE_STATE_CHANGE_LISTENER)
+public interface SatelliteStateChangeListener {
+    /**
+     * Called when satellite modem enabled state may have changed.
+     *
+     * <p>Note:there is no guarantee that this callback will only be invoked upon a change of state.
+     * In other word, in some cases, the callback may report with the same enabled states. It is the
+     * caller's responsibility to filter uninterested states.
+     *
+     * <p>Note:satellite enabled state is a device state that is NOT associated with subscription or
+     * SIM slot.
+     *
+     * @param isEnabled {@code true} means satellite modem is enabled.
+     */
+    void onEnabledStateChanged(boolean isEnabled);
+}
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 6bf7ff5..3247d1f 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -53,6 +53,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -607,6 +608,12 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public List<IntentFilter> getRegisteredIntentFilters(BroadcastReceiver receiver) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public ComponentName startService(Intent service) {
         throw new UnsupportedOperationException();
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
new file mode 100644
index 0000000..2cd625e
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankDataProcessorTest.java
@@ -0,0 +1,279 @@
+/*
+ * 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.jank.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.jank.Flags;
+import android.app.jank.JankDataProcessor;
+import android.app.jank.StateTracker;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class JankDataProcessorTest {
+
+    private Choreographer mChoreographer;
+    private StateTracker mStateTracker;
+    private JankDataProcessor mJankDataProcessor;
+    private static final int NANOS_PER_MS = 1_000_000;
+    private static String sActivityName;
+    private static ActivityScenario<EmptyActivity> sEmptyActivityActivityScenario;
+    private static final int APP_ID = 25;
+
+    @BeforeClass
+    public static void classSetup() {
+        sEmptyActivityActivityScenario = ActivityScenario.launch(EmptyActivity.class);
+        sActivityName = sEmptyActivityActivityScenario.toString();
+    }
+
+    @AfterClass
+    public static void classTearDown() {
+        sEmptyActivityActivityScenario.close();
+    }
+
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @Before
+    @UiThreadTest
+    public void setup() {
+        mChoreographer = Choreographer.getInstance();
+        mStateTracker = new StateTracker(mChoreographer);
+        mJankDataProcessor = new JankDataProcessor(mStateTracker);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_multipleFramesAndStates_attributesTotalFramesCorrectly() {
+        List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+
+        mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+        long totalFramesAttributed = getTotalFramesCounted();
+
+        // Each state is active for each frame that is passed in, there are two states being tested
+        // which is why jankData.size is multiplied by 2.
+        assertEquals(jankData.size() * 2, totalFramesAttributed);
+    }
+
+    /**
+     * Each JankData frame has an associated vsyncid, only frames that have vsyncids between the
+     * StatData start and end vsyncids should be counted.  This test confirms that if JankData
+     * does not share any frames with the states then no jank stats are added.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_outOfRangeVsyncId_skipOutOfRangeVsyncIds() {
+        List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_outOfRange());
+
+        mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+        assertEquals(0, mJankDataProcessor.getPendingJankStats().size());
+    }
+
+    /**
+     * It's expected to see many duplicate widget states, if a user is scrolling then
+     * pauses and resumes scrolling again, we may get three widget states two of which are the same.
+     * State 1: {Scroll,WidgetId,Scrolling} State 2: {Scroll,WidgetId,None}
+     * State 3: {Scroll,WidgetId,Scrolling}
+     * These duplicate states should coalesce into only one Jank stat. This test confirms that
+     * behavior.
+     */
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_duplicateStates_confirmDuplicatesCoalesce() {
+        // getMockStateData will return 10 states 5 of which are set to none and 5 of which are
+        // scrolling.
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+
+        mJankDataProcessor.processJankData(getMockJankData_vsyncId_inRange(), sActivityName,
+                APP_ID);
+
+        // Confirm the duplicate states are coalesced down to 2 stats 1 for the scrolling state
+        // another for the none state.
+        assertEquals(2, mJankDataProcessor.getPendingJankStats().size());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_inRangeVsyncIds_confirmOnlyInRangeFramesCounted() {
+        List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+        int inRangeFrameCount = jankData.size();
+
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+        mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+        // Two states are active for each frame which is why inRangeFrameCount is multiplied by 2.
+        assertEquals(inRangeFrameCount * 2, getTotalFramesCounted());
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+    public void processJankData_inRangeVsyncIds_confirmHistogramCountMatchesFrameCount() {
+        List<SurfaceControl.JankData> jankData = getMockJankData_vsyncId_inRange();
+        mStateTracker.addPendingStateData(getMockStateData_vsyncId_inRange());
+        mJankDataProcessor.processJankData(jankData, sActivityName, APP_ID);
+
+        long totalFrames = getTotalFramesCounted();
+        long histogramFrames = getHistogramFrameCount();
+
+        assertEquals(totalFrames, histogramFrames);
+    }
+
+    // TODO b/375005277 add tests that cover logging and releasing resources back to pool.
+
+    private long getTotalFramesCounted() {
+        return mJankDataProcessor.getPendingJankStats().values()
+                .stream().mapToLong(stat -> stat.getTotalFrames()).sum();
+    }
+
+    private long getHistogramFrameCount() {
+        long totalHistogramFrames = 0;
+
+        for (JankDataProcessor.PendingJankStat stats :
+                mJankDataProcessor.getPendingJankStats().values()) {
+            int[] overrunHistogram = stats.getFrameOverrunBuckets();
+
+            for (int i = 0; i < overrunHistogram.length; i++) {
+                totalHistogramFrames += overrunHistogram[i];
+            }
+        }
+
+        return totalHistogramFrames;
+    }
+
+    /**
+     * Out of range data will have a mVsyncIdStart and mVsyncIdEnd values set to below 25.
+     */
+    private List<StateTracker.StateData> getMockStateData_vsyncId_outOfRange() {
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>();
+        StateTracker.StateData newStateData = new StateTracker.StateData();
+        newStateData.mVsyncIdEnd = 20;
+        newStateData.mStateDataKey = "Test1_OutBand";
+        newStateData.mVsyncIdStart = 1;
+        newStateData.mWidgetState = "scrolling";
+        newStateData.mWidgetId = "widgetId";
+        newStateData.mWidgetCategory = "Scroll";
+        stateData.add(newStateData);
+
+        newStateData = new StateTracker.StateData();
+        newStateData.mVsyncIdEnd = 24;
+        newStateData.mStateDataKey = "Test1_InBand";
+        newStateData.mVsyncIdStart = 20;
+        newStateData.mWidgetState = "Idle";
+        newStateData.mWidgetId = "widgetId";
+        newStateData.mWidgetCategory = "Scroll";
+        stateData.add(newStateData);
+
+        newStateData = new StateTracker.StateData();
+        newStateData.mVsyncIdEnd = 20;
+        newStateData.mStateDataKey = "Test1_OutBand";
+        newStateData.mVsyncIdStart = 12;
+        newStateData.mWidgetState = "Idle";
+        newStateData.mWidgetId = "widgetId";
+        newStateData.mWidgetCategory = "Scroll";
+        stateData.add(newStateData);
+
+        return stateData;
+    }
+
+    /**
+     * This method returns two unique states, one state is set to scrolling the other is set
+     * to none. Both states will have the same startvsyncid to ensure each state is counted the same
+     * number of times. This keeps logic in asserts easier to reason about. Both states will have
+     * a startVsyncId between 25 and 35.
+     */
+    private List<StateTracker.StateData> getMockStateData_vsyncId_inRange() {
+        ArrayList<StateTracker.StateData> stateData = new ArrayList<StateTracker.StateData>();
+
+        for (int i = 0; i < 10; i++) {
+            StateTracker.StateData newStateData = new StateTracker.StateData();
+            newStateData.mVsyncIdEnd = Long.MAX_VALUE;
+            newStateData.mStateDataKey = "Test1_" + (i % 2 == 0 ? "scrolling" : "none");
+            // Divide i by two to ensure both the scrolling and none states get the same vsyncid
+            // This makes asserts in tests easier to reason about as each state should be counted
+            // the same number of times.
+            newStateData.mVsyncIdStart = 25 + (i / 2);
+            newStateData.mWidgetState = i % 2 == 0 ? "scrolling" : "none";
+            newStateData.mWidgetId = "widgetId";
+            newStateData.mWidgetCategory = "Scroll";
+
+            stateData.add(newStateData);
+        }
+
+        return stateData;
+    }
+
+    /**
+     * In range data will have a frameVsyncId value between 25 and 35.
+     */
+    private List<SurfaceControl.JankData> getMockJankData_vsyncId_inRange() {
+        ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>();
+
+        for (int i = 0; i < 10; i++) {
+            mockData.add(new SurfaceControl.JankData(
+                    /*frameVsyncId*/25 + i,
+                    SurfaceControl.JankData.JANK_NONE,
+                    NANOS_PER_MS * ((long) i),
+                    NANOS_PER_MS * ((long) i),
+                    NANOS_PER_MS * ((long) i)));
+
+        }
+
+        return mockData;
+    }
+
+    /**
+     * Out of range data will have frameVsyncId values below 25.
+     */
+    private List<SurfaceControl.JankData> getMockJankData_vsyncId_outOfRange() {
+        ArrayList<SurfaceControl.JankData> mockData = new ArrayList<>();
+
+        for (int i = 0; i < 10; i++) {
+            mockData.add(new SurfaceControl.JankData(
+                    /*frameVsyncId*/i,
+                    SurfaceControl.JankData.JANK_NONE,
+                    NANOS_PER_MS * ((long) i),
+                    NANOS_PER_MS * ((long) i),
+                    NANOS_PER_MS * ((long) i)));
+
+        }
+
+        return mockData;
+    }
+
+}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 927958e..f2d3229 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -206,6 +206,7 @@
         verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
         verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
         verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
+        verify(native).setTouchpadThreeFingerTapShortcutEnabled(anyBoolean())
         verify(native).setShowTouches(anyBoolean())
         verify(native).setMotionClassifierEnabled(anyBoolean())
         verify(native).setMaximumObscuringOpacityForTouch(anyFloat())
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 19dea0c..0b147d6 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -22,8 +22,10 @@
 import android.content.res.Resources
 import android.hardware.input.IInputManager
 import android.hardware.input.AidlKeyGestureEvent
+import android.hardware.input.AppLaunchData
 import android.hardware.input.IKeyGestureEventListener
 import android.hardware.input.IKeyGestureHandler
+import android.hardware.input.InputGestureData
 import android.hardware.input.InputManager
 import android.hardware.input.InputManagerGlobal
 import android.hardware.input.KeyGestureEvent
@@ -232,7 +234,7 @@
         keyGestureController.handleKeyGesture(/* deviceId = */ 0, intArrayOf(KeyEvent.KEYCODE_HOME),
             /* modifierState = */ 0, KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
             KeyGestureEvent.ACTION_GESTURE_COMPLETE, /* displayId */ 0,
-            /* focusedToken = */ null, /* flags = */ 0
+            /* focusedToken = */ null, /* flags = */ 0, /* appLaunchData = */null
         )
 
         assertEquals(
@@ -259,6 +261,7 @@
         val expectedKeys: IntArray,
         val expectedModifierState: Int,
         val expectedActions: IntArray,
+        val expectedAppLaunchData: AppLaunchData? = null,
     ) {
         override fun toString(): String = name
     }
@@ -884,6 +887,86 @@
     }
 
     @Test
+    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
+    fun testSnapLeftFreeformTask() {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        testKeyGestureInternal(
+            keyGestureController,
+            TestData(
+                "ALT + [ -> Resizes a task to fit the left half of the screen",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_LEFT_BRACKET
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
+    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
+    fun testSnapRightFreeformTask() {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        testKeyGestureInternal(
+            keyGestureController,
+            TestData(
+                "ALT + ] -> Resizes a task to fit the right half of the screen",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_RIGHT_BRACKET
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
+    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
+    fun testMaximizeFreeformTask() {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        testKeyGestureInternal(
+            keyGestureController,
+            TestData(
+                "ALT + '=' -> Maximizes a task to fit the screen",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_EQUALS
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+                intArrayOf(KeyEvent.KEYCODE_EQUALS),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
+    @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
+    fun testRestoreFreeformTask() {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        testKeyGestureInternal(
+            keyGestureController,
+            TestData(
+                "ALT + '-' -> Restores a task size to its previous bounds",
+                intArrayOf(
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_MINUS
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+                intArrayOf(KeyEvent.KEYCODE_MINUS),
+                KeyEvent.META_ALT_ON,
+                intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+            )
+        )
+    }
+
+    @Test
     fun testCapsLockPressNotified() {
         val keyGestureController = KeyGestureController(context, testLooper.looper)
         val listener = KeyGestureEventListener()
@@ -975,6 +1058,62 @@
         testKeyGestureInternal(keyGestureController, test)
     }
 
+    @Keep
+    private fun customInputGesturesTestArguments(): Array<TestData> {
+        return arrayOf(
+            TestData(
+                "META + ALT + Q -> Go Home",
+                intArrayOf(
+                    KeyEvent.KEYCODE_META_LEFT,
+                    KeyEvent.KEYCODE_ALT_LEFT,
+                    KeyEvent.KEYCODE_Q
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+                intArrayOf(KeyEvent.KEYCODE_Q),
+                KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+                intArrayOf(
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                )
+            ),
+            TestData(
+                "META + ALT + Q -> Launch app",
+                intArrayOf(
+                    KeyEvent.KEYCODE_CTRL_LEFT,
+                    KeyEvent.KEYCODE_SHIFT_LEFT,
+                    KeyEvent.KEYCODE_Q
+                ),
+                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+                intArrayOf(KeyEvent.KEYCODE_Q),
+                KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON,
+                intArrayOf(
+                    KeyGestureEvent.ACTION_GESTURE_COMPLETE
+                ),
+                AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+            ),
+        )
+    }
+
+    @Test
+    @Parameters(method = "customInputGesturesTestArguments")
+    fun testCustomKeyGestures(test: TestData) {
+        val keyGestureController = KeyGestureController(context, testLooper.looper)
+        val builder = InputGestureData.Builder()
+            .setKeyGestureType(test.expectedKeyGestureType)
+            .setTrigger(
+                InputGestureData.createKeyTrigger(
+                    test.expectedKeys[0],
+                    test.expectedModifierState
+                )
+            );
+        if (test.expectedAppLaunchData != null) {
+            builder.setAppLaunchData(test.expectedAppLaunchData)
+        }
+        val inputGestureData = builder.build();
+
+        keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData)
+        testKeyGestureInternal(keyGestureController, test)
+    }
+
     private fun testKeyGestureInternal(keyGestureController: KeyGestureController, test: TestData) {
         var handleEvents = mutableListOf<KeyGestureEvent>()
         val handler = KeyGestureHandler { event, _ ->
@@ -1013,6 +1152,11 @@
                 test.expectedActions[i],
                 event.action
             )
+            assertEquals(
+                "Test: $test doesn't produce correct app launch data",
+                test.expectedAppLaunchData,
+                event.appLaunchData
+            )
         }
 
         keyGestureController.unregisterKeyGestureHandler(handler, 0)
diff --git a/tests/graphics/HwAccelerationTest/AndroidManifest.xml b/tests/graphics/HwAccelerationTest/AndroidManifest.xml
index db3a992..05b2f4c 100644
--- a/tests/graphics/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/graphics/HwAccelerationTest/AndroidManifest.xml
@@ -24,7 +24,7 @@
     <uses-feature android:name="android.hardware.camera"/>
     <uses-feature android:name="android.hardware.camera.autofocus"/>
 
-    <uses-sdk android:minSdkVersion="21"/>
+    <uses-sdk android:minSdkVersion="21" />
 
     <application android:label="HwUi"
          android:theme="@android:style/Theme.Material.Light">
@@ -409,6 +409,24 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="ScrollingZAboveSurfaceView"
+            android:label="SurfaceView/Z-Above scrolling"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="ScrollingZAboveScaledSurfaceView"
+            android:label="SurfaceView/Z-Above scrolling, scaled surface"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.android.test.hwui.TEST"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="StretchySurfaceViewActivity"
                   android:label="SurfaceView/Stretchy Movement"
                   android:exported="true">
diff --git a/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml b/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml
new file mode 100644
index 0000000..31e5774
--- /dev/null
+++ b/tests/graphics/HwAccelerationTest/res/layout/scrolling_zabove_surfaceview.xml
@@ -0,0 +1,131 @@
+<?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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    tools:context=".MainActivity">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="Above the ScrollView"
+        android:textColor="#FFFFFFFF"
+        android:background="#FF444444"
+        android:padding="32dp" />
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Header"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <SurfaceView
+                android:layout_width="match_parent"
+                android:layout_height="500dp"
+                android:id="@+id/surfaceview" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="Scrolling Item"
+                android:background="#FFCCCCCC"
+                android:padding="32dp" />
+
+        </LinearLayout>
+
+    </ScrollView>
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="Below the ScrollView"
+        android:textColor="#FFFFFFFF"
+        android:background="#FF444444"
+        android:padding="32dp" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt
new file mode 100644
index 0000000..59ae885
--- /dev/null
+++ b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveScaledSurfaceView.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.test.hwui
+
+import android.app.Activity
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Bundle
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+
+class ScrollingZAboveScaledSurfaceView : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.scrolling_zabove_surfaceview)
+
+        findViewById<SurfaceView>(R.id.surfaceview).apply {
+            setZOrderOnTop(true)
+            holder.setFixedSize(1000, 2000)
+            holder.addCallback(object : SurfaceHolder.Callback {
+                override fun surfaceCreated(p0: SurfaceHolder) {
+
+                }
+
+                override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+                    holder.unlockCanvasAndPost(holder.lockCanvas().apply {
+                        drawColor(Color.BLUE)
+                        val paint = Paint()
+                        paint.textSize = 16 * resources.displayMetrics.density
+                        paint.textAlign = Paint.Align.CENTER
+                        paint.color = Color.WHITE
+                        drawText("I'm a setZOrderOnTop(true) SurfaceView!",
+                            (width / 2).toFloat(), (height / 2).toFloat(), paint)
+                    })
+                }
+
+                override fun surfaceDestroyed(p0: SurfaceHolder) {
+
+                }
+
+            })
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.kt
new file mode 100644
index 0000000..ccb71ec
--- /dev/null
+++ b/tests/graphics/HwAccelerationTest/src/com/android/test/hwui/ScrollingZAboveSurfaceView.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.test.hwui
+
+import android.app.Activity
+import android.graphics.Color
+import android.graphics.Paint
+import android.os.Bundle
+import android.view.SurfaceHolder
+import android.view.SurfaceView
+
+class ScrollingZAboveSurfaceView : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.scrolling_zabove_surfaceview)
+
+        findViewById<SurfaceView>(R.id.surfaceview).apply {
+            setZOrderOnTop(true)
+            holder.addCallback(object : SurfaceHolder.Callback {
+                override fun surfaceCreated(p0: SurfaceHolder) {
+
+                }
+
+                override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+                    holder.unlockCanvasAndPost(holder.lockCanvas().apply {
+                        drawColor(Color.BLUE)
+                        val paint = Paint()
+                        paint.textSize = 16 * resources.displayMetrics.density
+                        paint.textAlign = Paint.Align.CENTER
+                        paint.color = Color.WHITE
+                        drawText("I'm a setZOrderOnTop(true) SurfaceView!",
+                            (width / 2).toFloat(), (height / 2).toFloat(), paint)
+                    })
+                }
+
+                override fun surfaceDestroyed(p0: SurfaceHolder) {
+
+                }
+
+            })
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
index b27b826..ded4679 100644
--- a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
+++ b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
@@ -17,7 +17,13 @@
 package android.animation
 
 import android.animation.AnimatorTestRuleToolkit.Companion.TAG
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
 import android.util.Log
+import android.view.View
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.core.app.ActivityScenario
+import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,13 +42,45 @@
 import platform.test.motion.golden.TimeSeries
 import platform.test.motion.golden.TimeSeriesCaptureScope
 import platform.test.motion.golden.TimestampFrameId
+import platform.test.screenshot.captureToBitmapAsync
 
-class AnimatorTestRuleToolkit(val animatorTestRule: AnimatorTestRule, val testScope: TestScope) {
+class AnimatorTestRuleToolkit(
+    internal val animatorTestRule: AnimatorTestRule,
+    internal val testScope: TestScope,
+    internal val currentActivityScenario: () -> ActivityScenario<*>,
+) {
     internal companion object {
         const val TAG = "AnimatorRuleToolkit"
     }
 }
 
+/** Capture utility to extract a [Bitmap] from a [drawable]. */
+fun captureDrawable(drawable: Drawable): Bitmap {
+    val width = drawable.bounds.right - drawable.bounds.left
+    val height = drawable.bounds.bottom - drawable.bounds.top
+
+    // If either dimension is 0 this will fail, so we set it to 1 pixel instead.
+    return drawable.toBitmap(
+        width =
+        if (width > 0) {
+            width
+        } else {
+            1
+        },
+        height =
+        if (height > 0) {
+            height
+        } else {
+            1
+        },
+    )
+}
+
+/** Capture utility to extract a [Bitmap] from a [view]. */
+fun captureView(view: View): Bitmap {
+    return view.captureToBitmapAsync().get(10, TimeUnit.SECONDS)
+}
+
 /**
  * Controls the timing of the motion recording.
  *
@@ -71,24 +109,39 @@
     /** Time interval between frame captures, in milliseconds. */
     val frameDurationMs: Long = 16L,
 
-    /**  Produces the time-series, invoked on each animation frame. */
+    /** Whether a sequence of screenshots should also be recorded. */
+    val visualCapture: ((captureRoot: T) -> Bitmap)? = null,
+
+    /** Produces the time-series, invoked on each animation frame. */
     val timeSeriesCapture: TimeSeriesCaptureScope<T>.() -> Unit,
 )
 
 /** Records the time-series of the features specified in [recordingSpec]. */
 fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion(
-    recordingSpec: AnimatorRuleRecordingSpec<T>,
+    recordingSpec: AnimatorRuleRecordingSpec<T>
 ): RecordedMotion {
     with(toolkit.animatorTestRule) {
+        val activityScenario = toolkit.currentActivityScenario()
         val frameIdCollector = mutableListOf<FrameId>()
         val propertyCollector = mutableMapOf<String, MutableList<DataPoint<*>>>()
+        val screenshotCollector =
+            if (recordingSpec.visualCapture != null) {
+                mutableListOf<Bitmap>()
+            } else {
+                null
+            }
 
         fun recordFrame(frameId: FrameId) {
             Log.i(TAG, "recordFrame($frameId)")
             frameIdCollector.add(frameId)
-            recordingSpec.timeSeriesCapture.invoke(
-                TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
-            )
+            activityScenario.onActivity {
+                recordingSpec.timeSeriesCapture.invoke(
+                    TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
+                )
+            }
+
+            val bitmap = recordingSpec.visualCapture?.invoke(recordingSpec.captureRoot)
+            if (bitmap != null) screenshotCollector!!.add(bitmap)
         }
 
         val motionControl =
@@ -101,10 +154,13 @@
 
         Log.i(TAG, "recordMotion() begin recording")
 
-        val startFrameTime = currentTime
+        var startFrameTime: Long? = null
+        toolkit.currentActivityScenario().onActivity { startFrameTime = currentTime }
         while (!motionControl.recordingEnded) {
-            recordFrame(TimestampFrameId(currentTime - startFrameTime))
-            motionControl.nextFrame()
+            var time: Long? = null
+            toolkit.currentActivityScenario().onActivity { time = currentTime }
+            recordFrame(TimestampFrameId(time!! - startFrameTime!!))
+            toolkit.currentActivityScenario().onActivity { motionControl.nextFrame() }
         }
 
         Log.i(TAG, "recordMotion() end recording")
@@ -115,7 +171,7 @@
                 propertyCollector.entries.map { entry -> Feature(entry.key, entry.value) },
             )
 
-        return create(timeSeries, null)
+        return create(timeSeries, screenshotCollector)
     }
 }
 
diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp
index 7110564..f0cda53 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -37,10 +37,11 @@
         "androidx.core_core-ktx",
         "androidx.test.ext.junit",
         "androidx.test.rules",
-        "androidx.test.ext.junit",
         "hamcrest-library",
         "kotlinx_coroutines_test",
         "mockito-target-inline-minus-junit4",
+        "platform-screenshot-diff-core",
+        "platform-test-annotations",
         "testables",
         "truth",
     ],
@@ -55,6 +56,7 @@
         "android.test.mock.stubs.system",
     ],
     certificate: "platform",
+    test_config: "AndroidTest.xml",
     test_suites: [
         "device-tests",
         "automotive-tests",
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04f..6cba598 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -23,6 +23,10 @@
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
+        <activity
+            android:name="platform.test.screenshot.ScreenshotActivity"
+            android:exported="true">
+        </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 0000000..85f6e62
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs Tests for Testables.">
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="TestablesTests.apk" />
+        <option name="install-arg" value="-t" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="true" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="screen-always-on" value="on" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+        <option name="run-command" value="wm dismiss-keyguard" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="framework-base-presubmit" />
+    <option name="test-tag" value="TestableTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.testables" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="test-filter-dir" value="/data/data/com.android.testables" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/data/user/0/com.android.testables/files"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+        <option name="clean-up" value="true"/>
+    </metrics_collector>
+</configuration>
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
new file mode 100644
index 0000000..9aed2e9
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withSpring.png b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
new file mode 100644
index 0000000..1d0c0c3
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordMotion_withAnimator.json b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
similarity index 97%
rename from tests/testables/tests/goldens/recordMotion_withAnimator.json
rename to tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
index 87fece5..73eb6c7 100644
--- a/tests/testables/tests/goldens/recordMotion_withAnimator.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
@@ -29,7 +29,7 @@
   ],
   "features": [
     {
-      "name": "value",
+      "name": "alpha",
       "type": "float",
       "data_points": [
         1,
diff --git a/tests/testables/tests/goldens/recordMotion_withSpring.json b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
similarity index 96%
rename from tests/testables/tests/goldens/recordMotion_withSpring.json
rename to tests/testables/tests/goldens/recordTimeSeries_withSpring.json
index e9fb5b4..2b97bad 100644
--- a/tests/testables/tests/goldens/recordMotion_withSpring.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
@@ -21,7 +21,7 @@
   ],
   "features": [
     {
-      "name": "value",
+      "name": "alpha",
       "type": "float",
       "data_points": [
         1,
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
index fbef489..993c3fe 100644
--- a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
@@ -16,10 +16,15 @@
 
 package android.animation
 
-import android.util.FloatProperty
+import android.graphics.Color
+import android.platform.test.annotations.MotionTest
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.dynamicanimation.animation.DynamicAnimation
 import com.android.internal.dynamicanimation.animation.SpringAnimation
 import com.android.internal.dynamicanimation.animation.SpringForce
 import kotlinx.coroutines.test.TestScope
@@ -28,102 +33,169 @@
 import org.junit.runner.RunWith
 import platform.test.motion.MotionTestRule
 import platform.test.motion.RecordedMotion
-import platform.test.motion.golden.FeatureCapture
-import platform.test.motion.golden.asDataPoint
 import platform.test.motion.testing.createGoldenPathManager
+import platform.test.motion.view.ViewFeatureCaptures
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+import platform.test.screenshot.ScreenshotActivity
+import platform.test.screenshot.ScreenshotTestRule
 
 @SmallTest
+@MotionTest
 @RunWith(AndroidJUnit4::class)
 class AnimatorTestRuleToolkitTest {
     companion object {
         private val GOLDEN_PATH_MANAGER =
             createGoldenPathManager("frameworks/base/tests/testables/tests/goldens")
 
-        private val TEST_PROPERTY =
-            object : FloatProperty<TestState>("value") {
-                override fun get(state: TestState): Float {
-                    return state.animatedValue
-                }
-
-                override fun setValue(state: TestState, value: Float) {
-                    state.animatedValue = value
-                }
-            }
+        private val EMULATION_SPEC =
+            DeviceEmulationSpec(DisplaySpec("phone", width = 320, height = 690, densityDpi = 160))
     }
 
-    @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this)
-    @get:Rule(order = 1)
+    @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(EMULATION_SPEC)
+    @get:Rule(order = 1) val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
+    @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
+    @get:Rule(order = 3) val screenshotRule = ScreenshotTestRule(GOLDEN_PATH_MANAGER)
+    @get:Rule(order = 4)
     val motionRule =
-        MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, TestScope()), GOLDEN_PATH_MANAGER)
+        MotionTestRule(
+            AnimatorTestRuleToolkit(animatorTestRule, TestScope()) { activityRule.scenario },
+            GOLDEN_PATH_MANAGER,
+            bitmapDiffer = screenshotRule,
+        )
 
     @Test
-    fun recordMotion_withAnimator() {
-        val state = TestState()
-        AnimatorSet().apply {
-            duration = 500
-            play(
-                ValueAnimator.ofFloat(state.animatedValue, 0f).apply {
-                    addUpdateListener { state.animatedValue = it.animatedValue as Float }
-                }
+    fun recordFilmstrip_withAnimator() {
+        val animatedBox = createScene()
+        createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+        val recordedMotion =
+            record(
+                animatedBox,
+                MotionControl { awaitFrames(count = 26) },
+                sampleIntervalMs = 20L,
+                recordScreenshots = true,
             )
+
+        motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withAnimator")
+    }
+
+    @Test
+    fun recordTimeSeries_withAnimator() {
+        val animatedBox = createScene()
+        createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+        val recordedMotion =
+            record(
+                animatedBox,
+                MotionControl { awaitFrames(count = 26) },
+                sampleIntervalMs = 20L,
+                recordScreenshots = false,
+            )
+
+        motionRule
+            .assertThat(recordedMotion)
+            .timeSeriesMatchesGolden("recordTimeSeries_withAnimator")
+    }
+
+    @Test
+    fun recordFilmstrip_withSpring() {
+        val animatedBox = createScene()
+        var isDone = false
+        createSpring(animatedBox).apply {
+            addEndListener { _, _, _, _ -> isDone = true }
             getInstrumentation().runOnMainSync { start() }
         }
 
         val recordedMotion =
-            record(state, MotionControl { awaitFrames(count = 26) }, sampleIntervalMs = 20L)
+            record(
+                animatedBox,
+                MotionControl { awaitCondition { isDone } },
+                sampleIntervalMs = 16L,
+                recordScreenshots = true,
+            )
 
-        motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withAnimator")
+        motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withSpring")
     }
 
     @Test
-    fun recordMotion_withSpring() {
-        val state = TestState()
+    fun recordTimeSeries_withSpring() {
+        val animatedBox = createScene()
         var isDone = false
-        SpringAnimation(state, TEST_PROPERTY).apply {
+        createSpring(animatedBox).apply {
+            addEndListener { _, _, _, _ -> isDone = true }
+            getInstrumentation().runOnMainSync { start() }
+        }
+
+        val recordedMotion =
+            record(
+                animatedBox,
+                MotionControl { awaitCondition { isDone } },
+                sampleIntervalMs = 16L,
+                recordScreenshots = false,
+            )
+
+        motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordTimeSeries_withSpring")
+    }
+
+    private fun createScene(): ViewGroup {
+        lateinit var sceneRoot: ViewGroup
+        activityRule.scenario.onActivity { activity ->
+            sceneRoot = FrameLayout(activity).apply { setBackgroundColor(Color.BLACK) }
+            activity.setContentView(sceneRoot)
+        }
+        getInstrumentation().waitForIdleSync()
+        return sceneRoot
+    }
+
+    private fun createAnimator(animatedBox: ViewGroup): AnimatorSet {
+        return AnimatorSet().apply {
+            duration = 500
+            play(
+                ValueAnimator.ofFloat(animatedBox.alpha, 0f).apply {
+                    addUpdateListener { animatedBox.alpha = it.animatedValue as Float }
+                }
+            )
+        }
+    }
+
+    private fun createSpring(animatedBox: ViewGroup): SpringAnimation {
+        return SpringAnimation(animatedBox, DynamicAnimation.ALPHA).apply {
             spring =
                 SpringForce(0f).apply {
                     stiffness = 500f
                     dampingRatio = 0.95f
                 }
 
-            setStartValue(1f)
+            setStartValue(animatedBox.alpha)
             setMinValue(0f)
             setMaxValue(1f)
             minimumVisibleChange = 0.01f
-
-            addEndListener { _, _, _, _ -> isDone = true }
-            getInstrumentation().runOnMainSync { start() }
         }
-
-        val recordedMotion =
-            record(state, MotionControl { awaitCondition { isDone } }, sampleIntervalMs = 16L)
-
-        motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withSpring")
     }
 
     private fun record(
-        state: TestState,
+        container: ViewGroup,
         motionControl: MotionControl,
         sampleIntervalMs: Long,
+        recordScreenshots: Boolean,
     ): RecordedMotion {
-        var recordedMotion: RecordedMotion? = null
-        getInstrumentation().runOnMainSync {
-            recordedMotion =
-                motionRule.recordMotion(
-                    AnimatorRuleRecordingSpec(
-                        state,
-                        motionControl,
-                        sampleIntervalMs,
-                    ) {
-                        feature(
-                            FeatureCapture("value") { state -> state.animatedValue.asDataPoint() },
-                            "value",
-                        )
-                    }
-                )
-        }
-        return recordedMotion!!
+        val visualCapture =
+            if (recordScreenshots) {
+                ::captureView
+            } else {
+                null
+            }
+        return motionRule.recordMotion(
+            AnimatorRuleRecordingSpec(
+                container,
+                motionControl,
+                sampleIntervalMs,
+                visualCapture,
+            ) {
+                feature(ViewFeatureCaptures.alpha, "alpha")
+            }
+        )
     }
-
-    data class TestState(var animatedValue: Float = 1f)
 }
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
index a826646..e6eabd8 100644
--- a/tests/utils/testutils/java/android/os/test/TestLooper.java
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -90,17 +90,39 @@
      * and call {@link #dispatchAll()}.
      */
     public TestLooper(Clock clock) {
+        Field messageQueueUseConcurrentField = null;
+        boolean previousUseConcurrentValue = false;
+        try {
+            messageQueueUseConcurrentField = MessageQueue.class.getDeclaredField("sUseConcurrent");
+            messageQueueUseConcurrentField.setAccessible(true);
+            previousUseConcurrentValue = messageQueueUseConcurrentField.getBoolean(null);
+            // If we are using CombinedMessageQueue, we need to disable concurrent mode for testing.
+            messageQueueUseConcurrentField.set(null, false);
+        } catch (NoSuchFieldException e) {
+            // Ignore - maybe this is not CombinedMessageQueue?
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Reflection error constructing or accessing looper", e);
+        }
+
         try {
             mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
 
-            ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
-                    .get(null);
+            ThreadLocal<Looper> threadLocalLooper =
+                    (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD.get(null);
             threadLocalLooper.set(mLooper);
         } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
             throw new RuntimeException("Reflection error constructing or accessing looper", e);
         }
 
         mClock = clock;
+
+        if (messageQueueUseConcurrentField != null) {
+            try {
+                messageQueueUseConcurrentField.set(null, previousUseConcurrentValue);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException("Reflection error constructing or accessing looper", e);
+            }
+        }
     }
 
     public Looper getLooper() {
diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
index e045f10..4c7b25a 100644
--- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java
@@ -223,7 +223,6 @@
         doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider();
         doReturn(mFeatureFlags).when(mVcnContext).getFeatureFlags();
         doReturn(true).when(mVcnContext).isFlagSafeModeTimeoutConfigEnabled();
-        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
 
         doReturn(mUnderlyingNetworkController)
                 .when(mDeps)
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index bc7ff47..441b780 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -20,7 +20,6 @@
 import static com.android.server.vcn.routeselection.UnderlyingNetworkControllerTest.getLinkPropertiesWithName;
 
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -127,8 +126,6 @@
                                 false /* isInTestMode */));
         doNothing().when(mVcnContext).ensureRunningOnLooperThread();
 
-        doReturn(true).when(mVcnContext).isFlagIpSecTransformStateEnabled();
-
         setupSystemService(
                 mContext,
                 mConnectivityManager,
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
index 6f31d8d..e540932 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/UnderlyingNetworkControllerTest.java
@@ -226,7 +226,6 @@
     private void resetVcnContext(VcnContext vcnContext) {
         reset(vcnContext);
         doNothing().when(vcnContext).ensureRunningOnLooperThread();
-        doReturn(true).when(vcnContext).isFlagIpSecTransformStateEnabled();
     }
 
     // Package private for use in NetworkPriorityClassifierTest
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 2285880..108942e 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -368,7 +368,7 @@
     }
 
     interface ProtologViewerConfigBuilder {
-        fun build(statements: Map<LogCall, Long>): ByteArray
+        fun build(groups: Collection<LogGroup>, statements: Map<LogCall, Long>): ByteArray
     }
 
     private fun viewerConf(command: CommandOptions) {
@@ -416,7 +416,7 @@
         }
 
         val outFile = injector.fileOutputStream(command.viewerConfigFileNameArg)
-        outFile.write(configBuilder.build(logCallRegistry.getStatements()))
+        outFile.write(configBuilder.build(groups.values, logCallRegistry.getStatements()))
         outFile.close()
     }
 
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
index 7714db2..16a3d7c 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigJsonBuilder.kt
@@ -21,8 +21,7 @@
 import java.io.StringWriter
 
 class ViewerConfigJsonBuilder : ProtoLogTool.ProtologViewerConfigBuilder {
-    override fun build(statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
-        val groups = statements.map { it.key.logGroup }.toSet()
+    override fun build(groups: Collection<LogGroup>, statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
         val stringWriter = StringWriter()
         val writer = JsonWriter(stringWriter)
         writer.setIndent("  ")
diff --git a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
index 245e802..de85411 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ViewerConfigProtoBuilder.kt
@@ -28,10 +28,14 @@
      * @return a byte array of a ProtoLogViewerConfig proto message encoding all the viewer
      * configurations mapping protolog hashes to message information and log group information.
      */
-    override fun build(statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
+    override fun build(groups: Collection<LogGroup>, statements: Map<ProtoLogTool.LogCall, Long>): ByteArray {
         val configBuilder = ProtoLogViewerConfig.newBuilder()
 
-        val groups = statements.map { it.key.logGroup }.toSet()
+        // TODO(b/373754057): We are passing all the groups now, because some groups might only be
+        //  used by Kotlin code that is not processed, but for group that get enabled to log to
+        //  logcat we try and load the viewer configurations for this group, so the group must exist
+        //  in the viewer config. Once Kotlin is pre-processed or this logic changes we should only
+        //  use the groups that are actually used as an optimization.
         val groupIds = mutableMapOf<LogGroup, Int>()
         groups.forEach {
             groupIds.putIfAbsent(it, groupIds.size + 1)
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
index d27ae88..1a20d4c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigJsonBuilderTest.kt
@@ -36,6 +36,8 @@
         private val GROUP_DISABLED = LogGroup("DEBUG_GROUP", false, true, TAG2)
         private val GROUP_TEXT_DISABLED = LogGroup("DEBUG_GROUP", true, false, TAG2)
         private const val PATH = "/tmp/test.java"
+
+        private val GROUPS = listOf(GROUP1, GROUP2, GROUP3)
     }
 
     private val configBuilder = ViewerConfigJsonBuilder()
@@ -53,7 +55,7 @@
                 LogCall(TEST3.messageString, LogLevel.ERROR, GROUP3, PATH)))
 
         val parsedConfig = parseConfig(
-            configBuilder.build(logCallRegistry.getStatements()).toString(Charsets.UTF_8))
+            configBuilder.build(GROUPS, logCallRegistry.getStatements()).toString(Charsets.UTF_8))
         assertEquals(3, parsedConfig.size)
         assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH,
 	           TEST1.messageString, LogLevel.INFO, GROUP1)])
@@ -72,7 +74,7 @@
                 LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH)))
 
         val parsedConfig = parseConfig(
-            configBuilder.build(logCallRegistry.getStatements()).toString(Charsets.UTF_8))
+            configBuilder.build(GROUPS, logCallRegistry.getStatements()).toString(Charsets.UTF_8))
         assertEquals(1, parsedConfig.size)
         assertEquals(TEST1, parsedConfig[CodeUtils.hash(PATH, TEST1.messageString,
             LogLevel.INFO, GROUP1)])
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt
new file mode 100644
index 0000000..74a8de7
--- /dev/null
+++ b/tools/protologtool/tests/com/android/protolog/tool/ViewerConfigProtoBuilderTest.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.protolog.tool
+
+import com.android.internal.protolog.common.LogLevel
+import com.android.protolog.tool.ProtoLogTool.LogCall
+import com.google.common.truth.Truth
+import org.junit.Test
+import perfetto.protos.PerfettoTrace.ProtoLogViewerConfig
+
+class ViewerConfigProtoBuilderTest {
+    companion object {
+        private val TAG1 = "WM_TEST"
+        private val TAG2 = "WM_DEBUG"
+
+        private val TEST1 = ViewerConfigParser.ConfigEntry("test1", LogLevel.INFO.name,
+            TAG1
+        )
+        private val TEST2 = ViewerConfigParser.ConfigEntry("test2", LogLevel.DEBUG.name,
+            TAG2
+        )
+
+        private val GROUP1 = LogGroup("TEST_GROUP", true, true, TAG1)
+        private val GROUP2 = LogGroup("DEBUG_GROUP", true, true, TAG2)
+        private val GROUP3 = LogGroup("UNUSED_GROUP", true, true, TAG1)
+
+        private val GROUPS = listOf(
+            GROUP1,
+            GROUP2,
+            GROUP3
+        )
+
+        private const val PATH = "/tmp/test.java"
+    }
+
+    @Test
+    fun includesUnusedProtoLogGroups() {
+        // Added because of b/373754057. This test might need to be removed in the future.
+
+        val configBuilder = ViewerConfigProtoBuilder()
+
+        val logCallRegistry = ProtoLogTool.LogCallRegistry()
+        logCallRegistry.addLogCalls(listOf(
+            LogCall(TEST1.messageString, LogLevel.INFO, GROUP1, PATH),
+            LogCall(TEST2.messageString, LogLevel.INFO, GROUP2, PATH),
+        ))
+
+        val rawProto = configBuilder.build(GROUPS, logCallRegistry.getStatements())
+
+        val viewerConfig = ProtoLogViewerConfig.parseFrom(rawProto)
+        Truth.assertThat(viewerConfig.groupsCount).isEqualTo(GROUPS.size)
+        Truth.assertThat(viewerConfig.messagesCount).isLessThan(GROUPS.size)
+    }
+}
\ No newline at end of file
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index 590f719..e6d0a3d 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -58,6 +58,7 @@
         "junit",
         "objenesis",
         "mockito",
+        "systemfeatures-gen-lib",
         "truth",
     ],
 }
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index 196b5e7..1abe77f 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -71,7 +71,7 @@
         println("Usage: SystemFeaturesGenerator <outputClassName> [options]")
         println(" Options:")
         println("  --readonly=true|false    Whether to encode features as build-time constants")
-        println("  --feature=\$NAME:\$VER   A feature+version pair, where \$VER can be:")
+        println("  --feature=\$NAME:\$VER     A feature+version pair, where \$VER can be:")
         println("                             * blank/empty == undefined (variable API)")
         println("                             * valid int   == enabled   (constant API)")
         println("                             * UNAVAILABLE == disabled  (constant API)")
@@ -89,6 +89,17 @@
     /** Main entrypoint for build-time system feature codegen. */
     @JvmStatic
     fun main(args: Array<String>) {
+        generate(args, System.out)
+    }
+
+    /**
+     * Simple API entrypoint for build-time system feature codegen.
+     *
+     * Note: Typically this would be implemented in terms of a proper Builder-type input argument,
+     * but it's primarily used for testing as opposed to direct production usage.
+     */
+    @JvmStatic
+    fun generate(args: Array<String>, output: Appendable) {
         if (args.size < 1) {
             usage()
             return
@@ -155,7 +166,7 @@
             .addFileComment("This file is auto-generated. DO NOT MODIFY.\n")
             .addFileComment("Args: ${args.joinToString(" \\\n           ")}")
             .build()
-            .writeTo(System.out)
+            .writeTo(output)
     }
 
     /*
@@ -171,12 +182,27 @@
         return when (featureArgs.getOrNull(1)) {
             null, "" -> FeatureInfo(name, null, readonly = false)
             "UNAVAILABLE" -> FeatureInfo(name, null, readonly = true)
-            else -> FeatureInfo(name, featureArgs[1].toIntOrNull(), readonly = true)
+            else -> {
+                val featureVersion =
+                    featureArgs[1].toIntOrNull()
+                        ?: throw IllegalArgumentException(
+                            "Invalid feature version input for $name: ${featureArgs[1]}"
+                        )
+                FeatureInfo(name, featureArgs[1].toInt(), readonly = true)
+            }
         }
     }
 
     private fun parseFeatureName(name: String): String =
-        if (name.startsWith("FEATURE_")) name else "FEATURE_$name"
+        when {
+            name.startsWith("android") ->
+                throw IllegalArgumentException(
+                    "Invalid feature name input: \"android\"-namespaced features must be " +
+                    "provided as PackageManager.FEATURE_* suffixes, not raw feature strings."
+                )
+            name.startsWith("FEATURE_") -> name
+            else -> "FEATURE_$name"
+        }
 
     /*
      * Adds per-feature query methods to the class with the form:
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java
new file mode 100644
index 0000000..f8c585d
--- /dev/null
+++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorApiTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.systemfeatures;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.IOException;
+
+// Note: This is a very simple argument test to validate certain behaviors for
+// invalid arguments. Correctness and validity is largely exercised by
+// SystemFeaturesGeneratorTest.
+@RunWith(JUnit4.class)
+public class SystemFeaturesGeneratorApiTest {
+
+    @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock private Appendable mOut;
+
+    @Test
+    public void testEmpty() throws IOException {
+        final String[] args = new String[] {};
+        // This should just print the commandline and return.
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, never()).append(any());
+    }
+
+    @Test
+    public void testBasic() throws IOException {
+        final String[] args = new String[] {
+            "com.foo.Features",
+            "--feature=TELEVISION:0",
+        };
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, atLeastOnce()).append(any());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidFeatureVersion() throws IOException {
+        final String[] args = new String[] {
+            "com.foo.Features",
+            "--feature=TELEVISION:blarg",
+        };
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, never()).append(any());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidFeatureNameFromAndroidNamespace() throws IOException {
+        final String[] args = new String[] {
+            "com.foo.Features",
+            "--feature=android.hardware.doesntexist:0",
+        };
+        SystemFeaturesGenerator.generate(args, mOut);
+        verify(mOut, never()).append(any());
+    }
+}