Merge "Log ACCOUNT_MANAGER_EVENT__EVENT_TYPE__AUTHENTICATOR_ADDED" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 1fdb698..c8a15e0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4637,7 +4637,7 @@
 
   @FlaggedApi("android.content.pm.verification_service") public final class VerificationSession implements android.os.Parcelable {
     method public int describeContents();
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long extendTimeRemaining(long);
+    method public long extendTimeRemaining(long);
     method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getDeclaredLibraries();
     method @NonNull public android.os.PersistableBundle getExtensionParams();
     method public int getId();
@@ -4645,12 +4645,12 @@
     method @NonNull public String getPackageName();
     method @NonNull public android.content.pm.SigningInfo getSigningInfo();
     method @NonNull public android.net.Uri getStagedPackageUri();
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long getTimeoutTime();
+    method public long getTimeoutTime();
     method public int getVerificationPolicy();
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationIncomplete(int);
-    method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public boolean setVerificationPolicy(int);
+    method public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus);
+    method public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle);
+    method public void reportVerificationIncomplete(int);
+    method public boolean setVerificationPolicy(int);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationSession> CREATOR;
     field public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1; // 0x1
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index b0a8b1b..5225174 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -7681,7 +7681,7 @@
         });
 
         // Register callback to report native memory metrics post GC cleanup
-        if (Flags.reportPostgcMemoryMetrics() &&
+        if (Flags.reportPostgcMemoryMetricsReadonly() &&
             com.android.libcore.readonly.Flags.postCleanupApis()) {
             VMRuntime.addPostCleanupCallback(new Runnable() {
                 @Override public void run() {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2e3d226..56538d9 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -8755,21 +8755,9 @@
      * Do a quick check for whether an application might be able to perform an operation.
      * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String,
      * String, String)} or {@link #startOp(String, int, String, String, String)} for your actual
-     * security checks, which also ensure that the given uid and package name are consistent. This
-     * function can just be used for a quick check to see if an operation has been disabled for the
-     * application, as an early reject of some work.  This does not modify the time stamp or other
-     * data about the operation.
-     *
-     * <p>Important things this will not do (which you need to ultimate use
-     * {@link #noteOp(String, int, String, String, String)} or
-     * {@link #startOp(String, int, String, String, String)} to cover):</p>
-     * <ul>
-     *     <li>Verifying the uid and package are consistent, so callers can't spoof
-     *     their identity.</li>
-     *     <li>Taking into account the current foreground/background state of the
-     *     app; apps whose mode varies by this state will always be reported
-     *     as {@link #MODE_ALLOWED}.</li>
-     * </ul>
+     * security checks. This function can just be used for a quick check to see if an operation has
+     * been disabled for the application, as an early reject of some work.  This does not modify the
+     * time stamp or other data about the operation.
      *
      * @param op The operation to check.  One of the OPSTR_* constants.
      * @param uid The user id of the application attempting to perform the operation.
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index dca4336..a82c6ba7 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -47,7 +47,6 @@
  * <p>App function is a specific piece of functionality that an app offers to the system. These
  * functionalities can be integrated into various system features.
  */
-// TODO(b/357551503): Implement get and set enabled app function APIs.
 @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
 @SystemService(Context.APP_FUNCTION_SERVICE)
 public final class AppFunctionManager {
@@ -111,17 +110,19 @@
      * @param request the request to execute the app function
      * @param executor the executor to run the callback
      * @param cancellationSignal the cancellation signal to cancel the execution.
-     * @param callback the callback to receive the function execution result. if the calling app
-     *     does not own the app function or does not have {@code
+     * @param callback the callback to receive the function execution result.
+     *     <p>If the calling app does not own the app function or does not have {@code
      *     android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code
      *     android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code
      *     ExecuteAppFunctionResponse.RESULT_DENIED}.
+     *     <p>If the caller only has {@code android.permission.EXECUTE_APP_FUNCTIONS} but the
+     *     function requires {@code android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}, the execution
+     *     result will contain {@code ExecuteAppFunctionResponse.RESULT_DENIED}
+     *     <p>If the function requested for execution is disabled, then the execution result will
+     *     contain {@code ExecuteAppFunctionResponse.RESULT_DISABLED}
+     *     <p>If the cancellation signal is issued, the operation is cancelled and no response is
+     *     returned to the caller.
      */
-    // TODO(b/357551503): Document the behavior when the cancellation signal is issued.
-    // TODO(b/360864791): Document that apps can opt-out from being executed by callers with
-    //   EXECUTE_APP_FUNCTIONS and how a caller knows whether a function is opted out.
-    // TODO(b/357551503): Update documentation when get / set APIs are implemented that this will
-    //   also return RESULT_DENIED if the app function is disabled.
     @RequiresPermission(
             anyOf = {
                 Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED,
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
index fe7fd88..4c5e8c1 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -50,12 +50,12 @@
                 }
             };
 
-    /** Returns the package name of the app that hosts the function. */
+    /** Returns the package name of the app that hosts/owns the function. */
     @NonNull private final String mTargetPackageName;
 
     /**
-     * Returns the unique string identifier of the app function to be executed. TODO(b/357551503):
-     * Document how callers can get the available function identifiers.
+     * The unique string identifier of the app function to be executed. This identifier is used to
+     * execute a specific app function.
      */
     @NonNull private final String mFunctionIdentifier;
 
@@ -69,8 +69,6 @@
      *
      * <p>The document may have missing parameters. Developers are advised to implement defensive
      * handling measures.
-     *
-     * <p>TODO(b/357551503): Document how function parameters can be obtained for function execution
      */
     @NonNull private final GenericDocumentWrapper mParameters;
 
@@ -91,7 +89,19 @@
         return mTargetPackageName;
     }
 
-    /** Returns the unique string identifier of the app function to be executed. */
+    /**
+     * Returns the unique string identifier of the app function to be executed.
+     *
+     * <p>When there is a package change or the device starts up, the metadata of available
+     * functions is indexed by AppSearch. AppSearch stores the indexed information as {@code
+     * AppFunctionStaticMetadata} document.
+     *
+     * <p>The ID can be obtained by querying the {@code AppFunctionStaticMetadata} documents from
+     * AppSearch.
+     *
+     * <p>If the {@code functionId} provided is invalid, the caller will get an invalid argument
+     * response.
+     */
     @NonNull
     public String getFunctionIdentifier() {
         return mFunctionIdentifier;
@@ -103,6 +113,12 @@
      *
      * <p>The bundle may have missing parameters. Developers are advised to implement defensive
      * handling measures.
+     *
+     * <p>Similar to {@link #getFunctionIdentifier()} the parameters required by a function can be
+     * obtained by querying AppSearch for the corresponding {@code AppFunctionStaticMetadata}. This
+     * metadata will contain enough information for the caller to resolve the required parameters
+     * either using information from the metadata itself or using the AppFunction SDK for function
+     * callers.
      */
     @NonNull
     public GenericDocument getParameters() {
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index a879b1b..c907ef1 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -79,14 +79,15 @@
     /** The caller does not have the permission to execute an app function. */
     public static final int RESULT_DENIED = 1;
 
-    /** An unknown error occurred while processing the call in the AppFunctionService. */
+    /**
+     * 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 error occurred within AppFunctionManagerService.
-     *
-     * <p>This error may be considered similar to {@link IllegalStateException}
-     */
+    /** An internal unexpected error coming from the system. */
     public static final int RESULT_INTERNAL_ERROR = 3;
 
     /**
diff --git a/core/java/android/app/metrics.aconfig b/core/java/android/app/metrics.aconfig
index 488f1c7..55d9c2d 100644
--- a/core/java/android/app/metrics.aconfig
+++ b/core/java/android/app/metrics.aconfig
@@ -8,3 +8,12 @@
      description: "Controls whether to report memory metrics post GC cleanup"
      bug: "331243037"
 }
+
+flag {
+     namespace: "system_performance"
+     name: "report_postgc_memory_metrics_readonly"
+     is_exported: false
+     description: "Controls whether to report memory metrics post GC cleanup (readonly)"
+     bug: "331243037"
+     is_fixed_read_only: true
+}
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 933c336..df1028e 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -716,8 +716,8 @@
                         mCurrentSize);
             } else {
                 applyContent(null, false, e);
+                mLastExecutionSignal = null;
             }
-            mLastExecutionSignal = null;
         }
     }
 
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index c911326..ecea479 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -94,9 +94,9 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
     void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle);
 
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+    @EnforcePermission("VERIFICATION_AGENT")
     int getVerificationPolicy();
 
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
+    @EnforcePermission("VERIFICATION_AGENT")
     boolean setVerificationPolicy(int policy);
 }
diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
index 66caf2d..2ab7452 100644
--- a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
+++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl
@@ -24,16 +24,9 @@
  * @hide
  */
 interface IVerificationSessionInterface {
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
     long getTimeoutTime(int verificationId);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
     long extendTimeRemaining(int verificationId, long additionalMs);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
     boolean setVerificationPolicy(int verificationId, int policy);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
     void reportVerificationIncomplete(int verificationId, int reason);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
-    void reportVerificationComplete(int verificationId, in VerificationStatus status);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)")
-    void reportVerificationCompleteWithExtensionResponse(int verificationId, in VerificationStatus status, in PersistableBundle response);
+    void reportVerificationComplete(int verificationId, in VerificationStatus status, in @nullable PersistableBundle extensionResponse);
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.java b/core/java/android/content/pm/verify/pkg/VerificationSession.java
index 4ade211..97f78e0 100644
--- a/core/java/android/content/pm/verify/pkg/VerificationSession.java
+++ b/core/java/android/content/pm/verify/pkg/VerificationSession.java
@@ -19,7 +19,6 @@
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.content.pm.Flags;
 import android.content.pm.PackageInstaller;
@@ -166,8 +165,8 @@
     /**
      * Get the value of Clock.elapsedRealtime() at which time this verification
      * will timeout as incomplete if no other verification response is provided.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public long getTimeoutTime() {
         try {
             return mSession.getTimeoutTime(mId);
@@ -190,8 +189,8 @@
     /**
      * Override the verification policy for this session.
      * @return True if the override was successful, False otherwise.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) {
         if (mVerificationPolicy == policy) {
             // No effective policy change
@@ -215,8 +214,8 @@
      * This may be called multiple times. If the request would bypass any max
      * duration by the system, the method will return a lower value than the
      * requested amount that indicates how much the time was extended.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public long extendTimeRemaining(long additionalMs) {
         try {
             return mSession.extendTimeRemaining(mId, additionalMs);
@@ -227,9 +226,9 @@
 
     /**
      * Report to the system that verification could not be completed along
-     * with an approximate reason to pass on to the installer.
+     * with an approximate reason to pass on to the installer.]
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public void reportVerificationIncomplete(@VerificationIncompleteReason int reason) {
         try {
             mSession.reportVerificationIncomplete(mId, reason);
@@ -242,11 +241,11 @@
      * Report to the system that the verification has completed and the
      * install process may act on that status to either block in the case
      * of failure or continue to process the install in the case of success.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public void reportVerificationComplete(@NonNull VerificationStatus status) {
         try {
-            mSession.reportVerificationComplete(mId, status);
+            mSession.reportVerificationComplete(mId, status,  /* extensionResponse= */ null);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -256,12 +255,12 @@
      * Same as {@link #reportVerificationComplete(VerificationStatus)}, but also provide
      * a result to the extension params provided in the request, which will be passed to the
      * installer in the installation result.
+     * @throws SecurityException if the caller is not the current verifier bound by the system.
      */
-    @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)
     public void reportVerificationComplete(@NonNull VerificationStatus status,
-            @NonNull PersistableBundle response) {
+            @NonNull PersistableBundle extensionResponse) {
         try {
-            mSession.reportVerificationCompleteWithExtensionResponse(mId, status, response);
+            mSession.reportVerificationComplete(mId, status, extensionResponse);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/os/BatteryUsageStatsQuery.java b/core/java/android/os/BatteryUsageStatsQuery.java
index e68c4ca..6325b00 100644
--- a/core/java/android/os/BatteryUsageStatsQuery.java
+++ b/core/java/android/os/BatteryUsageStatsQuery.java
@@ -370,7 +370,10 @@
          * and most battery stats resets.
          */
         public Builder accumulated() {
-            mFlags |= FLAG_BATTERY_USAGE_STATS_ACCUMULATED;
+            mFlags |= FLAG_BATTERY_USAGE_STATS_ACCUMULATED
+                    | FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_STATE
+                    | FLAG_BATTERY_USAGE_STATS_INCLUDE_SCREEN_STATE
+                    | FLAG_BATTERY_USAGE_STATS_INCLUDE_PROCESS_STATE_DATA;
             return this;
         }
 
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 25f3f96..1d654e13 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -299,3 +299,11 @@
     description: "Allow host device permission dialogs (i.e., dialogs for non device-aware permissions) to be shown on virtual devices"
     bug: "371173672"
 }
+
+flag {
+    name: "appop_mode_caching_enabled"
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "Enable AppOp mode caching in AppOpsManager"
+    bug: "366013082"
+}
diff --git a/core/java/android/security/forensic/ForensicEvent.java b/core/java/android/security/forensic/ForensicEvent.java
index 9cbc5ec..90906ed 100644
--- a/core/java/android/security/forensic/ForensicEvent.java
+++ b/core/java/android/security/forensic/ForensicEvent.java
@@ -61,6 +61,14 @@
         in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class);
     }
 
+    public String getType() {
+        return mType;
+    }
+
+    public Map<String, String> getKeyValuePairs() {
+        return mKeyValuePairs;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
         out.writeString(mType);
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index e6de478..94f415b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -412,41 +412,28 @@
      */
     public static class JankData {
 
-        /** @hide */
-        @IntDef(flag = true, value = {JANK_NONE,
-                DISPLAY_HAL,
-                JANK_SURFACEFLINGER_DEADLINE_MISSED,
-                JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED,
-                JANK_APP_DEADLINE_MISSED,
-                PREDICTION_ERROR,
-                SURFACE_FLINGER_SCHEDULING})
+        /**
+         * Needs to be kept in sync with android_view_SurfaceControl.cpp's
+         * JankDataListenerWrapper::onJankDataAvailable.
+         * @hide
+         */
+        @IntDef(flag = true, value = {
+            JANK_NONE,
+            JANK_COMPOSER,
+            JANK_APPLICATION,
+            JANK_OTHER,
+        })
         @Retention(RetentionPolicy.SOURCE)
         public @interface JankType {}
 
-        // Needs to be kept in sync with frameworks/native/libs/gui/include/gui/JankInfo.h
-
         // No Jank
-        public static final int JANK_NONE = 0x0;
-
-        // Jank not related to SurfaceFlinger or the App
-        public static final int DISPLAY_HAL = 0x1;
-        // SF took too long on the CPU
-        public static final int JANK_SURFACEFLINGER_DEADLINE_MISSED = 0x2;
-        // SF took too long on the GPU
-        public static final int JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED = 0x4;
-        // Either App or GPU took too long on the frame
-        public static final int JANK_APP_DEADLINE_MISSED = 0x8;
-        // Vsync predictions have drifted beyond the threshold from the actual HWVsync
-        public static final int PREDICTION_ERROR = 0x10;
-        // Latching a buffer early might cause an early present of the frame
-        public static final int SURFACE_FLINGER_SCHEDULING = 0x20;
-        // A buffer is said to be stuffed if it was expected to be presented on a vsync but was
-        // presented later because the previous buffer was presented in its expected vsync. This
-        // usually happens if there is an unexpectedly long frame causing the rest of the buffers
-        // to enter a stuffed state.
-        public static final int BUFFER_STUFFING = 0x40;
-        // Jank due to unknown reasons.
-        public static final int UNKNOWN = 0x80;
+        public static final int JANK_NONE = 0;
+        // Jank caused by the composer missing a deadline
+        public static final int JANK_COMPOSER = 1 << 0;
+        // Jank caused by the application missing the composer's deadline
+        public static final int JANK_APPLICATION = 1 << 1;
+        // Jank due to other unknown reasons
+        public static final int JANK_OTHER = 1 << 2;
 
         public JankData(long frameVsyncId, @JankType int jankType, long frameIntervalNs,
                 long scheduledAppFrameTimeNs, long actualAppFrameTimeNs) {
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index cd31850..57aacff 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -72,6 +72,13 @@
 }
 
 flag {
+    name: "bal_strict_mode"
+    namespace: "responsible_apis"
+    description: "Strict mode flag"
+    bug: "324089586"
+}
+
+flag {
     name: "bal_reduce_grace_period"
     namespace: "responsible_apis"
     description: "Changes to reduce or ideally remove the grace period exemption."
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 003393c..0af4bea 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -16,13 +16,9 @@
 
 package com.android.internal.jank;
 
-import static android.view.SurfaceControl.JankData.DISPLAY_HAL;
-import static android.view.SurfaceControl.JankData.JANK_APP_DEADLINE_MISSED;
+import static android.view.SurfaceControl.JankData.JANK_APPLICATION;
+import static android.view.SurfaceControl.JankData.JANK_COMPOSER;
 import static android.view.SurfaceControl.JankData.JANK_NONE;
-import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
-import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED;
-import static android.view.SurfaceControl.JankData.PREDICTION_ERROR;
-import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;
 
 import static com.android.internal.jank.DisplayRefreshRate.UNKNOWN_REFRESH_RATE;
 import static com.android.internal.jank.DisplayRefreshRate.VARIABLE_REFRESH_RATE;
@@ -181,23 +177,11 @@
                 case JANK_NONE:
                     str.append("JANK_NONE");
                     break;
-                case JANK_APP_DEADLINE_MISSED:
-                    str.append("JANK_APP_DEADLINE_MISSED");
+                case JANK_APPLICATION:
+                    str.append("JANK_APPLICATION");
                     break;
-                case JANK_SURFACEFLINGER_DEADLINE_MISSED:
-                    str.append("JANK_SURFACEFLINGER_DEADLINE_MISSED");
-                    break;
-                case JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED:
-                    str.append("JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED");
-                    break;
-                case DISPLAY_HAL:
-                    str.append("DISPLAY_HAL");
-                    break;
-                case PREDICTION_ERROR:
-                    str.append("PREDICTION_ERROR");
-                    break;
-                case SURFACE_FLINGER_SCHEDULING:
-                    str.append("SURFACE_FLINGER_SCHEDULING");
+                case JANK_COMPOSER:
+                    str.append("JANK_COMPOSER");
                     break;
                 default:
                     str.append("UNKNOWN: ").append(jankType);
@@ -628,16 +612,12 @@
             if (info.surfaceControlCallbackFired) {
                 totalFramesCount++;
                 boolean missedFrame = false;
-                if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) {
+                if ((info.jankType & JANK_APPLICATION) != 0) {
                     Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + name);
                     missedAppFramesCount++;
                     missedFrame = true;
                 }
-                if ((info.jankType & DISPLAY_HAL) != 0
-                        || (info.jankType & JANK_SURFACEFLINGER_DEADLINE_MISSED) != 0
-                        || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
-                        || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0
-                        || (info.jankType & PREDICTION_ERROR) != 0) {
+                if ((info.jankType & JANK_COMPOSER) != 0) {
                     Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + name);
                     missedSfFramesCount++;
                     missedFrame = true;
diff --git a/core/java/com/android/internal/os/flags.aconfig b/core/java/com/android/internal/os/flags.aconfig
index 07df248..25a9fbc 100644
--- a/core/java/com/android/internal/os/flags.aconfig
+++ b/core/java/com/android/internal/os/flags.aconfig
@@ -2,6 +2,48 @@
 container: "system"
 
 flag {
+     namespace: "ravenwood"
+     name: "ravenwood_flag_rw_1"
+     description: "Ravenwood test RW flag 1"
+     bug: "311370221"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "ravenwood"
+     name: "ravenwood_flag_rw_2"
+     description: "Ravenwood test RW flag 2"
+     bug: "311370221"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "ravenwood"
+     name: "ravenwood_flag_ro_1"
+     description: "Ravenwood test RO flag 1"
+     is_fixed_read_only: true
+     bug: "311370221"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
+     namespace: "ravenwood"
+     name: "ravenwood_flag_ro_2"
+     description: "Ravenwood test RO flag 2"
+     is_fixed_read_only: true
+     bug: "311370221"
+     metadata {
+         purpose: PURPOSE_BUGFIX
+     }
+}
+
+flag {
     name: "enable_apache_http_legacy_preload"
     namespace: "system_performance"
     description: "Enables zygote preload of non-BCP org.apache.http.legacy.jar library."
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 10e49ef..50252c1 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -27,6 +27,7 @@
 #include <camera/StringUtils.h>
 #include <com_android_internal_camera_flags.h>
 #include <cutils/properties.h>
+#include <gui/Flags.h>
 #include <gui/GLConsumer.h>
 #include <gui/Surface.h>
 #include <nativehelper/JNIHelp.h>
@@ -715,16 +716,20 @@
     sp<Camera> camera = get_native_camera(env, thiz, NULL);
     if (camera == 0) return;
 
-    sp<IGraphicBufferProducer> gbp;
     sp<Surface> surface;
     if (jSurface) {
         surface = android_view_Surface_getSurface(env, jSurface);
-        if (surface != NULL) {
-            gbp = surface->getIGraphicBufferProducer();
-        }
     }
 
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    if (camera->setPreviewTarget(surface) != NO_ERROR) {
+#else
+    sp<IGraphicBufferProducer> gbp;
+    if (surface != NULL) {
+        gbp = surface->getIGraphicBufferProducer();
+    }
     if (camera->setPreviewTarget(gbp) != NO_ERROR) {
+#endif
         jniThrowException(env, "java/io/IOException", "setPreviewTexture failed");
     }
 }
@@ -736,6 +741,9 @@
     sp<Camera> camera = get_native_camera(env, thiz, NULL);
     if (camera == 0) return;
 
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    sp<Surface> surface;
+#endif
     sp<IGraphicBufferProducer> producer = NULL;
     if (jSurfaceTexture != NULL) {
         producer = SurfaceTexture_getProducer(env, jSurfaceTexture);
@@ -744,10 +752,16 @@
                     "SurfaceTexture already released in setPreviewTexture");
             return;
         }
-
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+        surface = new Surface(producer);
+#endif
     }
 
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    if (camera->setPreviewTarget(surface) != NO_ERROR) {
+#else
     if (camera->setPreviewTarget(producer) != NO_ERROR) {
+#endif
         jniThrowException(env, "java/io/IOException",
                 "setPreviewTexture failed");
     }
@@ -761,18 +775,32 @@
     sp<Camera> camera = get_native_camera(env, thiz, &context);
     if (camera == 0) return;
 
+#if !WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
     sp<IGraphicBufferProducer> gbp;
+#endif
     sp<Surface> surface;
     if (jSurface) {
         surface = android_view_Surface_getSurface(env, jSurface);
+        if (surface == NULL) {
+            jniThrowException(env, "java/lang/IllegalArgumentException",
+                              "android_view_Surface_getSurface failed");
+            return;
+        }
+
+#if !WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
         if (surface != NULL) {
             gbp = surface->getIGraphicBufferProducer();
         }
+#endif
     }
     // Clear out normal preview callbacks
     context->setCallbackMode(env, false, false);
     // Then set up callback surface
+#if WB_LIBCAMERASERVICE_WITH_DEPENDENCIES
+    if (camera->setPreviewCallbackTarget(surface) != NO_ERROR) {
+#else
     if (camera->setPreviewCallbackTarget(gbp) != NO_ERROR) {
+#endif
         jniThrowException(env, "java/io/IOException", "setPreviewCallbackTarget failed");
     }
 }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 755704a..f162b74 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -35,6 +35,7 @@
 #include <android_runtime/android_view_SurfaceSession.h>
 #include <cutils/ashmem.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/JankInfo.h>
 #include <gui/Surface.h>
 #include <gui/SurfaceComposerClient.h>
 #include <jni.h>
@@ -2161,9 +2162,30 @@
         jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
                 gJankDataClassInfo.clazz, nullptr);
         for (size_t i = 0; i < jankData.size(); i++) {
+            // The exposed constants in SurfaceControl are simplified, so we need to translate the
+            // jank type we get from SF to what is exposed in Java.
+            int sfJankType = jankData[i].jankType;
+            int javaJankType = 0x0; // SurfaceControl.JankData.JANK_NONE
+            if (sfJankType &
+                (JankType::DisplayHAL | JankType::SurfaceFlingerCpuDeadlineMissed |
+                 JankType::SurfaceFlingerGpuDeadlineMissed | JankType::PredictionError |
+                 JankType::SurfaceFlingerScheduling)) {
+                javaJankType |= 0x1; // SurfaceControl.JankData.JANK_COMPOSER
+            }
+            if (sfJankType & JankType::AppDeadlineMissed) {
+                javaJankType |= 0x2; // SurfaceControl.JankData.JANK_APPLICATION
+            }
+            if (sfJankType &
+                ~(JankType::DisplayHAL | JankType::SurfaceFlingerCpuDeadlineMissed |
+                  JankType::SurfaceFlingerGpuDeadlineMissed | JankType::AppDeadlineMissed |
+                  JankType::PredictionError | JankType::SurfaceFlingerScheduling |
+                  JankType::BufferStuffing | JankType::SurfaceFlingerStuffing)) {
+                javaJankType |= 0x4; // SurfaceControl.JankData.JANK_OTHER
+            }
+
             jobject jJankData =
                     env->NewObject(gJankDataClassInfo.clazz, gJankDataClassInfo.ctor,
-                                   jankData[i].frameVsyncId, jankData[i].jankType,
+                                   jankData[i].frameVsyncId, javaJankType,
                                    jankData[i].frameIntervalNs, jankData[i].scheduledAppFrameTimeNs,
                                    jankData[i].actualAppFrameTimeNs);
             env->SetObjectArrayElement(jJankDataArray, i, jJankData);
diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
index 90ae306..f1e1df5 100644
--- a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
+++ b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java
@@ -142,10 +142,10 @@
                 new VerificationStatus.Builder().setVerified(true).build();
         mTestSession.reportVerificationComplete(status);
         verify(mTestSessionInterface, times(1)).reportVerificationComplete(
-                eq(TEST_ID), eq(status));
+                eq(TEST_ID), eq(status), eq(null));
         mTestSession.reportVerificationComplete(status, response);
         verify(mTestSessionInterface, times(1))
-                .reportVerificationCompleteWithExtensionResponse(
+                .reportVerificationComplete(
                         eq(TEST_ID), eq(status), eq(response));
 
         final int reason = VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN;
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 1cbc7d6..60b5a42 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -16,9 +16,9 @@
 
 package com.android.internal.jank;
 
-import static android.view.SurfaceControl.JankData.JANK_APP_DEADLINE_MISSED;
+import static android.view.SurfaceControl.JankData.JANK_APPLICATION;
+import static android.view.SurfaceControl.JankData.JANK_COMPOSER;
 import static android.view.SurfaceControl.JankData.JANK_NONE;
-import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_MISSED;
 
 import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper;
 import static com.android.internal.jank.FrameTracker.ViewRootWrapper;
@@ -164,7 +164,7 @@
         verify(mRenderer, only()).addObserver(any());
 
         // send first frame with a long duration - should not be taken into account
-        sendFirstWindowFrame(tracker, 100, JANK_APP_DEADLINE_MISSED, 100L);
+        sendFirstWindowFrame(tracker, 100, JANK_APPLICATION, 100L);
 
         // send another frame with a short duration - should not be considered janky
         sendFrame(tracker, 5, JANK_NONE, 101L);
@@ -173,7 +173,7 @@
         when(mChoreographer.getVsyncId()).thenReturn(102L);
         tracker.end(FrameTracker.REASON_END_NORMAL);
         sendFrame(tracker, 5, JANK_NONE, 102L);
-        sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L);
+        sendFrame(tracker, 500, JANK_APPLICATION, 103L);
 
         verify(tracker).removeObservers();
         verify(mTrackerListener, never()).triggerPerfetto(any());
@@ -202,7 +202,7 @@
         sendFrame(tracker, 4, JANK_NONE, 100L);
 
         // send another frame - should be considered janky
-        sendFrame(tracker, 40, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
+        sendFrame(tracker, 40, JANK_COMPOSER, 101L);
 
         // end the trace session
         when(mChoreographer.getVsyncId()).thenReturn(102L);
@@ -236,7 +236,7 @@
         verify(mRenderer, only()).addObserver(any());
 
         // send first frame - janky
-        sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 100L);
+        sendFrame(tracker, 40, JANK_APPLICATION, 100L);
 
         // send another frame - not jank
         sendFrame(tracker, 4, JANK_NONE, 101L);
@@ -275,7 +275,7 @@
         sendFrame(tracker, 4, JANK_NONE, 100L);
 
         // send another frame - should be considered janky
-        sendFrame(tracker, 40, JANK_APP_DEADLINE_MISSED, 101L);
+        sendFrame(tracker, 40, JANK_APPLICATION, 101L);
 
         // end the trace session
         when(mChoreographer.getVsyncId()).thenReturn(102L);
@@ -317,7 +317,7 @@
         // end the trace session, simulate one more valid callback came after the end call.
         when(mChoreographer.getVsyncId()).thenReturn(102L);
         tracker.end(FrameTracker.REASON_END_NORMAL);
-        sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
+        sendFrame(tracker, 50, JANK_APPLICATION, 102L);
 
         // One more callback with VSYNC after the end() vsync id.
         sendFrame(tracker, 4, JANK_NONE, 103L);
@@ -365,7 +365,7 @@
         sendSfFrame(tracker, 4, 102L, JANK_NONE);
 
         // Send janky but complete callbck fo 103L
-        sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
+        sendFrame(tracker, 50, JANK_APPLICATION, 103L);
 
         verify(tracker).removeObservers();
         verify(mTrackerListener, never()).triggerPerfetto(any());
@@ -397,7 +397,7 @@
         sendFrame(tracker, 4, JANK_NONE, 101L);
 
         // a janky frame
-        sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 102L);
+        sendFrame(tracker, 50, JANK_APPLICATION, 102L);
 
         tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL);
         verify(tracker).removeObservers();
@@ -481,7 +481,7 @@
         // normal frame - not janky
         sendFrame(tracker, JANK_NONE, 101L);
         // a janky frame
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
+        sendFrame(tracker, JANK_APPLICATION, 102L);
 
         when(mChoreographer.getVsyncId()).thenReturn(102L);
         tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
@@ -514,7 +514,7 @@
         verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
 
         // First frame - janky
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 100L);
+        sendFrame(tracker, JANK_APPLICATION, 100L);
         // normal frame - not janky
         sendFrame(tracker, JANK_NONE, 101L);
         // normal frame - not janky
@@ -561,7 +561,7 @@
         tracker.end(FrameTracker.REASON_CANCEL_NORMAL);
 
         // janky frame, should be ignored, trigger finish
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
+        sendFrame(tracker, JANK_APPLICATION, 103L);
 
         verify(mJankStatsRegistration).removeAfter(anyLong());
         verify(mTrackerListener, never()).triggerPerfetto(any());
@@ -623,16 +623,16 @@
         tracker.begin();
         mRunnableArgumentCaptor.getValue().run();
         verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
-        sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 100L);
-        sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 102L);
+        sendFrame(tracker, JANK_COMPOSER, 100L);
+        sendFrame(tracker, JANK_COMPOSER, 101L);
+        sendFrame(tracker, JANK_APPLICATION, 102L);
         sendFrame(tracker, JANK_NONE, 103L);
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 104L);
-        sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 105L);
+        sendFrame(tracker, JANK_APPLICATION, 104L);
+        sendFrame(tracker, JANK_APPLICATION, 105L);
         when(mChoreographer.getVsyncId()).thenReturn(106L);
         tracker.end(FrameTracker.REASON_END_NORMAL);
-        sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L);
-        sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L);
+        sendFrame(tracker, JANK_COMPOSER, 106L);
+        sendFrame(tracker, JANK_COMPOSER, 107L);
         verify(mJankStatsRegistration).removeAfter(anyLong());
         verify(mTrackerListener).triggerPerfetto(any());
         verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 9fd255d..7adec39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -20,6 +20,7 @@
 import android.graphics.Point
 import android.graphics.Rect
 import android.util.Log
+import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -102,14 +103,17 @@
         hideEducation(animated = false)
         log { "showStackEducation at: $position" }
 
+        val rootBounds = Rect()
+        // Get root bounds on screen as position is in screen coordinates
+        root.getBoundsOnScreen(rootBounds)
         educationView =
             createEducationView(R.layout.bubble_bar_stack_education, root).apply {
                 setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
-                setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
-                updateEducationPosition(view = this, position, root)
+                updateEducationPosition(view = this, position, rootBounds)
                 val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
                 doOnLayout {
-                    it.pivotX = it.width - arrowToEdgeOffset
+                    it.pivotX = if (position.x < rootBounds.centerX())
+                        arrowToEdgeOffset else it.width - arrowToEdgeOffset
                     it.pivotY = it.height.toFloat()
                 }
                 setOnClickListener { educationClickHandler() }
@@ -218,12 +222,9 @@
      *
      * @param view the user education view to layout
      * @param position the reference position in Screen coordinates
-     * @param root the root view to use for the layout
+     * @param rootBounds bounds of the parent the education view is placed in
      */
-    private fun updateEducationPosition(view: BubblePopupView, position: Point, root: ViewGroup) {
-        val rootBounds = Rect()
-        // Get root bounds on screen as position is in screen coordinates
-        root.getBoundsOnScreen(rootBounds)
+    private fun updateEducationPosition(view: BubblePopupView, position: Point, rootBounds: Rect) {
         // Get the offset to the arrow from the edge of the education view
         val arrowToEdgeOffset =
             view.popupDrawable?.config?.let { it.cornerRadius + it.arrowWidth / 2f }?.roundToInt()
@@ -231,7 +232,15 @@
         // Calculate education view margins
         val params = view.layoutParams as FrameLayout.LayoutParams
         params.bottomMargin = rootBounds.bottom - position.y
-        params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+        if (position.x < rootBounds.centerX()) {
+            params.leftMargin = position.x - arrowToEdgeOffset
+            params.gravity = Gravity.LEFT or Gravity.BOTTOM
+            view.setArrowPosition(BubblePopupDrawable.ArrowPosition.Start)
+        } else {
+            params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+            params.gravity = Gravity.RIGHT or Gravity.BOTTOM
+            view.setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
+        }
         view.layoutParams = params
     }
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index a8af43f5..603a911 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2479,10 +2479,10 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             int currentUser = ActivityManager.getCurrentUser();
-            if (callingUser == currentUser) {
-                // enforce the deny list only if the caller is not current user. Currently only auto
-                // uses background visible user, and auto doesn't support profiles so profiles of
-                // current users is not checked here.
+            if (callingUser == currentUser || callingUser == UserHandle.USER_SYSTEM) {
+                // enforce the deny list only if the caller is not current user or not a system
+                // user. Currently only auto uses background visible user, and auto doesn't
+                // support profiles so profiles of current users is not checked here.
                 return;
             }
         } finally {
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 5f90b39..9265ceb 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -106,6 +106,7 @@
     srcs: [
         "tests/src/**/systemui/ExpandHelperTest.java",
         "tests/src/**/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java",
+        "tests/src/**/systemui/accessibility/AccessibilityGestureTargetsObserverTest.java",
         "tests/src/**/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java",
         "tests/src/**/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java",
         "tests/src/**/systemui/screenshot/appclips/AppClipsActivityTest.java",
@@ -269,6 +270,42 @@
         "tests/src/**/systemui/volume/VolumeDialogImplTest.java",
         "tests/src/**/systemui/wallet/controller/QuickAccessWalletControllerTest.java",
         "tests/src/**/systemui/wallet/ui/WalletScreenControllerTest.java",
+        "tests/src/**/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt",
+        "tests/src/**/systemui/biometrics/UdfpsControllerOverlayTest.kt",
+        // TODO(b/322324387): Fails to start due to missing ScreenshotActivity
+        "tests/src/**/systemui/bouncer/ui/composable/BouncerContentTest.kt",
+        "tests/src/**/systemui/bouncer/ui/composable/PatternBouncerTest.kt",
+        "tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
+        "tests/src/**/systemui/communal/data/db/CommunalDatabaseMigrationsTest.kt",
+        "tests/src/**/systemui/communal/data/db/CommunalWidgetDaoTest.kt",
+        "tests/src/**/systemui/display/data/repository/DisplayWindowPropertiesRepositoryImplTest.kt",
+        "tests/src/**/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt",
+        "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
+        "tests/src/**/systemui/lifecycle/ActivatableTest.kt",
+        "tests/src/**/systemui/lifecycle/HydratorTest.kt",
+        "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
+        "tests/src/**/systemui/qs/QSImplTest.java",
+        "tests/src/**/systemui/qs/panels/ui/compose/DragAndDropTest.kt",
+        "tests/src/**/systemui/qs/panels/ui/compose/ResizingTest.kt",
+        "tests/src/**/systemui/screenshot/ActionExecutorTest.kt",
+        "tests/src/**/systemui/screenshot/ScreenshotDetectionControllerTest.kt",
+        "tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt",
+        "tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java",
+        "tests/src/**/systemui/accessibility/floatingmenu/PositionTest.java",
+        "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
+        "tests/src/**/systemui/screenshot/scroll/ScrollCaptureControllerTest.java",
+        "tests/src/**/systemui/lifecycle/SysuiViewModelTest.kt",
+        "tests/src/**/systemui/flags/FakeFeatureFlags.kt",
+        "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
+        "tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
+        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
+        "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
+        "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
+        "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
+        "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
+        "tests/src/**/systemui/toast/ToastUITest.java",
+        "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
+        "tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt",
     ],
 }
 
@@ -276,42 +313,23 @@
 filegroup {
     name: "SystemUI-tests-broken-robofiles-compile",
     srcs: [
+        "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
+        "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
+        "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
+        "tests/src/**/systemui/doze/DozeScreenStateTest.java",
+        "tests/src/**/keyguard/CarrierTextManagerTest.java",
+        "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt",
+        "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
         "tests/src/**/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt",
-        "tests/src/**/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt",
-        "tests/src/**/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt",
         "tests/src/**/systemui/controls/management/ControlsFavoritingActivityTest.kt",
         "tests/src/**/systemui/controls/management/ControlsProviderSelectorActivityTest.kt",
-        "tests/src/**/systemui/controls/start/ControlsStartableTest.kt",
-        "tests/src/**/systemui/haptics/slider/SliderStateTrackerTest.kt",
-        "tests/src/**/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt",
-        "tests/src/**/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt",
-        "tests/src/**/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt",
-        "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt",
-        "tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
-        "tests/src/**/systemui/keyguard/ResourceTrimmerTest.kt",
-        "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt",
         "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt",
-        "tests/src/**/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt",
-        "tests/src/**/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt",
+        "tests/src/**/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt",
         "tests/src/**/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt",
         "tests/src/**/systemui/qs/tileimpl/QSTileViewImplTest.kt",
-        "tests/src/**/systemui/qs/tiles/DeviceControlsTileTest.kt",
-        "tests/src/**/systemui/screenshot/ActionExecutorTest.kt",
-        "tests/src/**/systemui/screenshot/ActionIntentCreatorTest.kt",
-        "tests/src/**/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt",
-        "tests/src/**/systemui/screenshot/TakeScreenshotServiceTest.kt",
-        "tests/src/**/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt",
-        "tests/src/**/systemui/statusbar/notification/icon/IconManagerTest.kt",
-        "tests/src/**/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt",
-        "tests/src/**/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt",
-        "tests/src/**/systemui/statusbar/notification/stack/ui/view/NotificationStatsLoggerTest.kt",
-        "tests/src/**/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt",
-        "tests/src/**/systemui/statusbar/policy/FlashlightControllerImplTest.kt",
-        "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt",
-        "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt",
+        "tests/src/**/systemui/statusbar/policy/BatteryStateNotifierTest.kt",
         "tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt",
         "tests/src/**/keyguard/ClockEventControllerTest.kt",
-        "tests/src/**/systemui/animation/TransitionAnimatorTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegateTest.kt",
@@ -319,9 +337,6 @@
         "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
         "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt",
-        // TODO(b/322324387): Fails to start due to missing ScreenshotActivity
-        "tests/src/**/systemui/bouncer/ui/composable/BouncerContentTest.kt",
-        "tests/src/**/systemui/bouncer/ui/composable/PatternBouncerTest.kt",
         "tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt",
         "tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt",
         "tests/src/**/systemui/clipboardoverlay/ClipboardModelTest.kt",
@@ -348,8 +363,6 @@
         "tests/src/**/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt",
         "tests/src/**/systemui/navigationbar/gestural/BackPanelControllerTest.kt",
         "tests/src/**/systemui/notetask/NoteTaskControllerTest.kt",
-        "tests/src/**/systemui/notetask/NoteTaskInitializerTest.kt",
-        "tests/src/**/systemui/power/domain/interactor/PowerInteractorTest.kt",
         "tests/src/**/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt",
         "tests/src/**/systemui/privacy/PrivacyItemControllerTest.kt",
         "tests/src/**/systemui/qs/external/CustomTileStatePersisterTest.kt",
@@ -376,7 +389,6 @@
         "tests/src/**/systemui/statusbar/LightRevealScrimTest.kt",
         "tests/src/**/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt",
         "tests/src/**/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt",
-        "tests/src/**/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt",
         "tests/src/**/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt",
         "tests/src/**/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt",
         "tests/src/**/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt",
@@ -406,8 +418,6 @@
         "tests/src/**/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt",
         "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt",
         "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt",
-        "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java",
-        "tests/src/**/keyguard/CarrierTextManagerTest.java",
         "tests/src/**/systemui/ScreenDecorationsTest.java",
         "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt",
         "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt",
@@ -431,33 +441,17 @@
         "tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
         "tests/src/**/systemui/statusbar/policy/BatteryControllerTest.java",
         "tests/src/**/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt",
-        "tests/src/**/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt",
         "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryTest.kt",
         "tests/src/**/systemui/statusbar/KeyboardShortcutsReceiverTest.java",
         "tests/src/**/systemui/wmshell/BubblesTest.java",
-        "tests/src/**/systemui/biometrics/AuthRippleControllerTest.kt",
-        "tests/src/**/keyguard/KeyguardAbsKeyInputViewControllerTest.java",
-        "tests/src/**/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java",
-        "tests/src/**/systemui/clipboardoverlay/ClipboardListenerTest.java",
-        "tests/src/**/systemui/doze/DozeScreenStateTest.java",
-        "tests/src/**/systemui/keyguard/WorkLockActivityControllerTest.java",
-        "tests/src/**/systemui/media/dialog/MediaSwitchingControllerTest.java",
-        "tests/src/**/systemui/navigationbar/views/NavigationBarTest.java",
-        "tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
-        "tests/src/**/systemui/qs/QSFooterViewControllerTest.java",
-        "tests/src/**/systemui/qs/QSImplTest.java",
-        "tests/src/**/systemui/qs/tiles/QuickAccessWalletTileTest.java",
-        "tests/src/**/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java",
-        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerBaseTest.java",
-        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerDataTest.java",
-        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java",
-        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerSignalTest.java",
-        "tests/src/**/systemui/statusbar/connectivity/NetworkControllerWifiTest.java",
-        "tests/src/**/systemui/statusbar/KeyguardIndicationControllerTest.java",
-        "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java",
-        "tests/src/**/systemui/statusbar/phone/ScrimControllerTest.java",
-        "tests/src/**/systemui/statusbar/policy/RotationLockControllerImplTest.java",
-        "tests/src/**/systemui/toast/ToastUITest.java",
+        "tests/src/**/systemui/power/PowerUITest.java",
+        "tests/src/**/systemui/qs/QSSecurityFooterTest.java",
+        "tests/src/**/systemui/qs/tileimpl/QSTileImplTest.java",
+        "tests/src/**/systemui/shared/plugins/PluginActionManagerTest.java",
+        "tests/src/**/systemui/statusbar/CommandQueueTest.java",
+        "tests/src/**/systemui/statusbar/connectivity/CallbackHandlerTest.java",
+        "tests/src/**/systemui/statusbar/policy/SecurityControllerTest.java",
+        "tests/src/**/systemui/shared/clocks/view/SimpleDigitalClockTextViewTest.kt",
     ],
     visibility: ["//visibility:private"],
 }
@@ -885,6 +879,7 @@
         "android.test.base.impl",
         "android.test.mock.impl",
         "truth",
+        "androidx.test.ext.truth",
     ],
 
     upstream: true,
@@ -922,6 +917,7 @@
         "android.test.base.impl",
         "android.test.mock.impl",
         "truth",
+        "androidx.test.ext.truth",
     ],
 
     upstream: true,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 5feb63d..1551ca9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -182,7 +182,8 @@
                     val itemBoundingBox = IntRect(item.offset, item.size)
                     draggingItemKey != item.key &&
                         contentListState.isItemEditable(item.index) &&
-                        draggingBoundingBox.contains(itemBoundingBox.center)
+                        (draggingBoundingBox.contains(itemBoundingBox.center) ||
+                            itemBoundingBox.contains(draggingBoundingBox.center))
                 }
             } else {
                 state.layoutInfo.visibleItemsInfo
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
index 48f6cc4..14d34d7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/ActiveUnlockConfigTest.kt
@@ -33,6 +33,7 @@
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS
 import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_TO_FORCE_DISMISS_KEYGUARD
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
@@ -47,6 +48,7 @@
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
@@ -55,6 +57,7 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
+@RunWith(AndroidJUnit4::class)
 class ActiveUnlockConfigTest : SysuiTestCase() {
     private lateinit var secureSettings: FakeSettings
     @Mock private lateinit var contentResolver: ContentResolver
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
index edf29c5..b937db6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockAccessibilityDelegateTest.java
@@ -25,6 +25,7 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
@@ -32,10 +33,12 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.List;
 
 @SmallTest
+@RunWith(AndroidJUnit4.class)
 public class KeyguardClockAccessibilityDelegateTest extends SysuiTestCase {
 
     private TextView mView;
@@ -111,4 +114,4 @@
     private boolean isEmpty(List<CharSequence> texts) {
         return texts.stream().allMatch(TextUtils::isEmpty);
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/DependencyTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/DependencyTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
index ccdcee5..1b07241 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ExpandHelperTest.java
@@ -21,12 +21,12 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.core.animation.ObjectAnimator;
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -42,7 +42,7 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper
 public class ExpandHelperTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/InitControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/InitControllerTest.java
similarity index 95%
rename from packages/SystemUI/tests/src/com/android/systemui/InitControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/InitControllerTest.java
index e0ca87c..e0c7c7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/InitControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/InitControllerTest.java
@@ -19,16 +19,16 @@
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
 
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper
 public class InitControllerTest extends SysuiTestCase {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/animation/AnimatorTestRuleOrderTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
index 25b85b5..9f0c7e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDialogDelegateTest.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.bluetooth.qsdialog
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import android.testing.TestableLooper
 import android.widget.Button
 import android.widget.TextView
@@ -43,7 +43,7 @@
 import org.mockito.kotlin.whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @OptIn(ExperimentalCoroutinesApi::class)
 class AudioSharingDialogDelegateTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
index 9c427c6..44f9720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorTest.kt
@@ -15,7 +15,7 @@
  */
 package com.android.systemui.bluetooth.qsdialog
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -37,7 +37,7 @@
 import org.mockito.kotlin.whenever
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @OptIn(ExperimentalCoroutinesApi::class)
 class DeviceItemActionInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
index b7ed27f..94d0dfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/broadcast/ActionReceiverTest.kt
@@ -21,8 +21,8 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger
@@ -51,7 +51,7 @@
 import java.lang.IllegalStateException
 import java.util.concurrent.Executor
 
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 @SmallTest
 class ActionReceiverTest : SysuiTestCase() {
@@ -273,4 +273,4 @@
     fun testBroadcastWithWrongAction_throwsException() {
         actionReceiver.onReceive(mContext, Intent(ACTION2))
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/compose/ComposeInitializerTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/compose/ComposeInitializerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/compose/ComposeInitializerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index f5d2d42..8062358 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -96,7 +96,7 @@
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.times
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
 import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -453,7 +453,7 @@
 
         mMainExecutor.runAllReady()
 
-        verifyZeroInteractions(mTouchMonitor)
+        verifyNoMoreInteractions(mTouchMonitor)
 
         val captor = ArgumentCaptor.forClass(DreamOverlayStateController.Callback::class.java)
         verify(mStateController).addCallback(captor.capture())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
new file mode 100644
index 0000000..5efb617
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModelTest.kt
@@ -0,0 +1,166 @@
+/*
+ * 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.haptics.msdl.qs
+
+import android.service.quicksettings.Tile
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.haptics.msdl.fakeMSDLPlayer
+import com.android.systemui.haptics.msdl.tileHapticsViewModelFactory
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.panels.ui.viewmodel.fakeQsTile
+import com.android.systemui.qs.panels.ui.viewmodel.tileViewModel
+import com.android.systemui.testKosmos
+import com.google.android.msdl.data.model.MSDLToken
+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
+
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class TileHapticsViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val qsTile = kosmos.fakeQsTile
+    private val msdlPlayer = kosmos.fakeMSDLPlayer
+    private val tileViewModel = kosmos.tileViewModel
+
+    private val underTest = kosmos.tileHapticsViewModelFactory.create(tileViewModel)
+
+    @Before
+    fun setUp() {
+        underTest.activateIn(testScope)
+    }
+
+    @Test
+    fun whenTileTogglesOnFromClick_playsSwitchOnHaptics() =
+        testScope.runTest {
+            // WHEN the tile toggles on after being clicked
+            underTest.setTileInteractionState(TileHapticsViewModel.TileInteractionState.CLICKED)
+            toggleOn()
+
+            // THEN the switch on token plays
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWITCH_ON)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    @Test
+    fun whenTileTogglesOffFromClick_playsSwitchOffHaptics() =
+        testScope.runTest {
+            // WHEN the tile toggles off after being clicked
+            underTest.setTileInteractionState(TileHapticsViewModel.TileInteractionState.CLICKED)
+            toggleOff()
+
+            // THEN the switch off token plays
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWITCH_OFF)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    @Test
+    fun whenTileTogglesOnWhileIdle_doesNotPlaySwitchOnHaptics() =
+        testScope.runTest {
+            // WHEN the tile toggles on without being clicked
+            toggleOn()
+
+            // THEN no token plays
+            assertThat(msdlPlayer.latestTokenPlayed).isNull()
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    @Test
+    fun whenTileTogglesOffWhileIdle_doesNotPlaySwitchOffHaptics() =
+        testScope.runTest {
+            // WHEN the tile toggles off without being clicked
+            toggleOff()
+
+            // THEN no token plays
+            assertThat(msdlPlayer.latestTokenPlayed).isNull()
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    @Test
+    fun whenLaunchingFromLongClick_playsLongPressHaptics() =
+        testScope.runTest {
+            // WHEN the tile is long-clicked and its action state changes accordingly
+            underTest.setTileInteractionState(
+                TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+            )
+            // WHEN the activity transition (from the long-click) starts
+            underTest.onActivityLaunchTransitionStart()
+            runCurrent()
+
+            // THEN the long-press token plays
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    @Test
+    fun onLongClick_whenTileDoesNotHandleLongClick_playsFailureHaptics() =
+        testScope.runTest {
+            // WHEN the tile is long-clicked but the tile does not handle a long-click
+            val state = QSTile.State().apply { handlesLongClick = false }
+            qsTile.changeState(state)
+            underTest.setTileInteractionState(
+                TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+            )
+            runCurrent()
+
+            // THEN the failure token plays
+            assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE)
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    @Test
+    fun whenLaunchingFromClick_doesNotPlayHaptics() =
+        testScope.runTest {
+            // WHEN the tile is clicked and its action state changes accordingly
+            underTest.setTileInteractionState(TileHapticsViewModel.TileInteractionState.CLICKED)
+            // WHEN an activity transition starts (from clicking)
+            underTest.onActivityLaunchTransitionStart()
+            runCurrent()
+
+            // THEN no haptics play
+            assertThat(msdlPlayer.latestTokenPlayed).isNull()
+            assertThat(msdlPlayer.latestPropertiesPlayed).isNull()
+        }
+
+    private fun TestScope.toggleOn() {
+        qsTile.changeState(QSTile.State().apply { state = Tile.STATE_INACTIVE })
+        runCurrent()
+
+        qsTile.changeState(QSTile.State().apply { state = Tile.STATE_ACTIVE })
+        runCurrent()
+    }
+
+    private fun TestScope.toggleOff() {
+        qsTile.changeState(QSTile.State().apply { state = Tile.STATE_ACTIVE })
+        runCurrent()
+
+        qsTile.changeState(QSTile.State().apply { state = Tile.STATE_INACTIVE })
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/FgsManagerControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
index 64796f1..12bd5af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.qs.external
 
 import android.app.StatusBarManager
-import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId
 import com.android.internal.logging.UiEventLogger
@@ -31,7 +31,7 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
 @TestableLooper.RunWithLooper
 class TileRequestDialogEventLoggerTest : SysuiTestCase() {
 
@@ -138,4 +138,4 @@
         assertThat(packageName).isEqualTo(PACKAGE_NAME)
         assertThat(this.instanceId).isEqualTo(instanceId)
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt
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 adc336d..47eebf6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -65,6 +65,7 @@
 import com.android.systemui.statusbar.notification.stack.AmbientState
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ConfigurationForwarder
 import com.android.systemui.statusbar.phone.DozeScrimController
 import com.android.systemui.statusbar.phone.DozeServiceHost
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
@@ -219,6 +220,7 @@
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 mock(BouncerViewBinder::class.java),
+                mock(ConfigurationForwarder::class.java),
             )
         underTest.setupExpandedStatusBar()
         underTest.setDragDownHelper(dragDownHelper)
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 d61320e..1c196c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -15,8 +15,10 @@
  */
 package com.android.systemui.shade
 
+import android.content.res.Configuration
 import android.os.SystemClock
 import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper.RunWithLooper
 import android.view.MotionEvent
 import android.widget.FrameLayout
@@ -54,6 +56,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
 import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.ConfigurationForwarder
 import com.android.systemui.statusbar.phone.DozeScrimController
 import com.android.systemui.statusbar.phone.DozeServiceHost
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -76,9 +79,11 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
+import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.eq
 
 @ExperimentalCoroutinesApi
 @RunWith(AndroidJUnit4::class)
@@ -101,7 +106,7 @@
     @Mock private lateinit var quickSettingsController: QuickSettingsController
     @Mock
     private lateinit var notificationStackScrollLayoutController:
-        NotificationStackScrollLayoutController
+            NotificationStackScrollLayoutController
     @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
     @Mock
     private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@@ -117,12 +122,13 @@
     private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
     @Mock
     private lateinit var unfoldTransitionProgressProvider:
-        Optional<UnfoldTransitionProgressProvider>
+            Optional<UnfoldTransitionProgressProvider>
     @Mock private lateinit var notificationInsetsController: NotificationInsetsController
     @Mock private lateinit var mGlanceableHubContainerController: GlanceableHubContainerController
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    @Mock lateinit var configurationForwarder: ConfigurationForwarder
     @Captor
     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
 
@@ -136,10 +142,10 @@
         MockitoAnnotations.initMocks(this)
         underTest = spy(NotificationShadeWindowView(context, null))
         whenever(
-                underTest.findViewById<NotificationStackScrollLayout>(
-                    R.id.notification_stack_scroller
-                )
+            underTest.findViewById<NotificationStackScrollLayout>(
+                R.id.notification_stack_scroller
             )
+        )
             .thenReturn(notificationStackScrollLayout)
         whenever(underTest.findViewById<FrameLayout>(R.id.keyguard_bouncer_container))
             .thenReturn(mock())
@@ -191,6 +197,7 @@
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 mock(),
+                configurationForwarder,
             )
 
         controller.setupExpandedStatusBar()
@@ -222,6 +229,23 @@
             assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isFalse()
         }
 
+    @Test
+    @DisableFlags(AConfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun onConfigurationChanged_configForwarderNotSet() {
+        underTest.onConfigurationChanged(Configuration())
+
+        verify(configurationForwarder, never()).onConfigurationChanged(any())
+    }
+
+    @Test
+    @EnableFlags(AConfigFlags.FLAG_SHADE_WINDOW_GOES_AROUND)
+    fun onConfigurationChanged_configForwarderSet_propagatesConfig() {
+        val config = Configuration()
+        underTest.onConfigurationChanged(config)
+
+        verify(configurationForwarder).onConfigurationChanged(eq(config))
+    }
+
     private fun captureInteractionEventHandler() {
         verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
         interactionEventHandler = interactionEventHandlerCaptor.value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
similarity index 99%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index b00f9e9..f502cab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -61,6 +61,7 @@
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
 
 import com.android.settingslib.R;
 import com.android.settingslib.graph.SignalDrawable;
@@ -98,6 +99,7 @@
 import java.util.Collections;
 import java.util.List;
 
+@SmallTest
 public class NetworkControllerBaseTest extends SysuiTestCase {
     private static final String TAG = "NetworkControllerBaseTest";
     protected static final int DEFAULT_LEVEL = 2;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/RotationLockControllerImplTest.java
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
new file mode 100644
index 0000000..faf01ed
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.media.AudioManager.STREAM_RING
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.haptics.fakeVibratorHelper
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.fakeVolumeDialogController
+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)
+@TestableLooper.RunWithLooper
+class VolumeDialogRingerDrawerViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val controller = kosmos.fakeVolumeDialogController
+    private val vibratorHelper = kosmos.fakeVibratorHelper
+
+    private lateinit var underTest: VolumeDialogRingerDrawerViewModel
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.volumeDialogRingerDrawerViewModel
+    }
+
+    @Test
+    fun onSelectedRingerNormalModeButtonClicked_openDrawer() =
+        testScope.runTest {
+            val ringerViewModel by collectLastValue(underTest.ringerViewModel)
+            val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
+
+            setUpRingerModeAndOpenDrawer(normalRingerMode)
+
+            assertThat(ringerViewModel).isNotNull()
+            assertThat(ringerViewModel?.drawerState)
+                .isEqualTo(RingerDrawerState.Open(normalRingerMode))
+        }
+
+    @Test
+    fun onSelectedRingerButtonClicked_drawerOpened_closeDrawer() =
+        testScope.runTest {
+            val ringerViewModel by collectLastValue(underTest.ringerViewModel)
+            val normalRingerMode = RingerMode(RINGER_MODE_NORMAL)
+
+            setUpRingerModeAndOpenDrawer(normalRingerMode)
+            underTest.onRingerButtonClicked(normalRingerMode)
+            controller.getState()
+
+            assertThat(ringerViewModel).isNotNull()
+            assertThat(ringerViewModel?.drawerState)
+                .isEqualTo(RingerDrawerState.Closed(normalRingerMode))
+        }
+
+    @Test
+    fun onNewRingerButtonClicked_drawerOpened_updateRingerMode_closeDrawer() =
+        testScope.runTest {
+            val ringerViewModel by collectLastValue(underTest.ringerViewModel)
+            val vibrateRingerMode = RingerMode(RINGER_MODE_VIBRATE)
+
+            setUpRingerModeAndOpenDrawer(RingerMode(RINGER_MODE_NORMAL))
+            // Select vibrate ringer mode.
+            underTest.onRingerButtonClicked(vibrateRingerMode)
+            controller.getState()
+            runCurrent()
+
+            assertThat(ringerViewModel).isNotNull()
+            assertThat(
+                    ringerViewModel
+                        ?.availableButtons
+                        ?.get(ringerViewModel!!.currentButtonIndex)
+                        ?.ringerMode
+                )
+                .isEqualTo(vibrateRingerMode)
+            assertThat(ringerViewModel?.drawerState)
+                .isEqualTo(RingerDrawerState.Closed(vibrateRingerMode))
+
+            val silentRingerMode = RingerMode(RINGER_MODE_SILENT)
+            // Open drawer
+            underTest.onRingerButtonClicked(vibrateRingerMode)
+            controller.getState()
+
+            // Select silent ringer mode.
+            underTest.onRingerButtonClicked(silentRingerMode)
+            controller.getState()
+            runCurrent()
+
+            assertThat(ringerViewModel).isNotNull()
+            assertThat(
+                    ringerViewModel
+                        ?.availableButtons
+                        ?.get(ringerViewModel!!.currentButtonIndex)
+                        ?.ringerMode
+                )
+                .isEqualTo(silentRingerMode)
+            assertThat(ringerViewModel?.drawerState)
+                .isEqualTo(RingerDrawerState.Closed(silentRingerMode))
+            assertThat(controller.hasScheduledTouchFeedback).isFalse()
+            assertThat(vibratorHelper.totalVibrations).isEqualTo(2)
+        }
+
+    private fun TestScope.setUpRingerModeAndOpenDrawer(selectedRingerMode: RingerMode) {
+        controller.setStreamVolume(STREAM_RING, 50)
+        controller.setRingerMode(selectedRingerMode.value, false)
+        runCurrent()
+
+        underTest.onRingerButtonClicked(RingerMode(selectedRingerMode.value))
+        controller.getState()
+        runCurrent()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt
new file mode 100644
index 0000000..bfafdab
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.domain.interactor
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.plugins.fakeVolumeDialogController
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class VolumeDialogSliderInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private lateinit var underTest: VolumeDialogSliderInteractor
+
+    @Before
+    fun setUp() {
+        underTest = kosmos.volumeDialogSliderInteractor
+    }
+
+    @Test
+    fun settingStreamVolume_setsActiveStream() =
+        with(kosmos) {
+            testScope.runTest {
+                runCurrent()
+                // initialize the stream model
+                fakeVolumeDialogController.setStreamVolume(volumeDialogSliderType.audioStream, 0)
+
+                val sliderModel by collectLastValue(underTest.slider)
+                underTest.setStreamVolume(1)
+                runCurrent()
+
+                assertThat(sliderModel!!.isActive).isTrue()
+            }
+        }
+
+    @Test
+    fun streamVolumeIs_minMaxAreEnforced() =
+        with(kosmos) {
+            testScope.runTest {
+                runCurrent()
+                fakeVolumeDialogController.updateState {
+                    states.put(
+                        volumeDialogSliderType.audioStream,
+                        VolumeDialogController.StreamState().apply {
+                            levelMin = 0
+                            level = 2
+                            levelMax = 1
+                        },
+                    )
+                }
+
+                val sliderModel by collectLastValue(underTest.slider)
+                runCurrent()
+
+                assertThat(sliderModel!!.level).isEqualTo(1)
+            }
+        }
+}
diff --git a/packages/SystemUI/res/layout-land-television/volume_dialog.xml b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
index f77db95..4321fb0 100644
--- a/packages/SystemUI/res/layout-land-television/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land-television/volume_dialog.xml
@@ -36,6 +36,7 @@
         android:showDividers="middle" />
 
     <LinearLayout
+        android:id="@+id/volume_dialog"
         android:layout_width="@dimen/volume_dialog_width"
         android:layout_height="wrap_content"
         android:background="@drawable/volume_dialog_background"
@@ -50,9 +51,7 @@
             android:layout_width="@dimen/volume_dialog_button_size"
             android:layout_height="@dimen/volume_dialog_button_size" />
 
-        <include
-            android:id="@+id/volume_dialog_slider"
-            layout="@layout/volume_dialog_slider" />
+        <include layout="@layout/volume_dialog_slider" />
 
         <Button
             android:id="@+id/volume_dialog_settings"
diff --git a/packages/SystemUI/res/layout-land/volume_dialog.xml b/packages/SystemUI/res/layout-land/volume_dialog.xml
index f77db95..4321fb0 100644
--- a/packages/SystemUI/res/layout-land/volume_dialog.xml
+++ b/packages/SystemUI/res/layout-land/volume_dialog.xml
@@ -36,6 +36,7 @@
         android:showDividers="middle" />
 
     <LinearLayout
+        android:id="@+id/volume_dialog"
         android:layout_width="@dimen/volume_dialog_width"
         android:layout_height="wrap_content"
         android:background="@drawable/volume_dialog_background"
@@ -50,9 +51,7 @@
             android:layout_width="@dimen/volume_dialog_button_size"
             android:layout_height="@dimen/volume_dialog_button_size" />
 
-        <include
-            android:id="@+id/volume_dialog_slider"
-            layout="@layout/volume_dialog_slider" />
+        <include layout="@layout/volume_dialog_slider" />
 
         <Button
             android:id="@+id/volume_dialog_settings"
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index f77db95..4321fb0 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -36,6 +36,7 @@
         android:showDividers="middle" />
 
     <LinearLayout
+        android:id="@+id/volume_dialog"
         android:layout_width="@dimen/volume_dialog_width"
         android:layout_height="wrap_content"
         android:background="@drawable/volume_dialog_background"
@@ -50,9 +51,7 @@
             android:layout_width="@dimen/volume_dialog_button_size"
             android:layout_height="@dimen/volume_dialog_button_size" />
 
-        <include
-            android:id="@+id/volume_dialog_slider"
-            layout="@layout/volume_dialog_slider" />
+        <include layout="@layout/volume_dialog_slider" />
 
         <Button
             android:id="@+id/volume_dialog_settings"
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 450863f..59c8f06 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -107,6 +107,7 @@
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeDisplayAwareModule;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
 import com.android.systemui.shared.condition.Monitor;
@@ -265,6 +266,7 @@
         CommonSystemUIUnfoldModule.class,
         TelephonyRepositoryModule.class,
         TemporaryDisplayModule.class,
+        ShadeDisplayAwareModule.class,
         TouchpadModule.class,
         TunerModule.class,
         UserDomainLayerModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
new file mode 100644
index 0000000..215ceac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/StateAwareExpandable.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.haptics.msdl.qs
+
+import android.content.ComponentName
+import android.view.ViewGroup
+import com.android.systemui.animation.ActivityTransitionAnimator
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.animation.Expandable
+
+private fun ActivityTransitionAnimator.Controller.withStateAwareness(
+    onActivityLaunchTransitionStart: () -> Unit,
+    onActivityLaunchTransitionEnd: () -> Unit,
+): ActivityTransitionAnimator.Controller {
+    val delegate = this
+    return object : ActivityTransitionAnimator.Controller by delegate {
+        override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
+            onActivityLaunchTransitionStart()
+            delegate.onTransitionAnimationStart(isExpandingFullyAbove)
+        }
+
+        override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
+            onActivityLaunchTransitionEnd()
+            delegate.onTransitionAnimationCancelled(newKeyguardOccludedState)
+        }
+
+        override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
+            onActivityLaunchTransitionEnd()
+            delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
+        }
+    }
+}
+
+private fun DialogTransitionAnimator.Controller.withStateAwareness(
+    onDialogDrawingStart: () -> Unit,
+    onDialogDrawingEnd: () -> Unit,
+): DialogTransitionAnimator.Controller {
+    val delegate = this
+    return object : DialogTransitionAnimator.Controller by delegate {
+
+        override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
+            onDialogDrawingStart()
+            delegate.startDrawingInOverlayOf(viewGroup)
+        }
+
+        override fun stopDrawingInOverlay() {
+            onDialogDrawingEnd()
+            delegate.stopDrawingInOverlay()
+        }
+    }
+}
+
+fun Expandable.withStateAwareness(
+    onDialogDrawingStart: () -> Unit,
+    onDialogDrawingEnd: () -> Unit,
+    onActivityLaunchTransitionStart: () -> Unit,
+    onActivityLaunchTransitionEnd: () -> Unit,
+): Expandable {
+    val delegate = this
+    return object : Expandable {
+        override fun activityTransitionController(
+            launchCujType: Int?,
+            cookie: ActivityTransitionAnimator.TransitionCookie?,
+            component: ComponentName?,
+            returnCujType: Int?,
+        ): ActivityTransitionAnimator.Controller? =
+            delegate
+                .activityTransitionController(launchCujType, cookie, component, returnCujType)
+                ?.withStateAwareness(onActivityLaunchTransitionStart, onActivityLaunchTransitionEnd)
+
+        override fun dialogTransitionController(
+            cuj: DialogCuj?
+        ): DialogTransitionAnimator.Controller? =
+            delegate
+                .dialogTransitionController(cuj)
+                ?.withStateAwareness(onDialogDrawingStart, onDialogDrawingEnd)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
new file mode 100644
index 0000000..7905950
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/haptics/msdl/qs/TileHapticsViewModel.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.haptics.msdl.qs
+
+import android.service.quicksettings.Tile
+import com.android.systemui.Flags
+import com.android.systemui.animation.Expandable
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+import com.android.systemui.util.kotlin.pairwise
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.MSDLPlayer
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.transform
+
+/** A view-model to trigger haptics feedback on Quick Settings tiles */
+@OptIn(ExperimentalCoroutinesApi::class)
+class TileHapticsViewModel
+@AssistedInject
+constructor(
+    private val msdlPlayer: MSDLPlayer,
+    @Assisted private val tileViewModel: TileViewModel,
+) : ExclusiveActivatable() {
+
+    private val tileInteractionState = MutableStateFlow(TileInteractionState.IDLE)
+    private val tileAnimationState = MutableStateFlow(TileAnimationState.IDLE)
+    private val canPlayToggleHaptics: Boolean
+        get() =
+            tileAnimationState.value == TileAnimationState.IDLE &&
+                tileInteractionState.value == TileInteractionState.CLICKED
+
+    val isIdle: Boolean
+        get() =
+            tileAnimationState.value == TileAnimationState.IDLE &&
+                tileInteractionState.value == TileInteractionState.IDLE
+
+    private val toggleHapticsState: Flow<TileHapticsState> =
+        tileViewModel.state
+            .mapLatest { it.state }
+            .pairwise()
+            .transform { (previous, current) ->
+                val toggleState =
+                    when {
+                        !canPlayToggleHaptics -> TileHapticsState.NO_HAPTICS
+                        previous == Tile.STATE_INACTIVE && current == Tile.STATE_ACTIVE ->
+                            TileHapticsState.TOGGLE_ON
+                        previous == Tile.STATE_ACTIVE && current == Tile.STATE_INACTIVE ->
+                            TileHapticsState.TOGGLE_OFF
+                        else -> TileHapticsState.NO_HAPTICS
+                    }
+                emit(toggleState)
+            }
+            .distinctUntilChanged()
+
+    private val interactionHapticsState: Flow<TileHapticsState> =
+        combine(tileInteractionState, tileAnimationState) { interactionState, animationState ->
+                when {
+                    interactionState == TileInteractionState.LONG_CLICKED &&
+                        animationState == TileAnimationState.ACTIVITY_LAUNCH ->
+                        TileHapticsState.LONG_PRESS
+                    interactionState == TileInteractionState.LONG_CLICKED &&
+                        !tileViewModel.currentState.handlesLongClick ->
+                        TileHapticsState.FAILED_LONGPRESS
+                    else -> TileHapticsState.NO_HAPTICS
+                }
+            }
+            .distinctUntilChanged()
+
+    private val hapticsState: Flow<TileHapticsState> =
+        merge(toggleHapticsState, interactionHapticsState)
+
+    override suspend fun onActivated(): Nothing {
+        try {
+            hapticsState.collect { hapticsState ->
+                val tokenToPlay: MSDLToken? =
+                    when (hapticsState) {
+                        TileHapticsState.TOGGLE_ON -> MSDLToken.SWITCH_ON
+                        TileHapticsState.TOGGLE_OFF -> MSDLToken.SWITCH_OFF
+                        TileHapticsState.LONG_PRESS -> MSDLToken.LONG_PRESS
+                        TileHapticsState.FAILED_LONGPRESS -> MSDLToken.FAILURE
+                        TileHapticsState.NO_HAPTICS -> null
+                    }
+                tokenToPlay?.let {
+                    msdlPlayer.playToken(it)
+                    resetStates()
+                }
+            }
+            awaitCancellation()
+        } finally {
+            resetStates()
+        }
+    }
+
+    private fun resetStates() {
+        tileInteractionState.value = TileInteractionState.IDLE
+        tileAnimationState.value = TileAnimationState.IDLE
+    }
+
+    fun onDialogDrawingStart() {
+        tileAnimationState.value = TileAnimationState.DIALOG_LAUNCH
+    }
+
+    fun onDialogDrawingEnd() {
+        tileAnimationState.value = TileAnimationState.IDLE
+    }
+
+    fun onActivityLaunchTransitionStart() {
+        tileAnimationState.value = TileAnimationState.ACTIVITY_LAUNCH
+    }
+
+    fun onActivityLaunchTransitionEnd() {
+        tileAnimationState.value = TileAnimationState.IDLE
+    }
+
+    fun setTileInteractionState(actionState: TileInteractionState) {
+        tileInteractionState.value = actionState
+    }
+
+    fun createStateAwareExpandable(baseExpandable: Expandable): Expandable =
+        baseExpandable.withStateAwareness(
+            onDialogDrawingStart = ::onDialogDrawingStart,
+            onDialogDrawingEnd = ::onDialogDrawingEnd,
+            onActivityLaunchTransitionStart = ::onActivityLaunchTransitionStart,
+            onActivityLaunchTransitionEnd = ::onActivityLaunchTransitionEnd,
+        )
+
+    /** Models the state of toggle haptics to play */
+    enum class TileHapticsState {
+        TOGGLE_ON,
+        TOGGLE_OFF,
+        LONG_PRESS,
+        FAILED_LONGPRESS,
+        NO_HAPTICS,
+    }
+
+    /** Models the interaction that took place on the tile */
+    enum class TileInteractionState {
+        IDLE,
+        CLICKED,
+        LONG_CLICKED,
+    }
+
+    /** Models the animation state of dialogs and activity launches from a tile */
+    enum class TileAnimationState {
+        IDLE,
+        DIALOG_LAUNCH,
+        ACTIVITY_LAUNCH,
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(tileViewModel: TileViewModel): TileHapticsViewModel
+    }
+}
+
+class TileHapticsViewModelFactoryProvider
+@Inject
+constructor(private val tileHapticsViewModelFactory: TileHapticsViewModel.Factory) {
+    fun getHapticsViewModelFactory(): TileHapticsViewModel.Factory? =
+        if (Flags.msdlFeedback()) {
+            tileHapticsViewModelFactory
+        } else {
+            null
+        }
+}
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 1c7a334..99a6cda 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
@@ -73,6 +73,7 @@
                 squishiness = { squishiness },
                 coroutineScope = scope,
                 bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
+                tileHapticsViewModelFactoryProvider = viewModel.tileHapticsViewModelFactoryProvider,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 28f5463..978a353 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -59,6 +59,7 @@
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import com.android.compose.modifiers.thenIf
+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.common.ui.compose.load
@@ -97,6 +98,7 @@
                             onClick = toggleClick!!,
                             onLongClick = onLongClick,
                             onLongClickLabel = longPressLabel,
+                            hapticFeedbackEnabled = !Flags.msdlFeedback(),
                         )
                         .thenIf(accessibilityUiState != null) {
                             Modifier.semantics {
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 366bc9a..91f2da2 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
@@ -28,6 +28,7 @@
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.grid.ui.compose.VerticalSpannedGrid
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
 import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
 import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout
@@ -49,6 +50,7 @@
 constructor(
     private val iconTilesViewModel: IconTilesViewModel,
     private val viewModelFactory: InfiniteGridViewModel.Factory,
+    private val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
 ) : PaginatableGridLayout {
 
     @Composable
@@ -92,6 +94,7 @@
                 iconOnly = iconTilesViewModel.isIconTile(it.tile.spec),
                 modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)),
                 squishiness = { squishiness },
+                tileHapticsViewModelFactoryProvider = tileHapticsViewModelFactoryProvider,
                 coroutineScope = scope,
                 bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns),
             )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
index 4104e53..e1583e3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt
@@ -64,9 +64,13 @@
 import com.android.compose.animation.Expandable
 import com.android.compose.animation.bounceable
 import com.android.compose.modifiers.thenIf
+import com.android.systemui.Flags
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModel
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.panels.ui.compose.BounceableInfo
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
@@ -109,6 +113,7 @@
     squishiness: () -> Float,
     coroutineScope: CoroutineScope,
     bounceableInfo: BounceableInfo,
+    tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
     modifier: Modifier = Modifier,
 ) {
     val state by tile.state.collectAsStateWithLifecycle(tile.currentState)
@@ -116,6 +121,10 @@
     val resources = resources()
     val uiState = remember(state, resources) { state.toUiState(resources) }
     val colors = TileDefaults.getColorForState(uiState)
+    val hapticsViewModel: TileHapticsViewModel? =
+        rememberViewModel(traceName = "TileHapticsViewModel") {
+            tileHapticsViewModelFactoryProvider.getHapticsViewModelFactory()?.create(tile)
+        }
 
     // TODO(b/361789146): Draw the shapes instead of clipping
     val tileShape = TileDefaults.animateTileShape(uiState.state)
@@ -129,6 +138,7 @@
             },
         shape = tileShape,
         squishiness = squishiness,
+        hapticsViewModel = hapticsViewModel,
         modifier =
             modifier
                 .fillMaxWidth()
@@ -143,11 +153,19 @@
         TileContainer(
             onClick = {
                 tile.onClick(expandable)
+                hapticsViewModel?.setTileInteractionState(
+                    TileHapticsViewModel.TileInteractionState.CLICKED
+                )
                 if (uiState.accessibilityUiState.toggleableState != null) {
                     coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() }
                 }
             },
-            onLongClick = { tile.onLongClick(expandable) },
+            onLongClick = {
+                hapticsViewModel?.setTileInteractionState(
+                    TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+                )
+                tile.onLongClick(expandable)
+            },
             uiState = uiState,
             iconOnly = iconOnly,
         ) {
@@ -161,9 +179,21 @@
             } else {
                 val iconShape = TileDefaults.animateIconShape(uiState.state)
                 val secondaryClick: (() -> Unit)? =
-                    { tile.onSecondaryClick() }.takeIf { uiState.handlesSecondaryClick }
+                    {
+                            hapticsViewModel?.setTileInteractionState(
+                                TileHapticsViewModel.TileInteractionState.CLICKED
+                            )
+                            tile.onSecondaryClick()
+                        }
+                        .takeIf { uiState.handlesSecondaryClick }
                 val longClick: (() -> Unit)? =
-                    { tile.onLongClick(expandable) }.takeIf { uiState.handlesLongClick }
+                    {
+                            hapticsViewModel?.setTileInteractionState(
+                                TileHapticsViewModel.TileInteractionState.LONG_CLICKED
+                            )
+                            tile.onLongClick(expandable)
+                        }
+                        .takeIf { uiState.handlesLongClick }
                 LargeTileContent(
                     label = uiState.label,
                     secondaryLabel = uiState.secondaryLabel,
@@ -185,6 +215,7 @@
     color: Color,
     shape: Shape,
     squishiness: () -> Float,
+    hapticsViewModel: TileHapticsViewModel?,
     modifier: Modifier = Modifier,
     content: @Composable (Expandable) -> Unit,
 ) {
@@ -193,7 +224,7 @@
         shape = shape,
         modifier = modifier.clip(shape).verticalSquish(squishiness),
     ) {
-        content(it)
+        content(hapticsViewModel?.createStateAwareExpandable(it) ?: it)
     }
 }
 
@@ -254,6 +285,7 @@
             onLongClick = onLongClick,
             onClickLabel = uiState.accessibilityUiState.clickLabel,
             onLongClickLabel = longPressLabel,
+            hapticFeedbackEnabled = !Flags.msdlFeedback(),
         )
         .semantics {
             role = uiState.accessibilityUiState.accessibilityRole
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 72b586a..887a70f 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
@@ -18,6 +18,7 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
 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
@@ -45,6 +46,7 @@
     val squishinessViewModel: TileSquishinessViewModel,
     private val iconTilesViewModel: IconTilesViewModel,
     @Application private val applicationScope: CoroutineScope,
+    val tileHapticsViewModelFactoryProvider: TileHapticsViewModelFactoryProvider,
 ) {
 
     val columns = qsColumnsViewModel.columns
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 750f5ad..67d162b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -689,7 +689,7 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             MetricsLogger metricsLogger,
             ShadeLogger shadeLogger,
-            ConfigurationController configurationController,
+            @ShadeDisplayAware ConfigurationController configurationController,
             Provider<FlingAnimationUtils.Builder> flingAnimationUtilsBuilder,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
             ConversationNotificationManager conversationNotificationManager,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index f83548d..5b6696b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -154,7 +154,7 @@
             IActivityManager activityManager,
             DozeParameters dozeParameters,
             StatusBarStateController statusBarStateController,
-            ConfigurationController configurationController,
+            @ShadeDisplayAware ConfigurationController configurationController,
             KeyguardViewMediator keyguardViewMediator,
             KeyguardBypassController keyguardBypassController,
             @Main Executor mainExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 64085fd..f5fc1f4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -24,6 +24,7 @@
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.LayoutRes;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
@@ -53,6 +54,8 @@
 import com.android.internal.view.FloatingActionMode;
 import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
 import com.android.systemui.scene.ui.view.WindowRootView;
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
 
 /**
  * Combined keyguard and notification panel view. Also holding backdrop and scrims. This view can
@@ -68,6 +71,7 @@
     private ActionMode mFloatingActionMode;
     private FloatingToolbar mFloatingToolbar;
     private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
+    @Nullable private ConfigurationForwarder mConfigurationForwarder;
 
     private InteractionEventHandler mInteractionEventHandler;
 
@@ -162,6 +166,20 @@
     }
 
     @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (mConfigurationForwarder != null) {
+            ShadeWindowGoesAround.isUnexpectedlyInLegacyMode();
+            mConfigurationForwarder.onConfigurationChanged(newConfig);
+        }
+    }
+
+    public void setConfigurationForwarder(ConfigurationForwarder configurationForwarder) {
+        ShadeWindowGoesAround.isUnexpectedlyInLegacyMode();
+        mConfigurationForwarder = configurationForwarder;
+    }
+
+    @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 36449be..365666d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -57,6 +57,7 @@
 import com.android.systemui.res.R;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
 import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -69,6 +70,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.ConfigurationForwarder;
 import com.android.systemui.statusbar.phone.DozeScrimController;
 import com.android.systemui.statusbar.phone.DozeServiceHost;
 import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
@@ -188,7 +190,8 @@
             QuickSettingsController quickSettingsController,
             PrimaryBouncerInteractor primaryBouncerInteractor,
             AlternateBouncerInteractor alternateBouncerInteractor,
-            BouncerViewBinder bouncerViewBinder) {
+            BouncerViewBinder bouncerViewBinder,
+            @ShadeDisplayAware ConfigurationForwarder configurationForwarder) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -245,6 +248,9 @@
                             mDisableSubpixelTextTransitionListener));
         }
 
+        if (ShadeWindowGoesAround.isEnabled()) {
+            mView.setConfigurationForwarder(configurationForwarder);
+        }
         dumpManager.registerDumpable(this);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index c72db56..51f1f81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,9 +20,13 @@
 import android.content.res.Resources
 import android.view.LayoutInflater
 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
-import com.android.systemui.Flags
+import com.android.systemui.common.ui.GlobalConfig
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.res.R
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
+import com.android.systemui.statusbar.phone.ConfigurationForwarder
+import com.android.systemui.statusbar.policy.ConfigurationController
 import dagger.Module
 import dagger.Provides
 
@@ -46,7 +50,7 @@
     @ShadeDisplayAware
     @SysUISingleton
     fun provideShadeDisplayAwareContext(context: Context): Context {
-        return if (Flags.shadeWindowGoesAround()) {
+        return if (ShadeWindowGoesAround.isEnabled) {
             context
                 .createWindowContext(context.display, TYPE_APPLICATION_OVERLAY, /* options= */ null)
                 .apply { setTheme(R.style.Theme_SystemUI) }
@@ -68,4 +72,33 @@
     fun providesDisplayAwareLayoutInflater(@ShadeDisplayAware context: Context): LayoutInflater {
         return LayoutInflater.from(context)
     }
+
+    @Provides
+    @ShadeDisplayAware
+    @SysUISingleton
+    fun provideShadeWindowConfigurationController(
+        @ShadeDisplayAware shadeContext: Context,
+        factory: ConfigurationControllerImpl.Factory,
+        @GlobalConfig globalConfigConfigController: ConfigurationController,
+    ): ConfigurationController {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            factory.create(shadeContext)
+        } else {
+            globalConfigConfigController
+        }
+    }
+
+    @Provides
+    @ShadeDisplayAware
+    @SysUISingleton
+    fun provideShadeWindowConfigurationForwarder(
+        @ShadeDisplayAware shadeConfigurationController: ConfigurationController,
+        @GlobalConfig globalConfigController: ConfigurationController,
+    ): ConfigurationForwarder {
+        return if (ShadeWindowGoesAround.isEnabled) {
+            shadeConfigurationController
+        } else {
+            globalConfigController
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index d0f0386..e8a792c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -91,7 +91,7 @@
     private val tintedIconManagerFactory: TintedIconManager.Factory,
     private val privacyIconsController: HeaderPrivacyIconsController,
     private val insetsProviderStore: StatusBarContentInsetsProviderStore,
-    private val configurationController: ConfigurationController,
+    @ShadeDisplayAware private val configurationController: ConfigurationController,
     private val variableDateViewControllerFactory: VariableDateViewController.Factory,
     @Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController,
     private val dumpManager: DumpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 6f5547a..72a4650 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -51,7 +51,7 @@
 /** Module for classes related to the notification shade. */
 @Module(
     includes =
-        [StartShadeModule::class, ShadeViewProviderModule::class, ShadeDisplayAwareModule::class]
+        [StartShadeModule::class, ShadeViewProviderModule::class]
 )
 abstract class ShadeModule {
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 5afc539..1918094 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -250,7 +250,7 @@
         fun providesBatteryMeterViewController(
             @Named(SHADE_HEADER) batteryMeterView: BatteryMeterView,
             userTracker: UserTracker,
-            configurationController: ConfigurationController,
+            @ShadeDisplayAware configurationController: ConfigurationController,
             tunerService: TunerService,
             @Main mainHandler: Handler,
             contentResolver: ContentResolver,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.kt
new file mode 100644
index 0000000..6f492cf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/shared/flag/ShadeWindowGoesAround.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 com.android.systemui.shade.shared.flag
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the shade window goes around flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object ShadeWindowGoesAround {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_SHADE_WINDOW_GOES_AROUND
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.shadeWindowGoesAround()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This will throw an exception if
+     * the flag is not enabled to ensure that the refactor author catches issues in testing.
+     * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+     */
+    @JvmStatic
+    inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
index 4ba5674..823742a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.res.Configuration
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import javax.inject.Inject
@@ -28,11 +29,11 @@
 internal class LargeScreenShadeInterpolatorImpl
 @Inject
 internal constructor(
-    configurationController: ConfigurationController,
+    @ShadeDisplayAware configurationController: ConfigurationController,
     private val context: Context,
     private val splitShadeInterpolator: SplitShadeInterpolator,
     private val portraitShadeInterpolator: LargeScreenPortraitShadeInterpolator,
-    private val splitShadeStateController: SplitShadeStateController
+    private val splitShadeStateController: SplitShadeStateController,
 ) : LargeScreenShadeInterpolator {
 
     private var inSplitShade = false
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
index 3d125b8..fa108842 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
@@ -105,8 +105,8 @@
             scope.trySend(VolumeDialogEventModel.ShowSafetyWarning(flags))
         }
 
-        override fun onAccessibilityModeChanged(showA11yStream: Boolean) {
-            scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream))
+        override fun onAccessibilityModeChanged(showA11yStream: Boolean?) {
+            scope.trySend(VolumeDialogEventModel.AccessibilityModeChanged(showA11yStream == true))
         }
 
         // Captions button is remove from the Volume Dialog
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
index 2668589b..fb108c5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt
@@ -60,7 +60,7 @@
 ) {
 
     @SuppressLint("SharedFlowCreation")
-    private val mutableDismissDialogEvents = MutableSharedFlow<Unit>()
+    private val mutableDismissDialogEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
     val dialogVisibility: Flow<VolumeDialogVisibilityModel> = repository.dialogVisibility
 
     init {
@@ -74,7 +74,7 @@
             .mapNotNull { it.toVisibilityModel() }
             .onEach { model ->
                 updateVisibility { model }
-                if (model is VolumeDialogVisibilityModel.Visible) {
+                if (model is Visible) {
                     resetDismissTimeout()
                 }
             }
@@ -87,17 +87,17 @@
      */
     fun dismissDialog(reason: Int) {
         updateVisibility { visibilityModel ->
-            if (visibilityModel is VolumeDialogVisibilityModel.Dismissed) {
+            if (visibilityModel is Dismissed) {
                 visibilityModel
             } else {
-                VolumeDialogVisibilityModel.Dismissed(reason)
+                Dismissed(reason)
             }
         }
     }
 
     /** Resets current dialog timeout. */
-    suspend fun resetDismissTimeout() {
-        mutableDismissDialogEvents.emit(Unit)
+    fun resetDismissTimeout() {
+        mutableDismissDialogEvents.tryEmit(Unit)
     }
 
     private fun updateVisibility(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.kt
new file mode 100644
index 0000000..78d2d16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerButtonViewModel.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.volume.dialog.ringer.ui.viewmodel
+
+import android.annotation.DrawableRes
+import android.annotation.StringRes
+import com.android.settingslib.volume.shared.model.RingerMode
+
+/** Models ringer button that corresponds to each ringer mode. */
+data class RingerButtonViewModel(
+    /** Image resource id for the image button. */
+    @DrawableRes val imageResId: Int,
+    /** Content description for a11y. */
+    @StringRes val contentDescriptionResId: Int,
+    /** Hint label for accessibility use. */
+    @StringRes val hintLabelResId: Int,
+    /** Used to notify view model when button is clicked. */
+    val ringerMode: RingerMode,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.kt
new file mode 100644
index 0000000..f321837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerDrawerState.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.volume.dialog.ringer.ui.viewmodel
+
+import com.android.settingslib.volume.shared.model.RingerMode
+
+/** Models volume dialog ringer drawer state */
+sealed interface RingerDrawerState {
+
+    /** When clicked to open drawer */
+    data class Open(val mode: RingerMode) : RingerDrawerState
+
+    /** When clicked to close drawer */
+    data class Closed(val mode: RingerMode) : RingerDrawerState
+
+    /** Initial state when volume dialog is shown with a closed drawer. */
+    interface Initial : RingerDrawerState {
+        companion object : Initial
+    }
+}
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
new file mode 100644
index 0000000..a09bfeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/RingerViewModel.kt
@@ -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.systemui.volume.dialog.ringer.ui.viewmodel
+
+/** Models volume dialog ringer */
+data class RingerViewModel(
+    /** List of the available buttons according to the available modes */
+    val availableButtons: List<RingerButtonViewModel?>,
+    /** The index of the currently selected button */
+    val currentButtonIndex: Int,
+    /** 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
new file mode 100644
index 0000000..ac82ae3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModel.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.ringer.ui.viewmodel
+
+import android.media.AudioAttributes
+import android.media.AudioManager.RINGER_MODE_NORMAL
+import android.media.AudioManager.RINGER_MODE_SILENT
+import android.media.AudioManager.RINGER_MODE_VIBRATE
+import android.os.VibrationEffect
+import com.android.settingslib.volume.shared.model.RingerMode
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.ringer.domain.VolumeDialogRingerInteractor
+import com.android.systemui.volume.dialog.ringer.shared.model.VolumeDialogRingerModel
+import com.android.systemui.volume.dialog.shared.VolumeDialogLogger
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
+
+private const val TAG = "VolumeDialogRingerDrawerViewModel"
+
+class VolumeDialogRingerDrawerViewModel
+@AssistedInject
+constructor(
+    @VolumeDialog private val coroutineScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val interactor: VolumeDialogRingerInteractor,
+    private val vibrator: VibratorHelper,
+    private val volumeDialogLogger: VolumeDialogLogger,
+) {
+
+    private val drawerState = MutableStateFlow<RingerDrawerState>(RingerDrawerState.Initial)
+
+    val ringerViewModel: Flow<RingerViewModel> =
+        combine(interactor.ringerModel, drawerState) { ringerModel, state ->
+                ringerModel.toViewModel(state)
+            }
+            .flowOn(backgroundDispatcher)
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+            .filterNotNull()
+
+    // Vibration attributes.
+    private val sonificiationVibrationAttributes =
+        AudioAttributes.Builder()
+            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+            .build()
+
+    fun onRingerButtonClicked(ringerMode: RingerMode) {
+        if (drawerState.value is RingerDrawerState.Open) {
+            Events.writeEvent(Events.EVENT_RINGER_TOGGLE, ringerMode.value)
+            provideTouchFeedback(ringerMode)
+            interactor.setRingerMode(ringerMode)
+        }
+        drawerState.value =
+            when (drawerState.value) {
+                is RingerDrawerState.Initial -> {
+                    RingerDrawerState.Open(ringerMode)
+                }
+                is RingerDrawerState.Open -> {
+                    RingerDrawerState.Closed(ringerMode)
+                }
+                is RingerDrawerState.Closed -> {
+                    RingerDrawerState.Open(ringerMode)
+                }
+            }
+    }
+
+    private fun provideTouchFeedback(ringerMode: RingerMode) {
+        when (ringerMode.value) {
+            RINGER_MODE_NORMAL -> {
+                interactor.scheduleTouchFeedback()
+                null
+            }
+            RINGER_MODE_SILENT -> VibrationEffect.get(VibrationEffect.EFFECT_CLICK)
+            RINGER_MODE_VIBRATE -> VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)
+            else -> VibrationEffect.get(VibrationEffect.EFFECT_DOUBLE_CLICK)
+        }?.let { vibrator.vibrate(it, sonificiationVibrationAttributes) }
+    }
+
+    private fun VolumeDialogRingerModel.toViewModel(
+        drawerState: RingerDrawerState
+    ): RingerViewModel {
+        val currentIndex = availableModes.indexOf(currentRingerMode)
+        if (currentIndex == -1) {
+            volumeDialogLogger.onCurrentRingerModeIsUnsupported(currentRingerMode)
+        }
+        return RingerViewModel(
+            availableButtons = availableModes.map { mode -> toButtonViewModel(mode) },
+            currentButtonIndex = currentIndex,
+            drawerState = drawerState,
+        )
+    }
+
+    private fun VolumeDialogRingerModel.toButtonViewModel(
+        ringerMode: RingerMode
+    ): RingerButtonViewModel? {
+        return when (ringerMode.value) {
+            RINGER_MODE_SILENT ->
+                RingerButtonViewModel(
+                    imageResId = R.drawable.ic_speaker_mute,
+                    contentDescriptionResId = R.string.volume_ringer_status_silent,
+                    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,
+                    hintLabelResId = R.string.volume_ringer_hint_vibrate,
+                    ringerMode = ringerMode,
+                )
+            RINGER_MODE_NORMAL ->
+                when {
+                    isMuted && isEnabled ->
+                        RingerButtonViewModel(
+                            imageResId = R.drawable.ic_speaker_mute,
+                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            hintLabelResId = R.string.volume_ringer_hint_unmute,
+                            ringerMode = ringerMode,
+                        )
+
+                    availableModes.contains(RingerMode(RINGER_MODE_VIBRATE)) ->
+                        RingerButtonViewModel(
+                            imageResId = R.drawable.ic_speaker_on,
+                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            hintLabelResId = R.string.volume_ringer_hint_vibrate,
+                            ringerMode = ringerMode,
+                        )
+
+                    else ->
+                        RingerButtonViewModel(
+                            imageResId = R.drawable.ic_speaker_on,
+                            contentDescriptionResId = R.string.volume_ringer_status_normal,
+                            hintLabelResId = R.string.volume_ringer_hint_mute,
+                            ringerMode = ringerMode,
+                        )
+                }
+            else -> null
+        }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): VolumeDialogRingerDrawerViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
index 59c38c0..9a3aa7e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/shared/VolumeDialogLogger.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.volume.dialog.shared
 
+import com.android.settingslib.volume.shared.model.RingerMode
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.dagger.VolumeLog
@@ -43,4 +44,13 @@
             { "Dismiss: ${Events.DISMISS_REASONS[int1]}" },
         )
     }
+
+    fun onCurrentRingerModeIsUnsupported(ringerMode: RingerMode) {
+        logBuffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { int1 = ringerMode.value },
+            { "Current ringer mode: $int1, ringer mode is unsupported in ringer drawer options" },
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
index f78a8dc..876bf2c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -25,6 +25,7 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.mapNotNull
 
 /** Operates a state of particular slider of the Volume Dialog. */
@@ -37,12 +38,23 @@
 ) {
 
     val slider: Flow<VolumeDialogStreamModel> =
-        volumeDialogStateInteractor.volumeDialogState.mapNotNull {
-            it.streamModels[sliderType.audioStream]
-        }
+        volumeDialogStateInteractor.volumeDialogState
+            .mapNotNull {
+                it.streamModels[sliderType.audioStream]?.run {
+                    if (level < levelMin || level > levelMax) {
+                        copy(level = level.coerceIn(levelMin, levelMax))
+                    } else {
+                        this
+                    }
+                }
+            }
+            .distinctUntilChanged()
 
     fun setStreamVolume(userLevel: Int) {
-        volumeDialogController.setStreamVolume(sliderType.audioStream, userLevel)
+        with(volumeDialogController) {
+            setStreamVolume(sliderType.audioStream, userLevel)
+            setActiveStream(sliderType.audioStream)
+        }
     }
 
     @VolumeDialogScope
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 25a5f28..5c4d53a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -16,32 +16,55 @@
 
 package com.android.systemui.volume.dialog.sliders.ui
 
+import android.animation.Animator
+import android.animation.ObjectAnimator
 import android.view.View
-import androidx.lifecycle.viewmodel.compose.viewModel
+import android.view.animation.DecelerateInterpolator
 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.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
+import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
+import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
+import com.google.android.material.slider.LabelFormatter
+import com.google.android.material.slider.Slider
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlin.math.roundToInt
 import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L
 
 class VolumeDialogSliderViewBinder
 @AssistedInject
-constructor(@Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel) {
+constructor(
+    @Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel,
+    private val jankListenerFactory: JankListenerFactory,
+) {
 
     fun bind(view: View) {
         with(view) {
+            val sliderView: Slider =
+                requireViewById<Slider>(R.id.volume_dialog_slider).apply {
+                    labelBehavior = LabelFormatter.LABEL_GONE
+                }
             repeatWhenAttached {
                 viewModel(
                     traceName = "VolumeDialogSliderViewBinder",
                     minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                     factory = { viewModelProvider() },
                 ) { viewModel ->
-                    setSnapshotBinding {}
+                    sliderView.addOnChangeListener { _, value, fromUser ->
+                        viewModel.setStreamVolume(value.roundToInt(), fromUser)
+                    }
+
+                    viewModel.model.onEach { it.bindToSlider(sliderView) }.launchIn(this)
 
                     awaitCancellation()
                 }
@@ -49,6 +72,19 @@
         }
     }
 
+    private suspend fun VolumeDialogStreamModel.bindToSlider(slider: Slider) {
+        with(slider) {
+            valueFrom = levelMin.toFloat()
+            valueTo = levelMax.toFloat()
+            // coerce the current value to the new value range before animating it
+            value = value.coerceIn(valueFrom, valueTo)
+            setValueAnimated(
+                level.toFloat(),
+                jankListenerFactory.update(this, PROGRESS_CHANGE_ANIMATION_DURATION_MS),
+            )
+        }
+    }
+
     @AssistedFactory
     @VolumeDialogScope
     interface Factory {
@@ -58,3 +94,16 @@
         ): VolumeDialogSliderViewBinder
     }
 }
+
+private suspend fun Slider.setValueAnimated(
+    newValue: Float,
+    jankListener: Animator.AnimatorListener,
+) {
+    ObjectAnimator.ofFloat(value, newValue)
+        .apply {
+            duration = PROGRESS_CHANGE_ANIMATION_DURATION_MS
+            interpolator = DecelerateInterpolator()
+            addListener(jankListener)
+        }
+        .awaitAnimation<Float> { value = it }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index 0a00f70..f486fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -16,14 +16,20 @@
 
 package com.android.systemui.volume.dialog.sliders.ui
 
+import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewGroup
+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.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
 import javax.inject.Inject
+import kotlin.math.abs
 import kotlinx.coroutines.awaitCancellation
 
 @VolumeDialogScope
@@ -33,17 +39,44 @@
 
     fun bind(view: View) {
         with(view) {
+            val volumeDialog: View = requireViewById(R.id.volume_dialog)
+            val floatingSlidersContainer: ViewGroup =
+                requireViewById(R.id.volume_dialog_floating_sliders_container)
             repeatWhenAttached {
                 viewModel(
                     traceName = "VolumeDialogSlidersViewBinder",
                     minWindowLifecycleState = WindowLifecycleState.ATTACHED,
                     factory = { viewModelFactory.create() },
                 ) { viewModel ->
-                    setSnapshotBinding {}
+                    setSnapshotBinding {
+                        viewModel.uiModel?.sliderViewBinder?.bind(volumeDialog)
 
+                        val floatingSliderViewBinders =
+                            viewModel.uiModel?.floatingSliderViewBinders ?: emptyList()
+                        floatingSlidersContainer.ensureChildCount(
+                            viewLayoutId = R.layout.volume_dialog_slider_floating,
+                            count = floatingSliderViewBinders.size,
+                        )
+                        floatingSliderViewBinders.fastForEachIndexed { index, viewBinder ->
+                            viewBinder.bind(floatingSlidersContainer.getChildAt(index))
+                        }
+                    }
                     awaitCancellation()
                 }
             }
         }
     }
 }
+
+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) }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index 7ee722d..ea0b49d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -16,26 +16,85 @@
 
 package com.android.systemui.volume.dialog.sliders.ui.viewmodel
 
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
 import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
 
+/*
+ This prevents volume slider updates while user interacts with it. This is needed due to the
+ flawed VolumeDialogControllerImpl. It has a single threaded message queue that handles all state
+ updates and doesn't skip sequential updates of the same stream. This leads to a bottleneck when
+ user rigorously adjusts the slider.
+
+ Remove this when getting rid of the VolumeDialogControllerImpl as this doesn't happen in the
+ Volume Panel that uses the new coroutine-backed AudioRepository.
+*/
+// TODO(b/375355785) remove this
+private const val VOLUME_UPDATE_GRACE_PERIOD = 1000
+
+@OptIn(ExperimentalCoroutinesApi::class)
 class VolumeDialogSliderViewModel
 @AssistedInject
-constructor(@Assisted private val interactor: VolumeDialogSliderInteractor) {
+constructor(
+    @Assisted private val interactor: VolumeDialogSliderInteractor,
+    private val visibilityInteractor: VolumeDialogVisibilityInteractor,
+    @VolumeDialog private val coroutineScope: CoroutineScope,
+    private val systemClock: SystemClock,
+) {
 
-    val model: Flow<VolumeDialogStreamModel> = interactor.slider
+    private val userVolumeUpdates = MutableStateFlow<VolumeUpdate?>(null)
 
-    fun setStreamVolume(volume: Int) {
-        interactor.setStreamVolume(volume)
+    val model: Flow<VolumeDialogStreamModel> =
+        interactor.slider
+            .filter {
+                val lastVolumeUpdateTime = userVolumeUpdates.value?.timestampMillis ?: 0
+                getTimestampMillis() - lastVolumeUpdateTime > VOLUME_UPDATE_GRACE_PERIOD
+            }
+            .stateIn(coroutineScope, SharingStarted.Eagerly, null)
+            .filterNotNull()
+
+    init {
+        userVolumeUpdates
+            .filterNotNull()
+            .mapLatest { volume ->
+                interactor.setStreamVolume(volume.newVolumeLevel)
+                Events.writeEvent(Events.EVENT_TOUCH_LEVEL_CHANGED, model.first().stream, volume)
+            }
+            .launchIn(coroutineScope)
     }
 
+    fun setStreamVolume(volume: Int, fromUser: Boolean) {
+        if (fromUser) {
+            visibilityInteractor.resetDismissTimeout()
+            userVolumeUpdates.value =
+                VolumeUpdate(newVolumeLevel = volume, timestampMillis = getTimestampMillis())
+        }
+    }
+
+    private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
+
     @AssistedFactory
     interface Factory {
 
         fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel
     }
+
+    private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
index b5b292f..22cf89f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -16,6 +16,9 @@
 
 package com.android.systemui.volume.dialog.sliders.ui.viewmodel
 
+import androidx.compose.runtime.getValue
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
 import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
@@ -24,10 +27,9 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
@@ -39,9 +41,10 @@
     private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
     private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory,
     private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory,
-) {
+) : ExclusiveActivatable() {
 
-    val sliders: Flow<VolumeDialogSliderUiModel> =
+    private val hydrator = Hydrator("VolumeDialogSlidersViewModel")
+    private val slidersStateFlow: StateFlow<VolumeDialogSliderUiModel?> =
         slidersInteractor.sliders
             .distinctUntilChanged()
             .map { slidersModel ->
@@ -52,7 +55,13 @@
                 )
             }
             .stateIn(coroutineScope, SharingStarted.Eagerly, null)
-            .filterNotNull()
+
+    val uiModel: VolumeDialogSliderUiModel? by
+        hydrator.hydratedStateOf("VolumeDialogSlidersViewModel#uiModel", slidersStateFlow)
+
+    override suspend fun onActivated(): Nothing {
+        hydrator.activate()
+    }
 
     private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder =
         sliderViewBinderFactory.create {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
index 77733fe..eb9483f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogBinder.kt
@@ -20,6 +20,7 @@
 import android.graphics.Color
 import android.graphics.PixelFormat
 import android.graphics.drawable.ColorDrawable
+import android.view.View
 import android.view.ViewGroup
 import android.view.Window
 import android.view.WindowManager
@@ -27,6 +28,7 @@
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
 import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
 import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
 import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogGravityViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -40,6 +42,7 @@
 constructor(
     @VolumeDialog private val coroutineScope: CoroutineScope,
     private val volumeDialogViewBinder: VolumeDialogViewBinder,
+    private val slidersViewBinder: VolumeDialogSlidersViewBinder,
     private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
     private val gravityViewModel: VolumeDialogGravityViewModel,
 ) {
@@ -50,11 +53,11 @@
             dialog.setContentView(R.layout.volume_dialog)
             dialog.setCanceledOnTouchOutside(true)
 
-            settingsButtonViewBinder.bind(dialog.requireViewById(R.id.volume_dialog_settings))
-            volumeDialogViewBinder.bind(
-                dialog,
-                dialog.requireViewById(R.id.volume_dialog_container),
-            )
+            with(dialog.requireViewById<View>(R.id.volume_dialog_container)) {
+                slidersViewBinder.bind(this)
+                settingsButtonViewBinder.bind(this)
+                volumeDialogViewBinder.bind(dialog, this)
+            }
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 34ebba8..b941fde 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -159,6 +159,7 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.utils.FieldSetter;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -172,7 +173,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
-import org.mockito.internal.util.reflection.FieldSetter;
 import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
index 1d1329ac..4e0ebae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.tiles.ReduceBrightColorsTile
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.utils.FieldSetter
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -46,7 +47,6 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.internal.util.reflection.FieldSetter
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/TileHapticsViewModelFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/TileHapticsViewModelFactoryKosmos.kt
new file mode 100644
index 0000000..a798eb7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/msdl/TileHapticsViewModelFactoryKosmos.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.haptics.msdl
+
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModel
+import com.android.systemui.haptics.msdl.qs.TileHapticsViewModelFactoryProvider
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
+
+val Kosmos.tileHapticsViewModelFactory by
+    Kosmos.Fixture {
+        object : TileHapticsViewModel.Factory {
+            override fun create(tileViewModel: TileViewModel): TileHapticsViewModel =
+                TileHapticsViewModel(fakeMSDLPlayer, tileViewModel)
+        }
+    }
+
+val Kosmos.tileHapticsViewModelFactoryProvider by
+    Kosmos.Fixture { TileHapticsViewModelFactoryProvider(tileHapticsViewModelFactory) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
index b45120e..43eb93e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeVolumeDialogController.kt
@@ -43,30 +43,36 @@
     private var state = VolumeDialogController.State()
 
     override fun setActiveStream(stream: Int) {
-        // ensure streamState existence for the active stream
-        state.states.getOrElse(stream) {
-            VolumeDialogController.StreamState().also { streamState ->
-                state.states.put(stream, streamState)
-            }
-        }
-        state.activeStream = stream
-    }
-
-    override fun setStreamVolume(stream: Int, userLevel: Int) {
-        val streamState =
-            state.states.getOrElse(stream) {
+        updateState {
+            // ensure streamState existence for the active stream`
+            states.getOrElse(stream) {
                 VolumeDialogController.StreamState().also { streamState ->
                     state.states.put(stream, streamState)
                 }
             }
-        streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
+            activeStream = stream
+        }
+    }
+
+    override fun setStreamVolume(stream: Int, userLevel: Int) {
+        updateState {
+            val streamState =
+                states.getOrElse(stream) {
+                    VolumeDialogController.StreamState().also { streamState ->
+                        states.put(stream, streamState)
+                    }
+                }
+            streamState.level = userLevel.coerceIn(streamState.levelMin, streamState.levelMax)
+        }
     }
 
     override fun setRingerMode(ringerModeNormal: Int, external: Boolean) {
-        if (external) {
-            state.ringerModeExternal = ringerModeNormal
-        } else {
-            state.ringerModeInternal = ringerModeNormal
+        updateState {
+            if (external) {
+                ringerModeExternal = ringerModeNormal
+            } else {
+                ringerModeInternal = ringerModeNormal
+            }
         }
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
index 093ebd6..562980d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt
@@ -20,13 +20,10 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.plugins.qs.QSTile
 
-class FakeQSTile(
-    var user: Int,
-    var available: Boolean = true,
-) : QSTile {
+class FakeQSTile(var user: Int, var available: Boolean = true) : QSTile {
     private var tileSpec: String? = null
     var destroyed = false
-    private val state = QSTile.State()
+    private var state = QSTile.State()
     val callbacks = mutableListOf<QSTile.Callback>()
 
     override fun getTileSpec(): String? {
@@ -93,4 +90,9 @@
     override fun isListening(): Boolean {
         return false
     }
+
+    fun changeState(newState: QSTile.State) {
+        state = newState
+        callbacks.forEach { it.onStateChanged(state) }
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
index b6b0a41..b5a6bf1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.kt
@@ -16,10 +16,17 @@
 
 package com.android.systemui.qs.panels.domain.interactor
 
+import com.android.systemui.haptics.msdl.tileHapticsViewModelFactoryProvider
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.qs.panels.ui.compose.infinitegrid.InfiniteGridLayout
 import com.android.systemui.qs.panels.ui.viewmodel.iconTilesViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.infiniteGridViewModelFactory
 
 val Kosmos.infiniteGridLayout by
-    Kosmos.Fixture { InfiniteGridLayout(iconTilesViewModel, infiniteGridViewModelFactory) }
+    Kosmos.Fixture {
+        InfiniteGridLayout(
+            iconTilesViewModel,
+            infiniteGridViewModelFactory,
+            tileHapticsViewModelFactoryProvider,
+        )
+    }
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 67d9e0e..41ee260 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.panels.ui.viewmodel
 
+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
@@ -30,5 +31,6 @@
             tileSquishinessViewModel,
             iconTilesViewModel,
             applicationCoroutineScope,
+            tileHapticsViewModelFactoryProvider,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModelKosmos.kt
new file mode 100644
index 0000000..223755d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModelKosmos.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.qs.panels.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.FakeQSTile
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+val Kosmos.fakeQsTile by Kosmos.Fixture { FakeQSTile(user = 0, available = true) }
+val Kosmos.tileViewModel by
+    Kosmos.Fixture { TileViewModel(fakeQsTile, TileSpec.Companion.create("test")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/FieldSetter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/FieldSetter.kt
new file mode 100644
index 0000000..6e2d722
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/FieldSetter.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.utils
+
+import java.lang.reflect.Field
+
+object FieldSetter {
+    @JvmStatic
+    fun setField(obj: Any, fieldName: String, value: Any?) {
+        try {
+            val field = obj.javaClass.getDeclaredField(fieldName)
+            field.isAccessible = true
+            field[obj] = value
+        } catch (e: NoSuchFieldException) {
+            throw RuntimeException("Failed to set $fieldName of obj", e)
+        } catch (e: IllegalAccessException) {
+            throw RuntimeException("Failed to set $fieldName of obj", e)
+        }
+    }
+
+    @JvmStatic
+    fun setField(obj: Any?, fld: Field, value: Any?) {
+        try {
+            fld.setAccessible(true)
+            fld.set(obj, value)
+        } catch (e: IllegalAccessException) {
+            throw RuntimeException("Failed to set ${fld.getName()} of obj", e)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.kt
new file mode 100644
index 0000000..db1c01a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ringer/ui/viewmodel/VolumeDialogRingerDrawerViewModelKosmos.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.volume.dialog.ringer.ui.viewmodel
+
+import com.android.systemui.haptics.vibratorHelper
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.volume.dialog.ringer.domain.volumeDialogRingerInteractor
+import com.android.systemui.volume.dialog.shared.volumeDialogLogger
+
+val Kosmos.volumeDialogRingerDrawerViewModel by
+    Kosmos.Fixture {
+        VolumeDialogRingerDrawerViewModel(
+            backgroundDispatcher = testDispatcher,
+            coroutineScope = applicationCoroutineScope,
+            interactor = volumeDialogRingerInteractor,
+            vibrator = vibratorHelper,
+            volumeDialogLogger = volumeDialogLogger,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.kt
new file mode 100644
index 0000000..f9d4a99
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/shared/VolumeDialogLoggerKosmos.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.volume.dialog.shared
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.volumeDialogLogger by Kosmos.Fixture { VolumeDialogLogger(logcatLogBuffer()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
new file mode 100644
index 0000000..423100a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.dialog.sliders.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
+
+val Kosmos.volumeDialogSliderInteractor: VolumeDialogSliderInteractor by
+    Kosmos.Fixture {
+        VolumeDialogSliderInteractor(
+            volumeDialogSliderType,
+            volumeDialogStateInteractor,
+            volumeDialogController,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderTypeKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderTypeKosmos.kt
new file mode 100644
index 0000000..cc8c1ea
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/model/VolumeDialogSliderTypeKosmos.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.volume.dialog.sliders.domain.model
+
+import android.media.AudioManager
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.volumeDialogSliderType: VolumeDialogSliderType by
+    Kosmos.Fixture { VolumeDialogSliderType.Stream(AudioManager.STREAM_SYSTEM) }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index f6a808b..a59f4bd 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -39,6 +39,7 @@
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
 import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -87,6 +88,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelableException;
+import android.os.PermissionEnforcer;
 import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
@@ -314,6 +316,8 @@
 
     public PackageInstallerService(Context context, PackageManagerService pm,
             Supplier<PackageParser2> apexParserSupplier) {
+        super(PermissionEnforcer.fromContext(context));
+
         mContext = context;
         mPm = pm;
 
@@ -1877,23 +1881,20 @@
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.VERIFICATION_AGENT)
     public @PackageInstaller.VerificationPolicy int getVerificationPolicy() {
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("You need the "
-                    + "com.android.permission.VERIFICATION_AGENT permission "
-                    + "to get the verification policy");
-        }
+        getVerificationPolicy_enforcePermission();
         return mVerificationPolicy.get();
     }
 
     @Override
+    @EnforcePermission(android.Manifest.permission.VERIFICATION_AGENT)
     public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) {
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("You need the "
-                    + "com.android.permission.VERIFICATION_AGENT permission "
-                    + "to set the verification policy");
+        setVerificationPolicy_enforcePermission();
+        final int callingUid = getCallingUid();
+        // Only the verifier currently bound by the system can change the policy, except for Shell
+        if (!PackageManagerServiceUtils.isRootOrShell(callingUid)) {
+            mVerifierController.assertCallerIsCurrentVerifier(callingUid);
         }
         if (!isValidVerificationPolicy(policy)) {
             return false;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 9a9e434..512b195 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2884,14 +2884,13 @@
             }
             // Send the request to the verifier and wait for its response before the rest of
             // the installation can proceed.
+            final VerifierCallback verifierCallback = new VerifierCallback();
             if (!mVerifierController.startVerificationSession(mPm::snapshotComputer, userId,
                     sessionId, getPackageName(), Uri.fromFile(stageDir), signingInfo,
                     declaredLibraries, mVerificationPolicy.get(), /* extensionParams= */ null,
-                    new VerifierCallback(), /* retry= */ false)) {
-                // A verifier is installed but cannot be connected. Installation disallowed.
-                onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR,
-                        "A verifier agent is available on device but cannot be connected.",
-                        /* extras= */ null);
+                    verifierCallback, /* retry= */ false)) {
+                // A verifier is installed but cannot be connected.
+                verifierCallback.onConnectionFailed();
             }
         } else {
             // No need to check with verifier. Proceed with the rest of the verification.
@@ -2995,7 +2994,6 @@
                         onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE,
                                 "A verifier agent is available on device but cannot be connected.",
                                 bundle);
-
                     });
         }
         /**
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 4652c3a..f8e56e1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -399,6 +399,10 @@
                     return runUnarchive();
                 case "get-domain-verification-agent":
                     return runGetDomainVerificationAgent();
+                case "get-verification-policy":
+                    return runGetVerificationPolicy();
+                case "set-verification-policy":
+                    return runSetVerificationPolicy();
                 default: {
                     if (ART_SERVICE_COMMANDS.contains(cmd)) {
                         return runArtServiceCommand();
@@ -4645,6 +4649,86 @@
         return 0;
     }
 
+    private int runGetVerificationPolicy() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+                if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+                    UserManagerInternal umi =
+                            LocalServices.getService(UserManagerInternal.class);
+                    UserInfo userInfo = umi.getUserInfo(userId);
+                    if (userInfo == null) {
+                        pw.println("Failure [user " + userId + " doesn't exist]");
+                        return 1;
+                    }
+                }
+            } else {
+                pw.println("Error: Unknown option: " + opt);
+                return 1;
+            }
+        }
+        final int translatedUserId =
+                translateUserId(userId, UserHandle.USER_SYSTEM, "runGetVerificationPolicy");
+        try {
+            final IPackageInstaller installer = mInterface.getPackageInstaller();
+            // TODO(b/360129657): global verification policy should be per user
+            final int policy = installer.getVerificationPolicy();
+            pw.println(policy);
+        } catch (Exception e) {
+            pw.println("Failure [" + e.getMessage() + "]");
+            return 1;
+        }
+        return 0;
+    }
+
+    private int runSetVerificationPolicy() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+                if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+                    UserManagerInternal umi =
+                            LocalServices.getService(UserManagerInternal.class);
+                    UserInfo userInfo = umi.getUserInfo(userId);
+                    if (userInfo == null) {
+                        pw.println("Failure [user " + userId + " doesn't exist]");
+                        return 1;
+                    }
+                }
+            } else {
+                pw.println("Error: Unknown option: " + opt);
+                return 1;
+            }
+        }
+        final String policyStr = getNextArg();
+        if (policyStr == null) {
+            pw.println("Error: policy not specified");
+            return 1;
+        }
+        final int translatedUserId =
+                translateUserId(userId, UserHandle.USER_SYSTEM, "runSetVerificationPolicy");
+        try {
+            final IPackageInstaller installer = mInterface.getPackageInstaller();
+            // TODO(b/360129657): global verification policy should be per user
+            final boolean success = installer.setVerificationPolicy(Integer.parseInt(policyStr));
+            if (!success) {
+                pw.println("Failure setting verification policy.");
+                return 1;
+            }
+        } catch (Exception e) {
+            pw.println("Failure [" + e.getMessage() + "]");
+            return 1;
+        }
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -5073,6 +5157,14 @@
         pw.println("      --user: return the agent of the given user (SYSTEM_USER if unspecified)");
         pw.println("  get-package-storage-stats [--user <USER_ID>] <PACKAGE>");
         pw.println("    Return the storage stats for the given app, if present");
+        pw.println("  get-verification-policy [--user USER_ID]");
+        pw.println("    Display current verification enforcement policy which will be applied to");
+        pw.println("    all the future installation sessions");
+        pw.println("      --user: show the policy of the given user (SYSTEM_USER if unspecified)");
+        pw.println("  set-verification-policy POLICY [--user USER_ID]");
+        pw.println("    Sets the verification policy of all the future installation sessions.");
+        pw.println("      --user: set the policy of the given user (SYSTEM_USER if unspecified)");
+        pw.println("");
         pw.println("");
         printArtServiceHelp();
         pw.println("");
diff --git a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
index a35618b..78849d2 100644
--- a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
+++ b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java
@@ -18,13 +18,12 @@
 
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import static android.os.Process.INVALID_UID;
 import static android.os.Process.SYSTEM_UID;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
 
-import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.content.ComponentName;
 import android.content.Context;
@@ -114,10 +113,17 @@
 
     private final Context mContext;
     private final Handler mHandler;
+    // Guards the remote service object, as well as the verifier name and UID, which should all be
+    // changed at the same time.
+    private final Object mLock = new Object();
     @Nullable
+    @GuardedBy("mLock")
     private ServiceConnector<IVerifierService> mRemoteService;
     @Nullable
+    @GuardedBy("mLock")
     private ComponentName mRemoteServiceComponentName;
+    @GuardedBy("mLock")
+    private int mRemoteServiceUid = INVALID_UID;
     @NonNull
     private Injector mInjector;
 
@@ -143,9 +149,11 @@
      */
     @Nullable
     public String getVerifierPackageName(Supplier<Computer> snapshotSupplier, int userId) {
-        if (isVerifierConnected()) {
-            // Verifier is connected or is being connected, so it must be installed.
-            return mRemoteServiceComponentName.getPackageName();
+        synchronized (mLock) {
+            if (isVerifierConnectedLocked()) {
+                // Verifier is connected or is being connected, so it must be installed.
+                return mRemoteServiceComponentName.getPackageName();
+            }
         }
         // Verifier has been disconnected, or it hasn't been connected. Check if it's installed.
         return mInjector.getVerifierPackageName(snapshotSupplier.get(), userId);
@@ -178,16 +186,29 @@
             }
             return true;
         }
+        Computer snapshot = snapshotSupplier.get();
         Pair<ServiceConnector<IVerifierService>, ComponentName> result =
-                mInjector.getRemoteService(snapshotSupplier.get(), mContext, userId, mHandler);
+                mInjector.getRemoteService(snapshot, mContext, userId, mHandler);
         if (result == null || result.first == null) {
             if (DEBUG) {
                 Slog.i(TAG, "Unable to find a qualified verifier.");
             }
             return false;
         }
-        mRemoteService = result.first;
-        mRemoteServiceComponentName = result.second;
+        final int verifierUid = snapshot.getPackageUidInternal(
+                result.second.getPackageName(), 0, userId, /* callingUid= */ SYSTEM_UID);
+        if (verifierUid == INVALID_UID) {
+            if (DEBUG) {
+                Slog.i(TAG, "Unable to find the UID of the qualified verifier.");
+            }
+            return false;
+        }
+        synchronized (mLock) {
+            mRemoteService = result.first;
+            mRemoteServiceComponentName = result.second;
+            mRemoteServiceUid = verifierUid;
+        }
+
         if (DEBUG) {
             Slog.i(TAG, "Connecting to a qualified verifier: " + mRemoteServiceComponentName);
         }
@@ -212,10 +233,13 @@
                     }
 
                     private void destroy() {
-                        if (isVerifierConnected()) {
-                            mRemoteService.unbind();
-                            mRemoteService = null;
-                            mRemoteServiceComponentName = null;
+                        synchronized (mLock) {
+                            if (isVerifierConnectedLocked()) {
+                                mRemoteService.unbind();
+                                mRemoteService = null;
+                                mRemoteServiceComponentName = null;
+                                mRemoteServiceUid = INVALID_UID;
+                            }
                         }
                     }
                 });
@@ -223,7 +247,8 @@
         return true;
     }
 
-    private boolean isVerifierConnected() {
+    @GuardedBy("mLock")
+    private boolean isVerifierConnectedLocked() {
         return mRemoteService != null && mRemoteServiceComponentName != null;
     }
 
@@ -232,19 +257,21 @@
      * requested for verification.
      */
     public void notifyPackageNameAvailable(@NonNull String packageName) {
-        if (!isVerifierConnected()) {
-            if (DEBUG) {
-                Slog.i(TAG, "Verifier is not connected. Not notifying package name available");
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Verifier is not connected. Not notifying package name available");
+                }
+                return;
             }
-            return;
+            // Best effort. We don't check for the result.
+            mRemoteService.run(service -> {
+                if (DEBUG) {
+                    Slog.i(TAG, "Notifying package name available for " + packageName);
+                }
+                service.onPackageNameAvailable(packageName);
+            });
         }
-        // Best effort. We don't check for the result.
-        mRemoteService.run(service -> {
-            if (DEBUG) {
-                Slog.i(TAG, "Notifying package name available for " + packageName);
-            }
-            service.onPackageNameAvailable(packageName);
-        });
     }
 
     /**
@@ -253,27 +280,29 @@
      * will no longer be requested for verification, possibly because the installation is canceled.
      */
     public void notifyVerificationCancelled(@NonNull String packageName) {
-        if (!isVerifierConnected()) {
-            if (DEBUG) {
-                Slog.i(TAG, "Verifier is not connected. Not notifying verification cancelled");
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Verifier is not connected. Not notifying verification cancelled");
+                }
+                return;
             }
-            return;
+            // Best effort. We don't check for the result.
+            mRemoteService.run(service -> {
+                if (DEBUG) {
+                    Slog.i(TAG, "Notifying verification cancelled for " + packageName);
+                }
+                service.onVerificationCancelled(packageName);
+            });
         }
-        // Best effort. We don't check for the result.
-        mRemoteService.run(service -> {
-            if (DEBUG) {
-                Slog.i(TAG, "Notifying verification cancelled for " + packageName);
-            }
-            service.onVerificationCancelled(packageName);
-        });
     }
 
     /**
      * Called to notify the bound verifier agent that a package that's pending installation needs
      * to be verified right now.
      * <p>The verification request must be sent to the verifier as soon as the verifier is
-     * connected. If the connection cannot be made within {@link #CONNECTION_TIMEOUT_SECONDS}</p>
-     * of when the request is sent out, we consider the verification to be failed and notify the
+     * connected. If the connection cannot be made within the specified time limit from
+     * when the request is sent out, we consider the verification to be failed and notify the
      * installation session.</p>
      * <p>If a response is not returned from the verifier agent within a timeout duration from the
      * time the request is sent to the verifier, the verification will be considered a failure.</p>
@@ -291,43 +320,48 @@
         if (!bindToVerifierServiceIfNeeded(snapshotSupplier, userId)) {
             return false;
         }
-        if (!isVerifierConnected()) {
-            if (DEBUG) {
-                Slog.i(TAG, "Verifier is not connected. Not notifying verification required");
-            }
-            // Normally this should not happen because we just tried to bind. But if the verifier
-            // just crashed or just became unavailable, we should notify the installation session so
-            // it can finish with a verification failure.
-            return false;
-        }
         // For now, the verification id is the same as the installation session id.
         final int verificationId = installationSessionId;
-        final VerificationSession session = new VerificationSession(
-                /* id= */ verificationId,
-                /* installSessionId= */ installationSessionId,
-                packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams,
-                verificationPolicy, new VerificationSessionInterface(callback));
-        AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
-            if (!retry) {
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
                 if (DEBUG) {
-                    Slog.i(TAG, "Notifying verification required for session " + verificationId);
+                    Slog.i(TAG, "Verifier is not connected. Not notifying verification required");
                 }
-                service.onVerificationRequired(session);
-            } else {
-                if (DEBUG) {
-                    Slog.i(TAG, "Notifying verification retry for session " + verificationId);
+                // Normally this should not happen because we just tried to bind. But if the
+                // verifier just crashed or just became unavailable, we should notify the
+                // installation session so it can finish with a verification failure.
+                return false;
+            }
+            final VerificationSession session = new VerificationSession(
+                    /* id= */ verificationId,
+                    /* installSessionId= */ installationSessionId,
+                    packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams,
+                    verificationPolicy, new VerificationSessionInterface(callback));
+            AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
+                if (!retry) {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Notifying verification required for session "
+                                + verificationId);
+                    }
+                    service.onVerificationRequired(session);
+                } else {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Notifying verification retry for session "
+                                + verificationId);
+                    }
+                    service.onVerificationRetry(session);
                 }
-                service.onVerificationRetry(session);
-            }
-        }).orTimeout(mInjector.getVerifierConnectionTimeoutMillis(), TimeUnit.MILLISECONDS)
-                .whenComplete((res, err) -> {
-            if (err != null) {
-                Slog.e(TAG, "Error notifying verification request for session " + verificationId,
-                        err);
-                // Notify the installation session so it can finish with verification failure.
-                callback.onConnectionFailed();
-            }
-        });
+            }).orTimeout(mInjector.getVerifierConnectionTimeoutMillis(), TimeUnit.MILLISECONDS)
+                    .whenComplete((res, err) -> {
+                        if (err != null) {
+                            Slog.e(TAG, "Error notifying verification request for session "
+                                    + verificationId, err);
+                            // Notify the installation session so it can finish with verification
+                            // failure.
+                            callback.onConnectionFailed();
+                        }
+                    });
+        }
         // Keep track of the session status with the ID. Start counting down the session timeout.
         final long defaultTimeoutMillis = mInjector.getVerificationRequestTimeoutMillis();
         final long maxExtendedTimeoutMillis = mInjector.getMaxVerificationExtendedTimeoutMillis();
@@ -369,24 +403,27 @@
      * Called to notify the bound verifier agent that a verification request has timed out.
      */
     public void notifyVerificationTimeout(int verificationId) {
-        if (!isVerifierConnected()) {
-            if (DEBUG) {
-                Slog.i(TAG,
-                        "Verifier is not connected. Not notifying timeout for " + verificationId);
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
+                if (DEBUG) {
+                    Slog.i(TAG,
+                            "Verifier is not connected. Not notifying timeout for "
+                                    + verificationId);
+                }
+                return;
             }
-            return;
+            AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
+                if (DEBUG) {
+                    Slog.i(TAG, "Notifying timeout for " + verificationId);
+                }
+                service.onVerificationTimeout(verificationId);
+            }).whenComplete((res, err) -> {
+                if (err != null) {
+                    Slog.e(TAG, "Error notifying VerificationTimeout for session "
+                            + verificationId, err);
+                }
+            });
         }
-        AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> {
-            if (DEBUG) {
-                Slog.i(TAG, "Notifying timeout for " + verificationId);
-            }
-            service.onVerificationTimeout(verificationId);
-        }).whenComplete((res, err) -> {
-            if (err != null) {
-                Slog.e(TAG, "Error notifying VerificationTimeout for session "
-                        + verificationId, (Throwable) err);
-            }
-        });
     }
 
     /**
@@ -405,17 +442,19 @@
         }
     }
 
-    @RequiresPermission(Manifest.permission.VERIFICATION_AGENT)
-    private void checkCallerPermission() {
-        // TODO: think of a better way to test it on non-eng builds
-        if (Build.IS_ENG) {
-            return;
-        }
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("You need the"
-                    + " com.android.permission.VERIFICATION_AGENT permission"
-                    + " to use VerificationSession APIs.");
+    /**
+     * Assert that the calling UID is the same as the UID of the currently connected verifier.
+     */
+    public void assertCallerIsCurrentVerifier(int callingUid) {
+        synchronized (mLock) {
+            if (!isVerifierConnectedLocked()) {
+                throw new IllegalStateException(
+                        "Unable to proceed because the verifier has been disconnected.");
+            }
+            if (callingUid != mRemoteServiceUid) {
+                throw new IllegalStateException(
+                        "Calling uid " + callingUid + " is not the current verifier.");
+            }
         }
     }
 
@@ -429,7 +468,7 @@
 
         @Override
         public long getTimeoutTime(int verificationId) {
-            checkCallerPermission();
+            assertCallerIsCurrentVerifier(getCallingUid());
             synchronized (mVerificationStatus) {
                 final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
                 if (tracker == null) {
@@ -442,7 +481,7 @@
 
         @Override
         public long extendTimeRemaining(int verificationId, long additionalMs) {
-            checkCallerPermission();
+            assertCallerIsCurrentVerifier(getCallingUid());
             synchronized (mVerificationStatus) {
                 final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
                 if (tracker == null) {
@@ -456,7 +495,7 @@
         @Override
         public boolean setVerificationPolicy(int verificationId,
                 @PackageInstaller.VerificationPolicy int policy) {
-            checkCallerPermission();
+            assertCallerIsCurrentVerifier(getCallingUid());
             synchronized (mVerificationStatus) {
                 final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId);
                 if (tracker == null) {
@@ -469,7 +508,7 @@
 
         @Override
         public void reportVerificationIncomplete(int id, int reason) {
-            checkCallerPermission();
+            assertCallerIsCurrentVerifier(getCallingUid());
             final VerificationStatusTracker tracker;
             synchronized (mVerificationStatus) {
                 tracker = mVerificationStatus.get(id);
@@ -484,15 +523,9 @@
         }
 
         @Override
-        public void reportVerificationComplete(int id, VerificationStatus verificationStatus) {
-            reportVerificationCompleteWithExtensionResponse(id, verificationStatus,
-                    /* extensionResponse= */ null);
-        }
-
-        @Override
-        public void reportVerificationCompleteWithExtensionResponse(int id,
-                VerificationStatus verificationStatus, PersistableBundle extensionResponse) {
-            checkCallerPermission();
+        public void reportVerificationComplete(int id, VerificationStatus verificationStatus,
+                @Nullable PersistableBundle extensionResponse) {
+            assertCallerIsCurrentVerifier(getCallingUid());
             final VerificationStatusTracker tracker;
             synchronized (mVerificationStatus) {
                 tracker = mVerificationStatus.get(id);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 0b5b0d2..6a7f22e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3621,7 +3621,9 @@
                     if (mEnableBugReportKeyboardShortcut && firstDown
                             && event.isMetaPressed() && event.isCtrlPressed()) {
                         try {
-                            mActivityManagerService.requestInteractiveBugReport();
+                            if (!mActivityManagerService.launchBugReportHandlerApp()) {
+                                mActivityManagerService.requestInteractiveBugReport();
+                            }
                         } catch (RemoteException e) {
                             Slog.d(TAG, "Error taking bugreport", e);
                         }
@@ -4117,7 +4119,9 @@
             case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
                 if (complete && mEnableBugReportKeyboardShortcut) {
                     try {
-                        mActivityManagerService.requestInteractiveBugReport();
+                        if (!mActivityManagerService.launchBugReportHandlerApp()) {
+                            mActivityManagerService.requestInteractiveBugReport();
+                        }
                     } catch (RemoteException e) {
                         Slog.d(TAG, "Error taking bugreport", e);
                     }
diff --git a/services/core/java/com/android/server/security/forensic/DataAggregator.java b/services/core/java/com/android/server/security/forensic/DataAggregator.java
new file mode 100644
index 0000000..0079818
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/DataAggregator.java
@@ -0,0 +1,142 @@
+/*
+ * 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.forensic;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.security.forensic.ForensicEvent;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DataAggregator {
+    private static final String TAG = "Forensic DataAggregator";
+    private static final int MSG_SINGLE_DATA = 0;
+    private static final int MSG_BATCH_DATA = 1;
+    private static final int MSG_DISABLE = 2;
+
+    private static final int STORED_EVENTS_SIZE_LIMIT = 1024;
+    private final ForensicService mForensicService;
+    private final ArrayList<DataSource> mDataSources;
+
+    private List<ForensicEvent> mStoredEvents = new ArrayList<>();
+    private ServiceThread mHandlerThread;
+    private Handler mHandler;
+    public DataAggregator(ForensicService forensicService) {
+        mForensicService = forensicService;
+        mDataSources = new ArrayList<DataSource>();
+    }
+
+    @VisibleForTesting
+    void setHandler(Looper looper, ServiceThread serviceThread) {
+        mHandlerThread = serviceThread;
+        mHandler = new EventHandler(looper, this);
+    }
+
+    /**
+     * Initialize DataSources
+     * @return Whether the initialization succeeds.
+     */
+    // TODO: Add the corresponding data sources
+    public boolean initialize() {
+        return true;
+    }
+
+    /**
+     * Enable the data collection of all DataSources.
+     */
+    public void enable() {
+        mHandlerThread = new ServiceThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND,
+                /* allowIo */ false);
+        mHandlerThread.start();
+        mHandler = new EventHandler(mHandlerThread.getLooper(), this);
+        for (DataSource ds : mDataSources) {
+            ds.enable();
+        }
+    }
+
+    /**
+     * DataSource calls it to transmit a single event.
+     */
+    public void addSingleData(ForensicEvent event) {
+        mHandler.obtainMessage(MSG_SINGLE_DATA, event).sendToTarget();
+    }
+
+    /**
+     * DataSource calls it to transmit list of events.
+     */
+    public void addBatchData(List<ForensicEvent> events) {
+        mHandler.obtainMessage(MSG_BATCH_DATA, events).sendToTarget();
+    }
+
+    /**
+     * Disable the data collection of all DataSources.
+     */
+    public void disable() {
+        mHandler.obtainMessage(MSG_DISABLE).sendToTarget();
+    }
+
+    private void onNewSingleData(ForensicEvent event) {
+        if (mStoredEvents.size() < STORED_EVENTS_SIZE_LIMIT) {
+            mStoredEvents.add(event);
+        } else {
+            mForensicService.addNewData(mStoredEvents);
+            mStoredEvents = new ArrayList<>();
+        }
+    }
+
+    private void onNewBatchData(List<ForensicEvent> events) {
+        mForensicService.addNewData(events);
+    }
+
+    private void onDisable() {
+        for (DataSource ds : mDataSources) {
+            ds.disable();
+        }
+        mHandlerThread.quitSafely();
+        mHandlerThread = null;
+    }
+
+    private static class EventHandler extends Handler {
+        private final DataAggregator mDataAggregator;
+        EventHandler(Looper looper, DataAggregator dataAggregator) {
+            super(looper);
+            mDataAggregator = dataAggregator;
+        }
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SINGLE_DATA:
+                    mDataAggregator.onNewSingleData((ForensicEvent) msg.obj);
+                    break;
+                case MSG_BATCH_DATA:
+                    mDataAggregator.onNewBatchData((List<ForensicEvent>) msg.obj);
+                    break;
+                case MSG_DISABLE:
+                    mDataAggregator.onDisable();
+                    break;
+                default:
+                    Slog.w(TAG, "Unknown message: " + msg.what);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/security/forensic/DataSource.java b/services/core/java/com/android/server/security/forensic/DataSource.java
new file mode 100644
index 0000000..da7ee21
--- /dev/null
+++ b/services/core/java/com/android/server/security/forensic/DataSource.java
@@ -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.server.security.forensic;
+
+public interface DataSource {
+    /**
+     * Enable the data collection.
+     */
+    void enable();
+
+    /**
+     * Disable the data collection.
+     */
+    void disable();
+}
diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java
index 20c648e..53b07c0 100644
--- a/services/core/java/com/android/server/security/forensic/ForensicService.java
+++ b/services/core/java/com/android/server/security/forensic/ForensicService.java
@@ -22,6 +22,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
+import android.security.forensic.ForensicEvent;
 import android.security.forensic.IForensicService;
 import android.security.forensic.IForensicServiceCommandCallback;
 import android.security.forensic.IForensicServiceStateCallback;
@@ -32,6 +33,7 @@
 import com.android.server.SystemService;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * @hide
@@ -64,6 +66,7 @@
     private final Context mContext;
     private final Handler mHandler;
     private final BackupTransportConnection mBackupTransportConnection;
+    private final DataAggregator mDataAggregator;
     private final BinderService mBinderService;
 
     private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>();
@@ -79,6 +82,7 @@
         mContext = injector.getContext();
         mHandler = new EventHandler(injector.getLooper(), this);
         mBackupTransportConnection = injector.getBackupTransportConnection();
+        mDataAggregator = injector.getDataAggregator(this);
         mBinderService = new BinderService(this);
     }
 
@@ -167,6 +171,9 @@
                         Slog.e(TAG, "RemoteException", e);
                     }
                     break;
+                case MSG_BACKUP:
+                    mService.backup((List<ForensicEvent>) msg.obj);
+                    break;
                 default:
                     Slog.w(TAG, "Unknown message: " + msg.what);
             }
@@ -192,6 +199,10 @@
     private void makeVisible(IForensicServiceCommandCallback callback) throws RemoteException {
         switch (mState) {
             case STATE_INVISIBLE:
+                if (!mDataAggregator.initialize()) {
+                    callback.onFailure(ERROR_DATA_SOURCE_UNAVAILABLE);
+                    break;
+                }
                 mState = STATE_VISIBLE;
                 notifyStateMonitors();
                 callback.onSuccess();
@@ -227,6 +238,7 @@
                     callback.onFailure(ERROR_BACKUP_TRANSPORT_UNAVAILABLE);
                     break;
                 }
+                mDataAggregator.enable();
                 mState = STATE_ENABLED;
                 notifyStateMonitors();
                 callback.onSuccess();
@@ -243,6 +255,7 @@
         switch (mState) {
             case STATE_ENABLED:
                 mBackupTransportConnection.release();
+                mDataAggregator.disable();
                 mState = STATE_VISIBLE;
                 notifyStateMonitors();
                 callback.onSuccess();
@@ -255,6 +268,17 @@
         }
     }
 
+    /**
+     * Add a list of ForensicEvent.
+     */
+    public void addNewData(List<ForensicEvent> events) {
+        mHandler.obtainMessage(MSG_BACKUP, events).sendToTarget();
+    }
+
+    private void backup(List<ForensicEvent> events) {
+        mBackupTransportConnection.addData(events);
+    }
+
     @Override
     public void onStart() {
         try {
@@ -275,6 +299,8 @@
         Looper getLooper();
 
         BackupTransportConnection getBackupTransportConnection();
+
+        DataAggregator getDataAggregator(ForensicService forensicService);
     }
 
     private static final class InjectorImpl implements Injector {
@@ -303,6 +329,11 @@
         public BackupTransportConnection getBackupTransportConnection() {
             return new BackupTransportConnection(mContext);
         }
+
+        @Override
+        public DataAggregator getDataAggregator(ForensicService forensicService) {
+            return new DataAggregator(forensicService);
+        }
     }
 }
 
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b87867a..6baab25 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -3037,8 +3037,13 @@
                     || context.getPackageManager().hasSystemFeature(
                             PackageManager.FEATURE_WIFI_RTT)) {
                 t.traceBegin("RangingService");
-                mSystemServiceManager.startServiceFromJar(RANGING_SERVICE_CLASS,
-                        RANGING_APEX_SERVICE_JAR_PATH);
+                // TODO: b/375264320 - Remove after RELEASE_RANGING_STACK is ramped to next.
+                try {
+                    mSystemServiceManager.startServiceFromJar(RANGING_SERVICE_CLASS,
+                            RANGING_APEX_SERVICE_JAR_PATH);
+                } catch (Throwable e) {
+                    Slog.d(TAG, "service-ranging.jar not found, not starting RangingService");
+                }
                 t.traceEnd();
             }
         }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
index 2461798..3046d4b 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -49,6 +50,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.infra.ServiceConnector;
@@ -122,6 +124,10 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        // Mock that the UID of this test becomes the UID of the verifier
+        when(mSnapshot.getPackageUidInternal(anyString(), anyLong(), anyInt(), anyInt()))
+                .thenReturn(InstrumentationRegistry.getInstrumentation().getContext()
+                        .getApplicationInfo().uid);
         when(mInjector.getVerifierPackageName(any(Computer.class), anyInt())).thenReturn(
                 TEST_VERIFIER_COMPONENT_NAME.getPackageName());
         when(mInjector.getRemoteService(
diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
index 2b55303..feb00e7 100644
--- a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
+++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java
@@ -19,23 +19,35 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
+import android.security.forensic.ForensicEvent;
 import android.security.forensic.IForensicServiceCommandCallback;
 import android.security.forensic.IForensicServiceStateCallback;
+import android.util.ArrayMap;
+
+import com.android.server.ServiceThread;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
 public class ForensicServiceTest {
     private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN;
     private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE;
@@ -55,10 +67,12 @@
     @Mock
     private Context mContext;
     private BackupTransportConnection mBackupTransportConnection;
-
+    private DataAggregator mDataAggregator;
     private ForensicService mForensicService;
     private TestLooper mTestLooper;
     private Looper mLooper;
+    private TestLooper mTestLooperOfDataAggregator;
+    private Looper mLooperOfDataAggregator;
 
     @SuppressLint("VisibleForTests")
     @Before
@@ -67,6 +81,8 @@
 
         mTestLooper = new TestLooper();
         mLooper = mTestLooper.getLooper();
+        mTestLooperOfDataAggregator = new TestLooper();
+        mLooperOfDataAggregator = mTestLooperOfDataAggregator.getLooper();
         mForensicService = new ForensicService(new MockInjector(mContext));
         mForensicService.onStart();
     }
@@ -121,6 +137,8 @@
         assertEquals(STATE_INVISIBLE, scb1.mState);
         assertEquals(STATE_INVISIBLE, scb2.mState);
 
+        doReturn(true).when(mDataAggregator).initialize();
+
         CommandCallback ccb = new CommandCallback();
         mForensicService.getBinderService().makeVisible(ccb);
         mTestLooper.dispatchAll();
@@ -130,6 +148,29 @@
     }
 
     @Test
+    public void testMakeVisible_FromInvisible_TwoMonitors_DataSourceUnavailable()
+            throws RemoteException {
+        mForensicService.setState(STATE_INVISIBLE);
+        StateCallback scb1 = new StateCallback();
+        StateCallback scb2 = new StateCallback();
+        mForensicService.getBinderService().monitorState(scb1);
+        mForensicService.getBinderService().monitorState(scb2);
+        mTestLooper.dispatchAll();
+        assertEquals(STATE_INVISIBLE, scb1.mState);
+        assertEquals(STATE_INVISIBLE, scb2.mState);
+
+        doReturn(false).when(mDataAggregator).initialize();
+
+        CommandCallback ccb = new CommandCallback();
+        mForensicService.getBinderService().makeVisible(ccb);
+        mTestLooper.dispatchAll();
+        assertEquals(STATE_INVISIBLE, scb1.mState);
+        assertEquals(STATE_INVISIBLE, scb2.mState);
+        assertNotNull(ccb.mErrorCode);
+        assertEquals(ERROR_DATA_SOURCE_UNAVAILABLE, ccb.mErrorCode.intValue());
+    }
+
+    @Test
     public void testMakeVisible_FromVisible_TwoMonitors() throws RemoteException {
         mForensicService.setState(STATE_VISIBLE);
         StateCallback scb1 = new StateCallback();
@@ -262,6 +303,8 @@
         CommandCallback ccb = new CommandCallback();
         mForensicService.getBinderService().enable(ccb);
         mTestLooper.dispatchAll();
+
+        verify(mDataAggregator, times(1)).enable();
         assertEquals(STATE_ENABLED, scb1.mState);
         assertEquals(STATE_ENABLED, scb2.mState);
         assertNull(ccb.mErrorCode);
@@ -361,14 +404,67 @@
 
         doNothing().when(mBackupTransportConnection).release();
 
+        ServiceThread mockThread = spy(ServiceThread.class);
+        mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
         CommandCallback ccb = new CommandCallback();
         mForensicService.getBinderService().disable(ccb);
         mTestLooper.dispatchAll();
+        mTestLooperOfDataAggregator.dispatchAll();
+        // TODO: We can verify the data sources once we implement them.
+        verify(mockThread, times(1)).quitSafely();
         assertEquals(STATE_VISIBLE, scb1.mState);
         assertEquals(STATE_VISIBLE, scb2.mState);
         assertNull(ccb.mErrorCode);
     }
 
+    @Test
+    public void testDataAggregator_AddBatchData() {
+        mForensicService.setState(STATE_ENABLED);
+        ServiceThread mockThread = spy(ServiceThread.class);
+        mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+
+        String eventOneType = "event_one_type";
+        String eventOneMapKey = "event_one_map_key";
+        String eventOneMapVal = "event_one_map_val";
+        Map<String, String> eventOneMap = new ArrayMap<String, String>();
+        eventOneMap.put(eventOneMapKey, eventOneMapVal);
+        ForensicEvent eventOne = new ForensicEvent(eventOneType, eventOneMap);
+
+        String eventTwoType = "event_two_type";
+        String eventTwoMapKey = "event_two_map_key";
+        String eventTwoMapVal = "event_two_map_val";
+        Map<String, String> eventTwoMap = new ArrayMap<String, String>();
+        eventTwoMap.put(eventTwoMapKey, eventTwoMapVal);
+        ForensicEvent eventTwo = new ForensicEvent(eventTwoType, eventTwoMap);
+
+        List<ForensicEvent> events = new ArrayList<>();
+        events.add(eventOne);
+        events.add(eventTwo);
+
+        doReturn(true).when(mBackupTransportConnection).addData(any());
+
+        mDataAggregator.addBatchData(events);
+        mTestLooperOfDataAggregator.dispatchAll();
+        mTestLooper.dispatchAll();
+
+        ArgumentCaptor<List<ForensicEvent>> captor = ArgumentCaptor.forClass(List.class);
+        verify(mBackupTransportConnection).addData(captor.capture());
+        List<ForensicEvent> receivedEvents = captor.getValue();
+        assertEquals(receivedEvents.size(), 2);
+
+        assertEquals(receivedEvents.getFirst().getType(), eventOneType);
+        assertEquals(receivedEvents.getFirst().getKeyValuePairs().size(), 1);
+        assertEquals(receivedEvents.getFirst().getKeyValuePairs().get(eventOneMapKey),
+                eventOneMapVal);
+
+        assertEquals(receivedEvents.getLast().getType(), eventTwoType);
+        assertEquals(receivedEvents.getLast().getKeyValuePairs().size(), 1);
+        assertEquals(receivedEvents.getLast().getKeyValuePairs().get(eventTwoMapKey),
+                eventTwoMapVal);
+
+    }
+
     private class MockInjector implements ForensicService.Injector {
         private final Context mContext;
 
@@ -393,6 +489,11 @@
             return mBackupTransportConnection;
         }
 
+        @Override
+        public DataAggregator getDataAggregator(ForensicService forensicService) {
+            mDataAggregator = spy(new DataAggregator(forensicService));
+            return mDataAggregator;
+        }
     }
 
     private static class StateCallback extends IForensicServiceStateCallback.Stub {
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 62670b4..6596ee9 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -822,9 +822,9 @@
     void assertTakeBugreport(boolean wasCalled) throws RemoteException {
         mTestLooper.dispatchAll();
         if (wasCalled) {
-            verify(mActivityManagerService).requestInteractiveBugReport();
+            verify(mActivityManagerService).launchBugReportHandlerApp();
         } else {
-            verify(mActivityManagerService, never()).requestInteractiveBugReport();
+            verify(mActivityManagerService, never()).launchBugReportHandlerApp();
         }
 
     }
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index f803717..7adcd46 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -101,7 +101,7 @@
     private Set<String> mPreviousExtraKeys;
     private final Object mExtrasLock = new Object();
     private Uri mAddress;
-    private int mAddressPresentation;
+    private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN;
     private String mCallerDisplayName;
     private int mCallerDisplayNamePresentation;
     private int mCallDirection;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index ad7d987..29d3942 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -2164,7 +2164,7 @@
     private CallAudioState mCallAudioState;
     private CallEndpoint mCallEndpoint;
     private Uri mAddress;
-    private int mAddressPresentation;
+    private int mAddressPresentation = TelecomManager.PRESENTATION_UNKNOWN;
     private String mCallerDisplayName;
     private int mCallerDisplayNamePresentation;
     private boolean mRingbackRequested = false;