Merge "Fix updated method names in javadoc." into main
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb
index a40adac..82ef3e6 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShimPriv_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "10093150"
+    build_id: "11947186"
     target: "CtsShim"
     source_file: "aosp_arm64/CtsShimPriv.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "udc-dev"
+  git_branch: "main"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb
index 96444ba..7d0e5d7 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__arm_CtsShim_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "10093150"
+    build_id: "11947186"
     target: "CtsShim"
     source_file: "aosp_arm64/CtsShim.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "udc-dev"
+  git_branch: "main"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb
index 4d6f8ed..be32060 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShimPriv_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "10093150"
+    build_id: "11947186"
     target: "CtsShim"
     source_file: "aosp_x86_64/CtsShimPriv.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "udc-dev"
+  git_branch: "main"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb
index bfd6788..1a6448a 100644
--- a/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb
+++ b/.prebuilt_info/prebuilt_info_packages_CtsShim_apk__x86_CtsShim_apk.asciipb
@@ -1,6 +1,6 @@
 drops {
   android_build_drop {
-    build_id: "10093150"
+    build_id: "11947186"
     target: "CtsShim"
     source_file: "aosp_x86_64/CtsShim.apk"
   }
@@ -8,7 +8,7 @@
   version: ""
   version_group: ""
   git_project: "platform/frameworks/base"
-  git_branch: "udc-dev"
+  git_branch: "main"
   transform: TRANSFORM_NONE
   transform_options {
   }
diff --git a/Android.bp b/Android.bp
index af312bf..2becf07 100644
--- a/Android.bp
+++ b/Android.bp
@@ -404,6 +404,7 @@
         "android.hardware.common.fmq-V1-java",
         "bouncycastle-repackaged-unbundled",
         "com.android.sysprop.foldlockbehavior",
+        "com.android.sysprop.view",
         "framework-internal-utils",
         // If MimeMap ever becomes its own APEX, then this dependency would need to be removed
         // in favor of an API stubs dependency in java_library "framework" below.
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp
index 98767ee..3534624 100644
--- a/cmds/bootanimation/Android.bp
+++ b/cmds/bootanimation/Android.bp
@@ -74,7 +74,4 @@
         "libGLESv2",
         "libgui",
     ],
-    whole_static_libs: [
-        "libc++fs",
-    ],
 }
diff --git a/core/api/current.txt b/core/api/current.txt
index a819b6e..244cad0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5605,7 +5605,6 @@
     method @Deprecated public void onCancel(android.content.DialogInterface);
     method @Deprecated public android.app.Dialog onCreateDialog(android.os.Bundle);
     method @Deprecated public void onDismiss(android.content.DialogInterface);
-    method public android.view.LayoutInflater onGetLayoutInflater(android.os.Bundle);
     method @Deprecated public void setCancelable(boolean);
     method @Deprecated public void setShowsDialog(boolean);
     method @Deprecated public void setStyle(int, int);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index ffb920b..15b13dc 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -757,15 +757,6 @@
     void addStartInfoTimestamp(int key, long timestampNs, int userId);
 
     /**
-    * Reports view related timestamps to be added to the calling apps most
-    * recent {@link ApplicationStartInfo}.
-    *
-    * @param renderThreadDrawStartTimeNs Clock monotonic time in nanoseconds of RenderThread draw start
-    * @param framePresentedTimeNs        Clock monotonic time in nanoseconds of frame presented
-    */
-    oneway void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs, long framePresentedTimeNs);
-
-    /**
      * Return a list of {@link ApplicationExitInfo} records.
      *
      * <p class="note"> Note: System stores these historical information in a ring buffer, older
diff --git a/core/java/android/database/DefaultDatabaseErrorHandler.java b/core/java/android/database/DefaultDatabaseErrorHandler.java
old mode 100755
new mode 100644
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 61f1ee1..e2159f7 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -38,6 +38,9 @@
 import android.content.Context;
 import android.content.DialogInterface;
 import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
@@ -193,7 +196,11 @@
         @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
         @NonNull
         public BiometricPrompt.Builder setLogoRes(@DrawableRes int logoRes) {
-            mPromptInfo.setLogoRes(logoRes);
+            if (mPromptInfo.getLogoBitmap() != null) {
+                throw new IllegalStateException(
+                        "Exclusively one of logo resource or logo bitmap can be set");
+            }
+            mPromptInfo.setLogo(logoRes, convertDrawableToBitmap(mContext.getDrawable(logoRes)));
             return this;
         }
 
@@ -212,7 +219,11 @@
         @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED)
         @NonNull
         public BiometricPrompt.Builder setLogoBitmap(@NonNull Bitmap logoBitmap) {
-            mPromptInfo.setLogoBitmap(logoBitmap);
+            if (mPromptInfo.getLogoRes() != -1) {
+                throw new IllegalStateException(
+                        "Exclusively one of logo resource or logo bitmap can be set");
+            }
+            mPromptInfo.setLogo(-1, logoBitmap);
             return this;
         }
 
@@ -1516,4 +1527,29 @@
     private static boolean isCredentialAllowed(@Authenticators.Types int allowedAuthenticators) {
         return (allowedAuthenticators & Authenticators.DEVICE_CREDENTIAL) != 0;
     }
+
+    /** Converts {@code drawable} to a {@link Bitmap}. */
+    private static Bitmap convertDrawableToBitmap(Drawable drawable) {
+        if (drawable == null) {
+            return null;
+        }
+
+        if (drawable instanceof BitmapDrawable) {
+            return ((BitmapDrawable) drawable).getBitmap();
+        }
+
+        Bitmap bitmap;
+        if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+            // Single color bitmap will be created of 1x1 pixel
+        } else {
+            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+                    drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+        }
+
+        final Canvas canvas = new Canvas(bitmap);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+        return bitmap;
+    }
 }
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index bb07b9b..f4a3c87 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -217,14 +217,17 @@
     // LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
 
     // Setters
-    public void setLogoRes(@DrawableRes int logoRes) {
-        mLogoRes = logoRes;
-        checkOnlyOneLogoSet();
-    }
 
-    public void setLogoBitmap(@NonNull Bitmap logoBitmap) {
+    /**
+     * Sets logo res and bitmap
+     *
+     * @param logoRes    The logo res set by the app; Or -1 if the app sets bitmap directly.
+     * @param logoBitmap The bitmap from logoRes if the app sets logoRes; Or the bitmap set by the
+     *                   app directly.
+     */
+    public void setLogo(@DrawableRes int logoRes, @NonNull Bitmap logoBitmap) {
+        mLogoRes = logoRes;
         mLogoBitmap = logoBitmap;
-        checkOnlyOneLogoSet();
     }
 
     public void setLogoDescription(@NonNull String logoDescription) {
@@ -326,13 +329,29 @@
     }
 
     // Getters
+
+    /**
+     * Returns the logo bitmap either from logo resource or bitmap passed in from the app.
+     */
+    public Bitmap getLogo() {
+        return mLogoBitmap;
+    }
+
+    /**
+     * Returns the logo res set by the app.
+     */
     @DrawableRes
     public int getLogoRes() {
         return mLogoRes;
     }
 
+    /**
+     * Returns the logo bitmap set by the app.
+     */
     public Bitmap getLogoBitmap() {
-        return mLogoBitmap;
+        // If mLogoRes has been set, return null since mLogoBitmap is from the res, but not from
+        // the app directly.
+        return mLogoRes == -1 ? mLogoBitmap : null;
     }
 
     public String getLogoDescription() {
@@ -436,10 +455,4 @@
         return mComponentNameForConfirmDeviceCredentialActivity;
     }
 
-    private void checkOnlyOneLogoSet() {
-        if (mLogoRes != -1 && mLogoBitmap != null) {
-            throw new IllegalStateException(
-                    "Exclusively one of logo resource or logo bitmap can be set");
-        }
-    }
 }
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index 7bdd53d..02f3a25 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -200,6 +200,8 @@
             POWER_COMPONENT_AUDIO,
             POWER_COMPONENT_VIDEO,
             POWER_COMPONENT_FLASHLIGHT,
+            POWER_COMPONENT_CAMERA,
+            POWER_COMPONENT_GNSS,
     };
 
     static final int COLUMN_INDEX_BATTERY_CONSUMER_TYPE = 0;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 3e6223a..065b3d6 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1994,7 +1994,8 @@
 
         // STATES2 bits that are used for Power Stats tracking
         public static final int IMPORTANT_FOR_POWER_STATS_STATES2 =
-                STATE2_VIDEO_ON_FLAG | STATE2_FLASHLIGHT_FLAG | STATE2_CAMERA_FLAG;
+                STATE2_VIDEO_ON_FLAG | STATE2_FLASHLIGHT_FLAG | STATE2_CAMERA_FLAG
+                | STATE2_GPS_SIGNAL_QUALITY_MASK;
 
         @UnsupportedAppUsage
         public int states2;
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
old mode 100755
new mode 100644
diff --git a/core/java/android/telephony/SubscriptionPlan.aidl b/core/java/android/telephony/SubscriptionPlan.aidl
old mode 100755
new mode 100644
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
old mode 100755
new mode 100644
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
old mode 100755
new mode 100644
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 54ee375..a26150c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -177,7 +177,6 @@
 import android.graphics.RenderNode;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
-import android.hardware.SyncFence;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.display.DisplayManagerGlobal;
@@ -203,6 +202,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.sysprop.DisplayProperties;
+import android.sysprop.ViewProperties;
 import android.text.TextUtils;
 import android.util.AndroidRuntimeException;
 import android.util.DisplayMetrics;
@@ -219,7 +219,6 @@
 import android.view.InputDevice.InputSourceClass;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl.Transaction;
-import android.view.SurfaceControl.TransactionStats;
 import android.view.View.AttachInfo;
 import android.view.View.FocusDirection;
 import android.view.View.MeasureSpec;
@@ -294,7 +293,6 @@
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 /**
  * The top of a view hierarchy, implementing the needed protocol between View
@@ -1191,13 +1189,6 @@
     private String mFpsTraceName;
     private String mLargestViewTraceName;
 
-    private final boolean mAppStartInfoTimestampsFlagValue;
-    @GuardedBy("this")
-    private boolean mAppStartTimestampsSent = false;
-    private boolean mAppStartTrackingStarted = false;
-    private long mRenderThreadDrawStartTimeNs = -1;
-    private long mFirstFramePresentedTimeNs = -1;
-
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
     private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1209,6 +1200,7 @@
             Flags.enableInvalidateCheckThread();
     private static boolean sSurfaceFlingerBugfixFlagValue =
             com.android.graphics.surfaceflinger.flags.Flags.vrrBugfix24q4();
+    private static final boolean sEnableVrr = ViewProperties.vrr_enabled().orElse(true);
 
     static {
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
@@ -1314,8 +1306,6 @@
         } else {
             mSensitiveContentProtectionService = null;
         }
-
-        mAppStartInfoTimestampsFlagValue = android.app.Flags.appStartInfoTimestamps();
     }
 
     public static void addFirstDrawHandler(Runnable callback) {
@@ -2588,12 +2578,6 @@
                     notifySurfaceDestroyed();
                 }
                 destroySurface();
-
-                // Reset so they can be sent again for warm starts.
-                mAppStartTimestampsSent = false;
-                mAppStartTrackingStarted = false;
-                mRenderThreadDrawStartTimeNs = -1;
-                mFirstFramePresentedTimeNs = -1;
             }
         }
     }
@@ -4392,30 +4376,6 @@
                 reportDrawFinished(t, seqId);
             }
         });
-
-        // Only trigger once per {@link ViewRootImpl} instance, so don't add listener if
-        // {link mTransactionCompletedTimeNs} has already been set.
-        if (mAppStartInfoTimestampsFlagValue && !mAppStartTrackingStarted) {
-            mAppStartTrackingStarted = true;
-            Transaction transaction = new Transaction();
-            transaction.addTransactionCompletedListener(mExecutor,
-                    new Consumer<TransactionStats>() {
-                        @Override
-                        public void accept(TransactionStats transactionStats) {
-                            SyncFence presentFence = transactionStats.getPresentFence();
-                            if (presentFence.awaitForever()) {
-                                if (mFirstFramePresentedTimeNs == -1) {
-                                    // Only trigger once per {@link ViewRootImpl} instance.
-                                    mFirstFramePresentedTimeNs = presentFence.getSignalTime();
-                                    maybeSendAppStartTimes();
-                                }
-                            }
-                            presentFence.close();
-                        }
-                    });
-            applyTransactionOnDraw(transaction);
-        }
-
         if (DEBUG_BLAST) {
             Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
         }
@@ -4423,45 +4383,6 @@
         mWmsRequestSyncGroup.add(this, null /* runnable */);
     }
 
-    private void maybeSendAppStartTimes() {
-        synchronized (this) {
-            if (mAppStartTimestampsSent) {
-                // Don't send timestamps more than once.
-                return;
-            }
-
-            // If we already have {@link mRenderThreadDrawStartTimeNs} then pass it through, if not
-            // post to main thread and check if we have it there.
-            if (mRenderThreadDrawStartTimeNs != -1) {
-                sendAppStartTimesLocked();
-            } else {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        synchronized (ViewRootImpl.this) {
-                            if (mRenderThreadDrawStartTimeNs == -1) {
-                                return;
-                            }
-                            sendAppStartTimesLocked();
-                        }
-                    }
-                });
-            }
-        }
-    }
-
-    @GuardedBy("this")
-    private void sendAppStartTimesLocked() {
-        try {
-            ActivityManager.getService().reportStartInfoViewTimestamps(
-                    mRenderThreadDrawStartTimeNs, mFirstFramePresentedTimeNs);
-            mAppStartTimestampsSent = true;
-        } catch (RemoteException e) {
-            // Ignore, timestamps may be lost.
-            if (DBG) Log.d(TAG, "Exception attempting to report start timestamps.", e);
-        }
-    }
-
     /**
      * Helper used to notify the service to block projection when a sensitive
      * view (the view displays sensitive content) is attached to the window.
@@ -5648,13 +5569,7 @@
                     registerCallbackForPendingTransactions();
                 }
 
-                long timeNs = SystemClock.uptimeNanos();
                 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
-
-                // Only trigger once per {@link ViewRootImpl} instance.
-                if (mAppStartInfoTimestampsFlagValue && mRenderThreadDrawStartTimeNs == -1) {
-                    mRenderThreadDrawStartTimeNs = timeNs;
-                }
             } else {
                 // If we get here with a disabled & requested hardware renderer, something went
                 // wrong (an invalidate posted right before we destroyed the hardware surface
@@ -12924,13 +12839,13 @@
 
     private boolean shouldSetFrameRateCategory() {
         // use toolkitSetFrameRate flag to gate the change
-        return  mSurface.isValid() && shouldEnableDvrr();
+        return shouldEnableDvrr() && mSurface.isValid() && shouldEnableDvrr();
     }
 
     private boolean shouldSetFrameRate() {
         // use toolkitSetFrameRate flag to gate the change
-        return mSurface.isValid() && mPreferredFrameRate >= 0
-                && shouldEnableDvrr() && !mIsFrameRateConflicted;
+        return shouldEnableDvrr() && mSurface.isValid() && mPreferredFrameRate >= 0
+                && !mIsFrameRateConflicted;
     }
 
     private boolean shouldTouchBoost(int motionEventAction, int windowType) {
@@ -12965,7 +12880,7 @@
      * @param view The View with the ThreadedRenderer animation that started.
      */
     public void addThreadedRendererView(View view) {
-        if (!mThreadedRendererViews.contains(view)) {
+        if (shouldEnableDvrr() && !mThreadedRendererViews.contains(view)) {
             mThreadedRendererViews.add(view);
         }
     }
@@ -12977,7 +12892,8 @@
      */
     public void removeThreadedRendererView(View view) {
         mThreadedRendererViews.remove(view);
-        if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) {
+        if (shouldEnableDvrr()
+                && !mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) {
             mInvalidationIdleMessagePosted = true;
             mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS);
         }
@@ -13198,7 +13114,7 @@
 
     private boolean shouldEnableDvrr() {
         // uncomment this when we are ready for enabling dVRR
-        if (sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
+        if (sEnableVrr && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
             return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced();
         }
         return false;
diff --git a/core/java/android/widget/CursorTreeAdapter.java b/core/java/android/widget/CursorTreeAdapter.java
old mode 100755
new mode 100644
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
old mode 100755
new mode 100644
diff --git a/core/java/android/widget/ListPopupWindow.java b/core/java/android/widget/ListPopupWindow.java
old mode 100755
new mode 100644
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
old mode 100755
new mode 100644
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index f24bc74..57bded7 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -113,6 +114,8 @@
     private final CustomAnimationInfo mCustomAnimationInfo;
 
     private final int mLetterboxColor;
+    @NonNull
+    private final Rect mTouchableRegion;
 
     /**
      * Create a new {@link BackNavigationInfo} instance.
@@ -128,7 +131,8 @@
             boolean isPrepareRemoteAnimation,
             boolean isAnimationCallback,
             @Nullable CustomAnimationInfo customAnimationInfo,
-            int letterboxColor) {
+            int letterboxColor,
+            @Nullable Rect touchableRegion) {
         mType = type;
         mOnBackNavigationDone = onBackNavigationDone;
         mOnBackInvokedCallback = onBackInvokedCallback;
@@ -136,6 +140,7 @@
         mAnimationCallback = isAnimationCallback;
         mCustomAnimationInfo = customAnimationInfo;
         mLetterboxColor = letterboxColor;
+        mTouchableRegion = new Rect(touchableRegion);
     }
 
     private BackNavigationInfo(@NonNull Parcel in) {
@@ -146,6 +151,7 @@
         mAnimationCallback = in.readBoolean();
         mCustomAnimationInfo = in.readTypedObject(CustomAnimationInfo.CREATOR);
         mLetterboxColor = in.readInt();
+        mTouchableRegion = in.readTypedObject(Rect.CREATOR);
     }
 
     /** @hide */
@@ -158,6 +164,7 @@
         dest.writeBoolean(mAnimationCallback);
         dest.writeTypedObject(mCustomAnimationInfo, flags);
         dest.writeInt(mLetterboxColor);
+        dest.writeTypedObject(mTouchableRegion, flags);
     }
 
     /**
@@ -206,6 +213,16 @@
     public int getLetterboxColor() {
         return mLetterboxColor;
     }
+
+    /**
+     * @return The app window region where the client can handle touch event.
+     * @hide
+     */
+    @NonNull
+    public Rect getTouchableRegion() {
+        return mTouchableRegion;
+    }
+
     /**
      * Callback to be called when the back preview is finished in order to notify the server that
      * it can clean up the resources created for the animation.
@@ -402,6 +419,7 @@
         private boolean mAnimationCallback = false;
 
         private int mLetterboxColor = Color.TRANSPARENT;
+        private Rect mTouchableRegion;
 
         /**
          * @see BackNavigationInfo#getType()
@@ -478,6 +496,13 @@
         }
 
         /**
+         * @param rect Non-empty for frame of current focus window.
+         */
+        public Builder setTouchableRegion(Rect rect) {
+            mTouchableRegion = new Rect(rect);
+            return this;
+        }
+        /**
          * Builds and returns an instance of {@link BackNavigationInfo}
          */
         public BackNavigationInfo build() {
@@ -486,7 +511,8 @@
                     mPrepareRemoteAnimation,
                     mAnimationCallback,
                     mCustomAnimationInfo,
-                    mLetterboxColor);
+                    mLetterboxColor,
+                    mTouchableRegion);
         }
     }
 }
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index b7f6f36..4ca64e7 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -435,7 +435,16 @@
         }
 
         @Override
-        public void onBackProgressed(BackMotionEvent backEvent) { }
+        public void onBackProgressed(BackMotionEvent backEvent) {
+            // This is only called in some special cases such as when activity embedding is active
+            // or when the activity is letterboxed. Otherwise mProgressAnimator#onBackProgressed is
+            // called from WindowOnBackInvokedDispatcher#onMotionEvent
+            mHandler.post(() -> {
+                if (getBackAnimationCallback() != null) {
+                    mProgressAnimator.onBackProgressed(backEvent);
+                }
+            });
+        }
 
         @Override
         public void onBackCancelled() {
diff --git a/core/java/com/android/internal/util/function/pooled/OmniFunction.java b/core/java/com/android/internal/util/function/pooled/OmniFunction.java
old mode 100755
new mode 100644
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambda.java b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
old mode 100755
new mode 100644
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java b/core/java/com/android/internal/util/function/pooled/PooledLambdaImpl.java
old mode 100755
new mode 100644
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index f914bee..cb5af91 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -360,6 +360,10 @@
             return 0;
         }
         pointerCoordsToNative(env, pointerCoordsObj, xOffset, yOffset, &rawPointerCoords[i]);
+        if (rawPointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION) != 0.f) {
+            flags |= AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_ORIENTATION |
+                    AMOTION_EVENT_PRIVATE_FLAG_SUPPORTS_DIRECTIONAL_ORIENTATION;
+        }
         env->DeleteLocalRef(pointerCoordsObj);
     }
 
@@ -685,13 +689,15 @@
 
 static jint android_view_MotionEvent_nativeGetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    return event->getFlags();
+    // Prevent private flags from being used in Java.
+    return event->getFlags() & ~AMOTION_EVENT_PRIVATE_FLAG_MASK;
 }
 
 static void android_view_MotionEvent_nativeSetFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr,
                                                     jint flags) {
     MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
-    event->setFlags(flags);
+    // Prevent private flags from being used from Java.
+    event->setFlags(flags & ~AMOTION_EVENT_PRIVATE_FLAG_MASK);
 }
 
 static jint android_view_MotionEvent_nativeGetEdgeFlags(CRITICAL_JNI_PARAMS_COMMA jlong nativePtr) {
diff --git a/core/res/res/layout/subscription_item_layout.xml b/core/res/res/layout/subscription_item_layout.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-af/donottranslate-cldr.xml b/core/res/res/values-af/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-am/donottranslate-cldr.xml b/core/res/res/values-am/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-ar-rEG/donottranslate-cldr.xml b/core/res/res/values-ar-rEG/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-ar/donottranslate-cldr.xml b/core/res/res/values-ar/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-bg/donottranslate-cldr.xml b/core/res/res/values-bg/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-ca/donottranslate-cldr.xml b/core/res/res/values-ca/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-cs/donottranslate-cldr.xml b/core/res/res/values-cs/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-da/donottranslate-cldr.xml b/core/res/res/values-da/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-de/donottranslate-cldr.xml b/core/res/res/values-de/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-el/donottranslate-cldr.xml b/core/res/res/values-el/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-en-rAU/donottranslate-cldr.xml b/core/res/res/values-en-rAU/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-en-rCA/donottranslate-cldr.xml b/core/res/res/values-en-rCA/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-en-rGB/donottranslate-cldr.xml b/core/res/res/values-en-rGB/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-en-rIE/donottranslate-cldr.xml b/core/res/res/values-en-rIE/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-en-rIN/donottranslate-cldr.xml b/core/res/res/values-en-rIN/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-en-rNZ/donottranslate-cldr.xml b/core/res/res/values-en-rNZ/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-en-rUS/donottranslate-cldr.xml b/core/res/res/values-en-rUS/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-en-rZA/donottranslate-cldr.xml b/core/res/res/values-en-rZA/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rCO/donottranslate-cldr.xml b/core/res/res/values-es-rCO/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rCR/donottranslate-cldr.xml b/core/res/res/values-es-rCR/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rEC/donottranslate-cldr.xml b/core/res/res/values-es-rEC/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rGT/donottranslate-cldr.xml b/core/res/res/values-es-rGT/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rHN/donottranslate-cldr.xml b/core/res/res/values-es-rHN/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rMX/donottranslate-cldr.xml b/core/res/res/values-es-rMX/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rNI/donottranslate-cldr.xml b/core/res/res/values-es-rNI/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rPA/donottranslate-cldr.xml b/core/res/res/values-es-rPA/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rPE/donottranslate-cldr.xml b/core/res/res/values-es-rPE/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rSV/donottranslate-cldr.xml b/core/res/res/values-es-rSV/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es-rUS/donottranslate-cldr.xml b/core/res/res/values-es-rUS/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-es/donottranslate-cldr.xml b/core/res/res/values-es/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-fa/donottranslate-cldr.xml b/core/res/res/values-fa/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-fi-rFI/donottranslate-cldr.xml b/core/res/res/values-fi-rFI/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-fi/donottranslate-cldr.xml b/core/res/res/values-fi/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-fr/donottranslate-cldr.xml b/core/res/res/values-fr/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-hi-rIN/donottranslate-cldr.xml b/core/res/res/values-hi-rIN/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-hi/donottranslate-cldr.xml b/core/res/res/values-hi/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-hr-rHR/donottranslate-cldr.xml b/core/res/res/values-hr-rHR/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-hr/donottranslate-cldr.xml b/core/res/res/values-hr/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-hu-rHU/donottranslate-cldr.xml b/core/res/res/values-hu-rHU/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-hu/donottranslate-cldr.xml b/core/res/res/values-hu/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-in-rID/donottranslate-cldr.xml b/core/res/res/values-in-rID/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-in/donottranslate-cldr.xml b/core/res/res/values-in/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-it/donottranslate-cldr.xml b/core/res/res/values-it/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-iw/donottranslate-cldr.xml b/core/res/res/values-iw/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-ja/donottranslate-cldr.xml b/core/res/res/values-ja/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-ko/donottranslate-cldr.xml b/core/res/res/values-ko/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-lt-rLT/donottranslate-cldr.xml b/core/res/res/values-lt-rLT/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-lt/donottranslate-cldr.xml b/core/res/res/values-lt/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-lv-rLV/donottranslate-cldr.xml b/core/res/res/values-lv-rLV/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-lv/donottranslate-cldr.xml b/core/res/res/values-lv/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-mcc204-mnc04/config.xml b/core/res/res/values-mcc204-mnc04/config.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-mcc310-mnc004/config.xml b/core/res/res/values-mcc310-mnc004/config.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-mcc311-mnc480/config.xml b/core/res/res/values-mcc311-mnc480/config.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-nb/donottranslate-cldr.xml b/core/res/res/values-nb/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-nl/donottranslate-cldr.xml b/core/res/res/values-nl/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-pl/donottranslate-cldr.xml b/core/res/res/values-pl/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-pt-rPT/donottranslate-cldr.xml b/core/res/res/values-pt-rPT/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-pt/donottranslate-cldr.xml b/core/res/res/values-pt/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-ro-rRO/donottranslate-cldr.xml b/core/res/res/values-ro-rRO/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-ro/donottranslate-cldr.xml b/core/res/res/values-ro/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-ru/donottranslate-cldr.xml b/core/res/res/values-ru/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-sk-rSK/donottranslate-cldr.xml b/core/res/res/values-sk-rSK/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-sk/donottranslate-cldr.xml b/core/res/res/values-sk/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-sl-rSI/donottranslate-cldr.xml b/core/res/res/values-sl-rSI/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-sl/donottranslate-cldr.xml b/core/res/res/values-sl/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-sr-rRS/donottranslate-cldr.xml b/core/res/res/values-sr-rRS/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-sr/donottranslate-cldr.xml b/core/res/res/values-sr/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-sv/donottranslate-cldr.xml b/core/res/res/values-sv/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-sw/donottranslate-cldr.xml b/core/res/res/values-sw/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-th-rTH/donottranslate-cldr.xml b/core/res/res/values-th-rTH/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-th/donottranslate-cldr.xml b/core/res/res/values-th/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-tl/donottranslate-cldr.xml b/core/res/res/values-tl/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-tr/donottranslate-cldr.xml b/core/res/res/values-tr/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-uk-rUA/donottranslate-cldr.xml b/core/res/res/values-uk-rUA/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-uk/donottranslate-cldr.xml b/core/res/res/values-uk/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-vi-rVN/donottranslate-cldr.xml b/core/res/res/values-vi-rVN/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-vi/donottranslate-cldr.xml b/core/res/res/values-vi/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-zh-rCN/donottranslate-cldr.xml b/core/res/res/values-zh-rCN/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-zh-rTW/donottranslate-cldr.xml b/core/res/res/values-zh-rTW/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values-zu/donottranslate-cldr.xml b/core/res/res/values-zu/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4dfe000..f43351a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -7076,4 +7076,8 @@
 
     <!-- Whether the system uses auto-suspend mode. -->
     <bool name="config_useAutoSuspend">true</bool>
+
+    <!-- Whether to show GAIA education screen during account login of private space setup.
+         OEM/Partner can explicitly opt to disable the screen. -->
+    <bool name="config_enableGaiaEducationInPrivateSpace">true</bool>
 </resources>
diff --git a/core/res/res/values/donottranslate-cldr.xml b/core/res/res/values/donottranslate-cldr.xml
old mode 100755
new mode 100644
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cc74d02..639b746 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -521,6 +521,7 @@
   <java-symbol type="bool" name="config_preferKeepClearForFocus" />
   <java-symbol type="bool" name="config_hibernationDeletesOatArtifactsEnabled"/>
   <java-symbol type="integer" name="config_defaultAnalogClockSecondsHandFps"/>
+  <java-symbol type="bool" name="config_enableGaiaEducationInPrivateSpace"/>
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
 
diff --git a/core/sysprop/Android.bp b/core/sysprop/Android.bp
index 512a2eb..ed82765 100644
--- a/core/sysprop/Android.bp
+++ b/core/sysprop/Android.bp
@@ -43,3 +43,10 @@
     property_owner: "Platform",
     api_packages: ["android.sysprop"],
 }
+
+sysprop_library {
+    name: "com.android.sysprop.view",
+    srcs: ["ViewProperties.sysprop"],
+    property_owner: "Platform",
+    api_packages: ["android.sysprop"],
+}
diff --git a/core/sysprop/ViewProperties.sysprop b/core/sysprop/ViewProperties.sysprop
new file mode 100644
index 0000000..e801643
--- /dev/null
+++ b/core/sysprop/ViewProperties.sysprop
@@ -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.
+
+module: "android.sysprop.ViewProperties"
+owner: Platform
+
+# On low-end devices, the cost of calculating frame rate can
+# have noticeable overhead. These devices don't benefit from
+# reduced frame rate as much as they benefit from reduced
+# work. By setting this to false, the device won't do any
+# VRR frame rate calculation for Views.
+prop {
+    api_name: "vrr_enabled"
+    type: Boolean
+    prop_name: "ro.view.vrr.enabled"
+    scope: Internal
+    access: Readonly
+}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 35b984a..169300a 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -45,6 +45,7 @@
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.sysprop.ViewProperties;
 import android.util.DisplayMetrics;
 import android.widget.FrameLayout;
 import android.widget.ProgressBar;
@@ -101,6 +102,9 @@
     @RequiresFlagsEnabled({FLAG_VIEW_VELOCITY_API,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void frameRateChangesWhenContentMoves() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         waitForFrameRateCategoryToSettle();
         mActivityRule.runOnUiThread(() -> {
             mMovingView.offsetLeftAndRight(100);
@@ -127,6 +131,9 @@
     @Test
     @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
     public void frameBoostDisable() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mActivityRule.runOnUiThread(() -> {
             long now = SystemClock.uptimeMillis();
             MotionEvent down = MotionEvent.obtain(
@@ -155,6 +162,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void lowVelocity60() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mActivityRule.runOnUiThread(() -> {
             ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
             layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -175,6 +185,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void velocityWithChildMovement() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         FrameLayout frameLayout = new FrameLayout(mActivity);
         mActivityRule.runOnUiThread(() -> {
             ViewGroup.LayoutParams fullSize = new ViewGroup.LayoutParams(
@@ -201,6 +214,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void highVelocity120() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mActivityRule.runOnUiThread(() -> {
             ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
             layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
@@ -222,6 +238,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void noVelocityUsesCategorySmall() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         final CountDownLatch drawLatch1 = new CountDownLatch(1);
         mActivityRule.runOnUiThread(() -> {
             DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
@@ -259,6 +278,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void noVelocityUsesCategoryNarrowWidth() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         final CountDownLatch drawLatch1 = new CountDownLatch(1);
         mActivityRule.runOnUiThread(() -> {
             DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
@@ -295,6 +317,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void noVelocityUsesCategoryNarrowHeight() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         final CountDownLatch drawLatch1 = new CountDownLatch(1);
         mActivityRule.runOnUiThread(() -> {
             DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
@@ -331,6 +356,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void noVelocityUsesCategoryLargeWidth() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         final CountDownLatch drawLatch1 = new CountDownLatch(1);
         mActivityRule.runOnUiThread(() -> {
             DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
@@ -367,6 +395,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void noVelocityUsesCategoryLargeHeight() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         final CountDownLatch drawLatch1 = new CountDownLatch(1);
         mActivityRule.runOnUiThread(() -> {
             DisplayMetrics displayMetrics = mActivity.getResources().getDisplayMetrics();
@@ -403,6 +434,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void defaultNormal() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mActivityRule.runOnUiThread(() -> {
             View parent = (View) mMovingView.getParent();
             ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
@@ -427,6 +461,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VELOCITY_MAPPING_READ_ONLY
     })
     public void frameRateAndCategory() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
         waitForFrameRateCategoryToSettle();
         mActivityRule.runOnUiThread(() -> {
@@ -447,6 +484,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
     })
     public void willNotDrawUsesCategory() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mActivityRule.runOnUiThread(() -> {
             mMovingView.setWillNotDraw(true);
             mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
@@ -480,6 +520,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void intermittentDoubleInvalidate() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         View parent = (View) mMovingView.getParent();
         mActivityRule.runOnUiThread(() -> {
             parent.setWillNotDraw(false);
@@ -526,6 +569,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
     })
     public void sameFrameMotion() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
         waitForFrameRateCategoryToSettle();
 
@@ -549,6 +595,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
     })
     public void frameRateReset() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mMovingView.setRequestedFrameRate(120f);
         waitForFrameRateCategoryToSettle();
         mActivityRule.runOnUiThread(() -> mMovingView.setVisibility(View.INVISIBLE));
@@ -570,6 +619,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
     })
     public void frameRateResetWithInvalidations() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mMovingView.setRequestedFrameRate(120f);
         waitForFrameRateCategoryToSettle();
         mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL);
@@ -590,6 +642,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY
     })
     public void testQuickTouchBoost() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mActivityRule.runOnUiThread(() -> {
             mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW);
             ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams();
@@ -630,6 +685,9 @@
             com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4
     })
     public void idleDetected() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         waitForFrameRateCategoryToSettle();
         mActivityRule.runOnUiThread(() -> {
             mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH);
@@ -654,6 +712,9 @@
             com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4
     })
     public void vectorDrawableFrameRate() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         final ProgressBar[] progressBars = new ProgressBar[3];
         final ViewGroup[] parents = new ViewGroup[1];
         mActivityRule.runOnUiThread(() -> {
@@ -711,6 +772,9 @@
             com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4
     })
     public void renderNodeAnimatorFrameRateCanceled() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
         waitForFrameRateCategoryToSettle();
 
@@ -748,6 +812,9 @@
             com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4
     })
     public void renderNodeAnimatorFrameRateRemoved() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE);
         waitForFrameRateCategoryToSettle();
 
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 94e187a..5d52da1 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -72,6 +72,7 @@
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.Settings;
+import android.sysprop.ViewProperties;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.WindowInsets.Side;
@@ -503,6 +504,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_getDefaultValues() {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
                 sContext.getDisplayNoVerify());
         assertEquals(FRAME_RATE_CATEGORY_DEFAULT,
@@ -521,6 +525,9 @@
             FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_visibility_bySize() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         attachViewToWindow(mView);
         mViewRootImpl = mView.getViewRootImpl();
@@ -558,6 +565,9 @@
             FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_smallSize_bySize() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -590,6 +600,9 @@
             FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_normalSize_bySize() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -627,6 +640,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_visibility_defaultHigh()
             throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -688,6 +704,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_smallSize_defaultHigh()
             throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -723,6 +742,9 @@
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_normalSize_defaultHigh()
             throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -758,6 +780,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         attachViewToWindow(mView);
         mViewRootImpl = mView.getViewRootImpl();
@@ -804,6 +829,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRate_aggregate() {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         attachViewToWindow(mView);
         mViewRootImpl = mView.getViewRootImpl();
@@ -876,6 +904,9 @@
             FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRate_category() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         attachViewToWindow(mView);
         sInstrumentation.waitForIdleSync();
@@ -930,6 +961,9 @@
             FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateCategory_velocityToHigh() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -973,6 +1007,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_insetsAnimation() {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
         wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
@@ -1010,6 +1047,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_frameRateBoostOnTouch() {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         attachViewToWindow(mView);
         sInstrumentation.waitForIdleSync();
@@ -1043,6 +1083,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateTimeOut() throws InterruptedException {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         final long delay = 200L;
 
         mView = new View(sContext);
@@ -1082,6 +1125,9 @@
             FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_voteFrameRateOnly() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         float frameRate = 20;
         attachViewToWindow(mView);
@@ -1133,6 +1179,9 @@
             FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_infrequentLayer_defaultHigh() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         final long delay = 200L;
 
         mView = new View(sContext);
@@ -1211,6 +1260,9 @@
     @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_isFrameRatePowerSavingsBalanced() {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         attachViewToWindow(mView);
         sInstrumentation.waitForIdleSync();
@@ -1245,6 +1297,9 @@
             FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY,
             FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY})
     public void votePreferredFrameRate_applyTextureViewHeuristic() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         final long delay = 30L;
 
         mView = new TextureView(sContext);
@@ -1289,6 +1344,9 @@
     @Test
     @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY)
     public void votePreferredFrameRate_velocityVotedAfterOnDraw() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
         mView = new View(sContext);
         double delta = 0.1;
         float pixelsPerSecond = 1000_000;
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiDeviceInfoTest.java
old mode 100755
new mode 100644
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
old mode 100755
new mode 100644
diff --git a/data/sounds/alarms/material/ogg/Awaken_OG7_1ch_48k.ogg b/data/sounds/alarms/material/ogg/Awaken_OG7_1ch_48k.ogg
old mode 100755
new mode 100644
Binary files differ
diff --git a/data/sounds/alarms/material/ogg/Bounce_OG7_1ch_48k.ogg b/data/sounds/alarms/material/ogg/Bounce_OG7_1ch_48k.ogg
old mode 100755
new mode 100644
Binary files differ
diff --git a/data/sounds/alarms/material/ogg/Drip_OG7_1ch_48k.ogg b/data/sounds/alarms/material/ogg/Drip_OG7_1ch_48k.ogg
old mode 100755
new mode 100644
Binary files differ
diff --git a/data/sounds/alarms/material/ogg/Gallop_OG7_1ch_48k.ogg b/data/sounds/alarms/material/ogg/Gallop_OG7_1ch_48k.ogg
old mode 100755
new mode 100644
Binary files differ
diff --git a/data/sounds/alarms/material/ogg/Nudge_OG7_1ch_48k.ogg b/data/sounds/alarms/material/ogg/Nudge_OG7_1ch_48k.ogg
old mode 100755
new mode 100644
Binary files differ
diff --git a/data/sounds/alarms/material/ogg/Orbit_OG7_1ch_48k.ogg b/data/sounds/alarms/material/ogg/Orbit_OG7_1ch_48k.ogg
old mode 100755
new mode 100644
Binary files differ
diff --git a/data/sounds/alarms/material/ogg/Rise_OG7_1ch_48k.ogg b/data/sounds/alarms/material/ogg/Rise_OG7_1ch_48k.ogg
old mode 100755
new mode 100644
Binary files differ
diff --git a/data/sounds/alarms/material/ogg/Sway_OG7_1ch_48k.ogg b/data/sounds/alarms/material/ogg/Sway_OG7_1ch_48k.ogg
old mode 100755
new mode 100644
Binary files differ
diff --git a/data/sounds/alarms/material/ogg/Wag_OG7_1ch_48k.ogg b/data/sounds/alarms/material/ogg/Wag_OG7_1ch_48k.ogg
old mode 100755
new mode 100644
Binary files differ
diff --git a/docs/downloads/training/LocationUpdates.zip b/docs/downloads/training/LocationUpdates.zip
old mode 100755
new mode 100644
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 0119289..0fd21f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
+import android.graphics.Rect;
 import android.hardware.input.InputManager;
 import android.net.Uri;
 import android.os.Bundle;
@@ -47,6 +48,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
+import android.view.WindowManager;
 import android.window.BackAnimationAdapter;
 import android.window.BackEvent;
 import android.window.BackMotionEvent;
@@ -119,6 +121,9 @@
     private final ShellCommandHandler mShellCommandHandler;
     private final ShellExecutor mShellExecutor;
     private final Handler mBgHandler;
+    private final WindowManager mWindowManager;
+    @VisibleForTesting
+    final Rect mTouchableArea = new Rect();
 
     /**
      * Tracks the current user back gesture.
@@ -222,6 +227,8 @@
         mShellBackAnimationRegistry = shellBackAnimationRegistry;
         mLatencyTracker = LatencyTracker.getInstance(mContext);
         mShellCommandHandler = shellCommandHandler;
+        mWindowManager = context.getSystemService(WindowManager.class);
+        updateTouchableArea();
     }
 
     private void onInit() {
@@ -283,6 +290,11 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         mShellBackAnimationRegistry.onConfigurationChanged(newConfig);
+        updateTouchableArea();
+    }
+
+    private void updateTouchableArea() {
+        mTouchableArea.set(mWindowManager.getCurrentWindowMetrics().getBounds());
     }
 
     @Override
@@ -416,11 +428,18 @@
         if (!shouldDispatchToAnimator && mActiveCallback != null) {
             mCurrentTracker.updateStartLocation();
             tryDispatchOnBackStarted(mActiveCallback, mCurrentTracker.createStartEvent(null));
+            if (mBackNavigationInfo != null && !isAppProgressGenerationAllowed()) {
+                tryPilferPointers();
+            }
         } else if (shouldDispatchToAnimator) {
             tryPilferPointers();
         }
     }
 
+    private boolean isAppProgressGenerationAllowed() {
+        return mBackNavigationInfo.getTouchableRegion().equals(mTouchableArea);
+    }
+
     /**
      * Called when a new motion event needs to be transferred to this
      * {@link BackAnimationController}
@@ -536,6 +555,9 @@
             // App is handling back animation. Cancel system animation latency tracking.
             cancelLatencyTracking();
             tryDispatchOnBackStarted(mActiveCallback, touchTracker.createStartEvent(null));
+            if (!isAppProgressGenerationAllowed()) {
+                tryPilferPointers();
+            }
         }
     }
 
@@ -642,7 +664,8 @@
 
     private void dispatchOnBackProgressed(IOnBackInvokedCallback callback,
             BackMotionEvent backEvent) {
-        if (callback == null || !shouldDispatchToAnimator()) {
+        if (callback == null || (!shouldDispatchToAnimator() && mBackNavigationInfo != null
+                && isAppProgressGenerationAllowed())) {
             return;
         }
         try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
index b5f25433f..e779879 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/IPip.aidl
@@ -53,9 +53,11 @@
      * @param destinationBounds the destination bounds the PiP window lands into
      * @param overlay an optional overlay to fade out after entering PiP
      * @param appBounds the bounds used to set the buffer size of the optional content overlay
+     * @param sourceRectHint the bounds to show in the transition to PiP
      */
     oneway void stopSwipePipToHome(int taskId, in ComponentName componentName,
-            in Rect destinationBounds, in SurfaceControl overlay, in Rect appBounds) = 2;
+            in Rect destinationBounds, in SurfaceControl overlay, in Rect appBounds,
+            in Rect sourceRectHint) = 2;
 
     /**
      * Notifies the swiping Activity to PiP onto home transition is aborted
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 04dd0ef..8c9206c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -373,6 +373,10 @@
     @NonNull
     final Rect mAppBounds = new Rect();
 
+    /** The source rect hint from stopSwipePipToHome(). */
+    @Nullable
+    private Rect mSwipeSourceRectHint;
+
     public PipTaskOrganizer(Context context,
             @NonNull SyncTransactionQueue syncTransactionQueue,
             @NonNull PipTransitionState pipTransitionState,
@@ -504,7 +508,7 @@
      * Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
      */
     public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
-            SurfaceControl overlay, Rect appBounds) {
+            SurfaceControl overlay, Rect appBounds, Rect sourceRectHint) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "stopSwipePipToHome: %s, stat=%s", componentName, mPipTransitionState);
         // do nothing if there is no startSwipePipToHome being called before
@@ -513,6 +517,7 @@
         }
         mPipBoundsState.setBounds(destinationBounds);
         setContentOverlay(overlay, appBounds);
+        mSwipeSourceRectHint = sourceRectHint;
         if (ENABLE_SHELL_TRANSITIONS && overlay != null) {
             // With Shell transition, the overlay was attached to the remote transition leash, which
             // will be removed when the current transition is finished, so we need to reparent it
@@ -529,6 +534,20 @@
         }
     }
 
+    /**
+     * Returns non-null Rect if the pip is entering from swipe-to-home with a specified source hint.
+     * This also consumes the rect hint.
+     */
+    @Nullable
+    Rect takeSwipeSourceRectHint() {
+        final Rect sourceRectHint = mSwipeSourceRectHint;
+        if (sourceRectHint == null || sourceRectHint.isEmpty()) {
+            return null;
+        }
+        mSwipeSourceRectHint = null;
+        return mPipTransitionState.getInSwipePipToHomeTransition() ? sourceRectHint : null;
+    }
+
     private void mayRemoveContentOverlay(SurfaceControl overlay) {
         final WeakReference<SurfaceControl> overlayRef = new WeakReference<>(overlay);
         final long timeoutDuration = (mEnterAnimationDuration
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 5ee6f6b..dbf18fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -1004,8 +1004,11 @@
         final Rect currentBounds = pipChange.getStartAbsBounds();
 
         int rotationDelta = deltaRotation(startRotation, endRotation);
-        Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
-                taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
+        Rect sourceHintRect = mPipOrganizer.takeSwipeSourceRectHint();
+        if (sourceHintRect == null) {
+            sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
+                    taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
+        }
         if (rotationDelta != Surface.ROTATION_0
                 && endRotation != mPipDisplayLayoutState.getRotation()) {
             // Computes the destination bounds in new rotation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index de105c0..8c4bf76 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -1001,9 +1001,9 @@
     }
 
     private void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
-            SurfaceControl overlay, Rect appBounds) {
+            SurfaceControl overlay, Rect appBounds, Rect sourceRectHint) {
         mPipTaskOrganizer.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
-                appBounds);
+                appBounds, sourceRectHint);
     }
 
     private void abortSwipePipToHome(int taskId, ComponentName componentName) {
@@ -1291,13 +1291,15 @@
 
         @Override
         public void stopSwipePipToHome(int taskId, ComponentName componentName,
-                Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+                Rect destinationBounds, SurfaceControl overlay, Rect appBounds,
+                Rect sourceRectHint) {
             if (overlay != null) {
                 overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
             }
             executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
                     (controller) -> controller.stopSwipePipToHome(
-                            taskId, componentName, destinationBounds, overlay, appBounds));
+                            taskId, componentName, destinationBounds, overlay, appBounds,
+                            sourceRectHint));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 1846720..fc0d36d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -285,7 +285,8 @@
     }
 
     private void onSwipePipToHomeAnimationStart(int taskId, ComponentName componentName,
-            Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+            Rect destinationBounds, SurfaceControl overlay, Rect appBounds,
+            Rect sourceRectHint) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                 "onSwipePipToHomeAnimationStart: %s", componentName);
         Bundle extra = new Bundle();
@@ -409,13 +410,15 @@
 
         @Override
         public void stopSwipePipToHome(int taskId, ComponentName componentName,
-                Rect destinationBounds, SurfaceControl overlay, Rect appBounds) {
+                Rect destinationBounds, SurfaceControl overlay, Rect appBounds,
+                Rect sourceRectHint) {
             if (overlay != null) {
                 overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome");
             }
             executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome",
                     (controller) -> controller.onSwipePipToHomeAnimationStart(
-                            taskId, componentName, destinationBounds, overlay, appBounds));
+                            taskId, componentName, destinationBounds, overlay, appBounds,
+                            sourceRectHint));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index a5fc347..b6a18e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -1524,6 +1524,7 @@
                 prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
                 mSplitTransitions.startDismissTransition(wct, this,
                         mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
+                setSplitsVisible(false);
             } else {
                 exitSplitScreen(
                         mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
@@ -2761,6 +2762,14 @@
             // cases above and it is not already visible
             return null;
         } else {
+            if (triggerTask.parentTaskId == mMainStage.mRootTaskInfo.taskId
+                    || triggerTask.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
+                ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d "
+                                + "restoring to split", request.getDebugId());
+                out = new WindowContainerTransaction();
+                mSplitTransitions.setEnterTransition(transition, request.getRemoteTransition(),
+                        TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, false /* resizeAnim */);
+            }
             if (isOpening && getStageOfTask(triggerTask) != null) {
                 ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "handleRequest: transition=%d enter split",
                         request.getDebugId());
@@ -3107,7 +3116,7 @@
                 // Includes TRANSIT_CHANGE to cover reparenting top-most task to split.
                 mainChild = change;
             } else if (sideChild == null && stageType == STAGE_TYPE_SIDE
-                    && isOpeningType(change.getMode())) {
+                    && (isOpeningType(change.getMode()) || change.getMode() == TRANSIT_CHANGE)) {
                 sideChild = change;
             } else if (stageType != STAGE_TYPE_UNDEFINED && change.getMode() == TRANSIT_TO_BACK) {
                 // Collect all to back task's and evict them when transition finished.
@@ -3118,7 +3127,8 @@
         SplitScreenTransitions.EnterSession pendingEnter = mSplitTransitions.mPendingEnter;
         if (pendingEnter.mExtraTransitType
                 == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
-            // Open to side should only be used when split already active and foregorund.
+            // Open to side should only be used when split already active and foregorund or when
+            // app is restoring to split from fullscreen.
             if (mainChild == null && sideChild == null) {
                 Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
                         "Launched a task in split, but didn't receive any task in transition."));
@@ -3205,6 +3215,22 @@
             mPausingTasks.clear();
         });
 
+        if (info.getType() == TRANSIT_CHANGE && !isSplitActive()
+                && pendingEnter.mExtraTransitType == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
+            if (finalMainChild != null && finalSideChild == null) {
+                requestEnterSplitSelect(finalMainChild.getTaskInfo(),
+                        new WindowContainerTransaction(),
+                        getMainStagePosition(), finalMainChild.getStartAbsBounds());
+            } else if (finalSideChild != null && finalMainChild == null) {
+                requestEnterSplitSelect(finalSideChild.getTaskInfo(),
+                        new WindowContainerTransaction(),
+                        getSideStagePosition(), finalSideChild.getStartAbsBounds());
+            } else {
+                throw new IllegalStateException(
+                        "Attempting to restore to split but reparenting change not found");
+            }
+        }
+
         finishEnterSplitScreen(finishT);
         addDividerBarToTransition(info, true /* show */);
         return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index a4ade1b..01a479f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -233,32 +233,7 @@
         }
 
         if (oldRootView != mResult.mRootView) {
-            if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) {
-                mWindowDecorViewHolder = new AppHandleViewHolder(
-                        mResult.mRootView,
-                        mOnCaptionTouchListener,
-                        mOnCaptionButtonClickListener
-                );
-            } else if (mRelayoutParams.mLayoutResId
-                    == R.layout.desktop_mode_app_header) {
-                loadAppInfoIfNeeded();
-                mWindowDecorViewHolder = new AppHeaderViewHolder(
-                        mResult.mRootView,
-                        mOnCaptionTouchListener,
-                        mOnCaptionButtonClickListener,
-                        mOnCaptionLongClickListener,
-                        mOnCaptionGenericMotionListener,
-                        mAppName,
-                        mAppIconBitmap,
-                        () -> {
-                            if (!isMaximizeMenuActive()) {
-                                createMaximizeMenu();
-                            }
-                            return Unit.INSTANCE;
-                        });
-            } else {
-                throw new IllegalArgumentException("Unexpected layout resource id");
-            }
+            mWindowDecorViewHolder = createViewHolder();
         }
         Trace.beginSection("DesktopModeWindowDecoration#relayout-binding");
         mWindowDecorViewHolder.bindData(mTaskInfo);
@@ -269,16 +244,18 @@
             closeMaximizeMenu();
         }
 
-        final boolean isFreeform =
-                taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
-        final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
-        if (!isDragResizeable) {
+        updateDragResizeListener(oldDecorationSurface);
+        updateMaximizeMenu(startT);
+        Trace.endSection(); // DesktopModeWindowDecoration#relayout
+    }
+
+    private void updateDragResizeListener(SurfaceControl oldDecorationSurface) {
+        if (!isDragResizable(mTaskInfo)) {
             if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
                 // We still want to track caption bar's exclusion region on a non-resizeable task.
                 updateExclusionRegion();
             }
             closeDragResizeListener();
-            Trace.endSection(); // DesktopModeWindowDecoration#relayout
             return;
         }
 
@@ -312,15 +289,51 @@
                 || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
             updateExclusionRegion();
         }
+    }
 
-        if (isMaximizeMenuActive()) {
-            if (!mTaskInfo.isVisible()) {
-                closeMaximizeMenu();
-            } else {
-                mMaximizeMenu.positionMenu(calculateMaximizeMenuPosition(), startT);
-            }
+    private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo) {
+        final boolean isFreeform =
+                taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+        return isFreeform && taskInfo.isResizeable;
+    }
+
+    private void updateMaximizeMenu(SurfaceControl.Transaction startT) {
+        if (!isDragResizable(mTaskInfo) || !isMaximizeMenuActive()) {
+            return;
         }
-        Trace.endSection(); // DesktopModeWindowDecoration#relayout
+        if (!mTaskInfo.isVisible()) {
+            closeMaximizeMenu();
+        } else {
+            mMaximizeMenu.positionMenu(calculateMaximizeMenuPosition(), startT);
+        }
+    }
+
+    private WindowDecorationViewHolder createViewHolder() {
+        if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) {
+            return new AppHandleViewHolder(
+                    mResult.mRootView,
+                    mOnCaptionTouchListener,
+                    mOnCaptionButtonClickListener
+            );
+        } else if (mRelayoutParams.mLayoutResId
+                == R.layout.desktop_mode_app_header) {
+            loadAppInfoIfNeeded();
+            return new AppHeaderViewHolder(
+                    mResult.mRootView,
+                    mOnCaptionTouchListener,
+                    mOnCaptionButtonClickListener,
+                    mOnCaptionLongClickListener,
+                    mOnCaptionGenericMotionListener,
+                    mAppName,
+                    mAppIconBitmap,
+                    () -> {
+                        if (!isMaximizeMenuActive()) {
+                            createMaximizeMenu();
+                        }
+                        return Unit.INSTANCE;
+                    });
+        }
+        throw new IllegalArgumentException("Unexpected layout resource id");
     }
 
     @VisibleForTesting
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index a08f97c..b9532dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -213,13 +213,39 @@
             return;
         }
 
+        inflateIfNeeded(params, wct, rootView, oldLayoutResId, outResult);
+        if (outResult.mRootView == null) {
+            // Didn't manage to create a root view, early out.
+            return;
+        }
+        rootView = null; // Clear it just in case we use it accidentally
+
+        updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
+
+        final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
+        outResult.mWidth = taskBounds.width();
+        outResult.mHeight = taskBounds.height();
+        outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
+        final Resources resources = mDecorWindowContext.getResources();
+        outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
+        outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
+                ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
+        outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
+
+        updateDecorationContainerSurface(startT, outResult);
+        updateCaptionContainerSurface(startT, outResult);
+        updateCaptionInsets(params, wct, outResult, taskBounds);
+        updateTaskSurface(params, startT, finishT, outResult);
+        updateViewHost(params, startT, outResult);
+    }
+
+    private void inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct,
+            T rootView, int oldLayoutResId, RelayoutResult<T> outResult) {
         if (rootView == null && params.mLayoutResId == 0) {
             throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
         }
 
         outResult.mRootView = rootView;
-        rootView = null; // Clear it just in case we use it accidentally
-
         final int oldDensityDpi = mWindowDecorConfig != null
                 ? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED;
         final int oldNightMode =  mWindowDecorConfig != null
@@ -253,25 +279,17 @@
             outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
                     .inflate(params.mLayoutResId, null);
         }
+    }
 
-        updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
-
-        final Resources resources = mDecorWindowContext.getResources();
-        final Configuration taskConfig = mTaskInfo.getConfiguration();
-        final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
-        final boolean isFullscreen = taskConfig.windowConfiguration.getWindowingMode()
-                == WINDOWING_MODE_FULLSCREEN;
-        outResult.mWidth = taskBounds.width();
-        outResult.mHeight = taskBounds.height();
-
-        // DecorationContainerSurface
+    private void updateDecorationContainerSurface(
+            SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
         if (mDecorationContainerSurface == null) {
             final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
             mDecorationContainerSurface = builder
                     .setName("Decor container of Task=" + mTaskInfo.taskId)
                     .setContainerLayer()
                     .setParent(mTaskSurface)
-                    .setCallsite("WindowDecoration.relayout_1")
+                    .setCallsite("WindowDecoration.updateDecorationContainerSurface")
                     .build();
 
             startT.setTrustedOverlay(mDecorationContainerSurface, true)
@@ -281,101 +299,101 @@
 
         startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
                 .show(mDecorationContainerSurface);
+    }
 
-        // CaptionContainerSurface, CaptionWindowManager
+    private void updateCaptionContainerSurface(
+            SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
         if (mCaptionContainerSurface == null) {
             final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
             mCaptionContainerSurface = builder
                     .setName("Caption container of Task=" + mTaskInfo.taskId)
                     .setContainerLayer()
                     .setParent(mDecorationContainerSurface)
-                    .setCallsite("WindowDecoration.relayout_2")
+                    .setCallsite("WindowDecoration.updateCaptionContainerSurface")
                     .build();
         }
 
-        outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
-        outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
-                ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
-        outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
-
         startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
                         outResult.mCaptionHeight)
                 .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
                 .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                 .show(mCaptionContainerSurface);
+    }
 
-        outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
-
-        // Caption insets
-        if (mIsCaptionVisible) {
-            // Caption inset is the full width of the task with the |captionHeight| and
-            // positioned at the top of the task bounds, also in absolute coordinates.
-            // So just reuse the task bounds and adjust the bottom coordinate.
-            final Rect captionInsetsRect = new Rect(taskBounds);
-            captionInsetsRect.bottom = captionInsetsRect.top + outResult.mCaptionHeight;
-
-            // Caption bounding rectangles: these are optional, and are used to present finer
-            // insets than traditional |Insets| to apps about where their content is occluded.
-            // These are also in absolute coordinates.
-            final Rect[] boundingRects;
-            final int numOfElements = params.mOccludingCaptionElements.size();
-            if (numOfElements == 0) {
-                boundingRects = null;
-            } else {
-                // The customizable region can at most be equal to the caption bar.
-                if (params.hasInputFeatureSpy()) {
-                    outResult.mCustomizableCaptionRegion.set(captionInsetsRect);
-                }
-                boundingRects = new Rect[numOfElements];
-                for (int i = 0; i < numOfElements; i++) {
-                    final OccludingCaptionElement element =
-                            params.mOccludingCaptionElements.get(i);
-                    final int elementWidthPx =
-                            resources.getDimensionPixelSize(element.mWidthResId);
-                    boundingRects[i] =
-                            calculateBoundingRect(element, elementWidthPx, captionInsetsRect);
-                    // Subtract the regions used by the caption elements, the rest is
-                    // customizable.
-                    if (params.hasInputFeatureSpy()) {
-                        outResult.mCustomizableCaptionRegion.op(boundingRects[i],
-                                Region.Op.DIFFERENCE);
-                    }
-                }
-            }
-
-            final WindowDecorationInsets newInsets = new WindowDecorationInsets(
-                    mTaskInfo.token, mOwner, captionInsetsRect, boundingRects);
-            if (!newInsets.equals(mWindowDecorationInsets)) {
-                // Add or update this caption as an insets source.
-                mWindowDecorationInsets = newInsets;
-                mWindowDecorationInsets.addOrUpdate(wct);
-            }
-        } else {
+    private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct,
+            RelayoutResult<T> outResult, Rect taskBounds) {
+        if (!mIsCaptionVisible) {
             if (mWindowDecorationInsets != null) {
                 mWindowDecorationInsets.remove(wct);
                 mWindowDecorationInsets = null;
             }
+            return;
         }
+        // Caption inset is the full width of the task with the |captionHeight| and
+        // positioned at the top of the task bounds, also in absolute coordinates.
+        // So just reuse the task bounds and adjust the bottom coordinate.
+        final Rect captionInsetsRect = new Rect(taskBounds);
+        captionInsetsRect.bottom = captionInsetsRect.top + outResult.mCaptionHeight;
 
-        // Task surface itself
-        float shadowRadius;
-        final Point taskPosition = mTaskInfo.positionInParent;
-        if (isFullscreen) {
-            // Shadow is not needed for fullscreen tasks
-            shadowRadius = 0;
+        // Caption bounding rectangles: these are optional, and are used to present finer
+        // insets than traditional |Insets| to apps about where their content is occluded.
+        // These are also in absolute coordinates.
+        final Rect[] boundingRects;
+        final int numOfElements = params.mOccludingCaptionElements.size();
+        if (numOfElements == 0) {
+            boundingRects = null;
         } else {
-            shadowRadius = loadDimension(resources, params.mShadowRadiusId);
+            // The customizable region can at most be equal to the caption bar.
+            if (params.hasInputFeatureSpy()) {
+                outResult.mCustomizableCaptionRegion.set(captionInsetsRect);
+            }
+            final Resources resources = mDecorWindowContext.getResources();
+            boundingRects = new Rect[numOfElements];
+            for (int i = 0; i < numOfElements; i++) {
+                final OccludingCaptionElement element =
+                        params.mOccludingCaptionElements.get(i);
+                final int elementWidthPx =
+                        resources.getDimensionPixelSize(element.mWidthResId);
+                boundingRects[i] =
+                        calculateBoundingRect(element, elementWidthPx, captionInsetsRect);
+                // Subtract the regions used by the caption elements, the rest is
+                // customizable.
+                if (params.hasInputFeatureSpy()) {
+                    outResult.mCustomizableCaptionRegion.op(boundingRects[i],
+                            Region.Op.DIFFERENCE);
+                }
+            }
         }
 
+        final WindowDecorationInsets newInsets = new WindowDecorationInsets(
+                mTaskInfo.token, mOwner, captionInsetsRect, boundingRects);
+        if (!newInsets.equals(mWindowDecorationInsets)) {
+            // Add or update this caption as an insets source.
+            mWindowDecorationInsets = newInsets;
+            mWindowDecorationInsets.addOrUpdate(wct);
+        }
+    }
+
+    private void updateTaskSurface(RelayoutParams params, SurfaceControl.Transaction startT,
+            SurfaceControl.Transaction finishT, RelayoutResult<T> outResult) {
         if (params.mSetTaskPositionAndCrop) {
+            final Point taskPosition = mTaskInfo.positionInParent;
             startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
             finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
                     .setPosition(mTaskSurface, taskPosition.x, taskPosition.y);
         }
 
-        startT.setShadowRadius(mTaskSurface, shadowRadius)
-                .show(mTaskSurface);
+        float shadowRadius;
+        if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+            // Shadow is not needed for fullscreen tasks
+            shadowRadius = 0;
+        } else {
+            shadowRadius =
+                    loadDimension(mDecorWindowContext.getResources(), params.mShadowRadiusId);
+        }
+        startT.setShadowRadius(mTaskSurface, shadowRadius).show(mTaskSurface);
         finishT.setShadowRadius(mTaskSurface, shadowRadius);
+
         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             if (!DesktopModeStatus.isVeiledResizeEnabled()) {
                 // When fluid resize is enabled, add a background to freeform tasks
@@ -390,7 +408,10 @@
         } else if (!DesktopModeStatus.isVeiledResizeEnabled()) {
             startT.unsetColor(mTaskSurface);
         }
+    }
 
+    private void updateViewHost(RelayoutParams params, SurfaceControl.Transaction onDrawTransaction,
+            RelayoutResult<T> outResult) {
         Trace.beginSection("CaptionViewHostLayout");
         if (mCaptionWindowManager == null) {
             // Put caption under a container surface because ViewRootImpl sets the destination frame
@@ -399,9 +420,7 @@
                     mTaskInfo.getConfiguration(), mCaptionContainerSurface,
                     null /* hostInputToken */);
         }
-
-        // Caption view
-        mCaptionWindowManager.setConfiguration(taskConfig);
+        mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
         final WindowManager.LayoutParams lp =
                 new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
                         TYPE_APPLICATION,
@@ -414,14 +433,14 @@
             mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
                     mCaptionWindowManager);
             if (params.mApplyStartTransactionOnDraw) {
-                mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
+                mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
             }
             mViewHost.setView(outResult.mRootView, lp);
             Trace.endSection();
         } else {
             Trace.beginSection("CaptionViewHostLayout-relayout");
             if (params.mApplyStartTransactionOnDraw) {
-                mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
+                mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
             }
             mViewHost.relayout(lp);
             Trace.endSection();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index f6f3aa4..1903586 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -123,6 +123,7 @@
     private DefaultCrossActivityBackAnimation mDefaultCrossActivityBackAnimation;
     private CrossTaskBackAnimation mCrossTaskBackAnimation;
     private ShellBackAnimationRegistry mShellBackAnimationRegistry;
+    private Rect mTouchableRegion;
 
     @Before
     public void setUp() throws Exception {
@@ -158,6 +159,8 @@
                         mShellCommandHandler);
         mShellInit.init();
         mShellExecutor.flushAll();
+        mTouchableRegion = new Rect(0, 0, 100, 100);
+        mController.mTouchableArea.set(mTouchableRegion);
     }
 
     private void createNavigationInfo(int backType,
@@ -169,7 +172,8 @@
                         .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
                         .setOnBackInvokedCallback(mAppCallback)
                         .setPrepareRemoteAnimation(enableAnimation)
-                        .setAnimationCallback(isAnimationCallback);
+                        .setAnimationCallback(isAnimationCallback)
+                        .setTouchableRegion(mTouchableRegion);
 
         createNavigationInfo(builder);
     }
@@ -234,7 +238,8 @@
                     .setType(type)
                     .setOnBackInvokedCallback(mAppCallback)
                     .setPrepareRemoteAnimation(true)
-                    .setOnBackNavigationDone(new RemoteCallback(result)));
+                    .setOnBackNavigationDone(new RemoteCallback(result))
+                    .setTouchableRegion(mTouchableRegion));
             triggerBackGesture();
             simulateRemoteAnimationStart();
             mShellExecutor.flushAll();
@@ -512,7 +517,8 @@
                     .setType(type)
                     .setOnBackInvokedCallback(mAppCallback)
                     .setPrepareRemoteAnimation(true)
-                    .setOnBackNavigationDone(new RemoteCallback(result)));
+                    .setOnBackNavigationDone(new RemoteCallback(result))
+                    .setTouchableRegion(mTouchableRegion));
             triggerBackGesture();
             simulateRemoteAnimationStart();
             mShellExecutor.flushAll();
@@ -543,7 +549,8 @@
         createNavigationInfo(new BackNavigationInfo.Builder()
                 .setType(type)
                 .setOnBackInvokedCallback(mAppCallback)
-                .setOnBackNavigationDone(new RemoteCallback(result)));
+                .setOnBackNavigationDone(new RemoteCallback(result))
+                .setTouchableRegion(mTouchableRegion));
         triggerBackGesture();
         mShellExecutor.flushAll();
         releaseBackGesture();
@@ -570,7 +577,8 @@
         createNavigationInfo(new BackNavigationInfo.Builder()
                 .setType(type)
                 .setOnBackInvokedCallback(mAppCallback)
-                .setOnBackNavigationDone(new RemoteCallback(result)));
+                .setOnBackNavigationDone(new RemoteCallback(result))
+                .setTouchableRegion(mTouchableRegion));
         doMotionEvent(MotionEvent.ACTION_CANCEL, 0);
         mShellExecutor.flushAll();
 
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
old mode 100755
new mode 100644
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
old mode 100755
new mode 100644
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
old mode 100755
new mode 100644
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
old mode 100755
new mode 100644
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
old mode 100755
new mode 100644
diff --git a/native/android/input.cpp b/native/android/input.cpp
index 0a22314..9a8cda3 100644
--- a/native/android/input.cpp
+++ b/native/android/input.cpp
@@ -100,7 +100,8 @@
 }
 
 int32_t AMotionEvent_getFlags(const AInputEvent* motion_event) {
-    return static_cast<const MotionEvent*>(motion_event)->getFlags();
+    return static_cast<const MotionEvent*>(motion_event)->getFlags() &
+            ~AMOTION_EVENT_PRIVATE_FLAG_MASK;
 }
 
 int32_t AMotionEvent_getMetaState(const AInputEvent* motion_event) {
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 8ea4632..746c280 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -111,6 +111,7 @@
             "allocator_may_return_null = 1",
         ],
     },
+    dictionary: "fuzz/imagedecoder_fuzzer.dict",
     host_supported: true,
 }
 
diff --git a/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
index 838bf3f..6743997 100644
--- a/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
+++ b/native/graphics/jni/fuzz/fuzz_imagedecoder.cpp
@@ -15,32 +15,15 @@
  */
 
 #include <android/imagedecoder.h>
-
 #include <binder/IPCThreadState.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <cstdlib>
-#include <memory>
+#include <fuzzer/FuzzedDataProvider.h>
 
 #ifdef PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR
 #include <fuzz/png_mutator.h>
 #endif
 
-struct DecoderDeleter {
-    void operator()(AImageDecoder* decoder) const { AImageDecoder_delete(decoder); }
-};
-
-using DecoderPointer = std::unique_ptr<AImageDecoder, DecoderDeleter>;
-
-static DecoderPointer makeDecoder(const uint8_t* data, size_t size) {
-    AImageDecoder* decoder = nullptr;
-    int result = AImageDecoder_createFromBuffer(data, size, &decoder);
-    if (result != ANDROID_IMAGE_DECODER_SUCCESS) {
-        // This was not a valid image.
-        return nullptr;
-    }
-    return DecoderPointer(decoder);
-}
+constexpr int32_t kMaxDimension = 5000;
+constexpr int32_t kMinDimension = 0;
 
 struct PixelFreer {
     void operator()(void* pixels) const { std::free(pixels); }
@@ -48,41 +31,113 @@
 
 using PixelPointer = std::unique_ptr<void, PixelFreer>;
 
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
-    // Without this call, decoding HEIF may time out on binder IPC calls.
-    android::ProcessState::self()->startThreadPool();
+AImageDecoder* init(const uint8_t* data, size_t size, bool useFileDescriptor) {
+    AImageDecoder* decoder = nullptr;
+    if (useFileDescriptor) {
+        constexpr char testFd[] = "tempFd";
+        int32_t fileDesc = open(testFd, O_RDWR | O_CREAT | O_TRUNC);
+        write(fileDesc, data, size);
+        AImageDecoder_createFromFd(fileDesc, &decoder);
+        close(fileDesc);
+    } else {
+        AImageDecoder_createFromBuffer(data, size, &decoder);
+    }
+    return decoder;
+}
 
-    DecoderPointer decoder = makeDecoder(data, size);
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+    FuzzedDataProvider dataProvider = FuzzedDataProvider(data, size);
+    /**
+     * Use maximum of 80% of buffer for creating decoder and save at least
+     * 20% buffer for fuzzing other APIs
+     */
+    const int32_t dataSize = dataProvider.ConsumeIntegralInRange<int32_t>(0, (size * 80) / 100);
+    std::vector<uint8_t> inputBuffer = dataProvider.ConsumeBytes<uint8_t>(dataSize);
+    AImageDecoder* decoder =
+            init(inputBuffer.data(), inputBuffer.size(), dataProvider.ConsumeBool());
     if (!decoder) {
         return 0;
     }
-
-    const AImageDecoderHeaderInfo* info = AImageDecoder_getHeaderInfo(decoder.get());
-    int32_t width = AImageDecoderHeaderInfo_getWidth(info);
-    int32_t height = AImageDecoderHeaderInfo_getHeight(info);
-
-    // Set an arbitrary limit on the size of an image. The fuzzer runs with a
-    // limited amount of memory, and keeping this allocation small allows the
-    // fuzzer to continue running to try to find more serious problems. This
-    // size is large enough to hold a photo taken by a current gen phone.
-    constexpr int32_t kMaxDimension = 5000;
-    if (width > kMaxDimension || height > kMaxDimension) {
-        return 0;
+    const AImageDecoderHeaderInfo* headerInfo = AImageDecoder_getHeaderInfo(decoder);
+    AImageDecoderFrameInfo* frameInfo = AImageDecoderFrameInfo_create();
+    int32_t height = AImageDecoderHeaderInfo_getHeight(headerInfo);
+    int32_t width = AImageDecoderHeaderInfo_getWidth(headerInfo);
+    while (dataProvider.remaining_bytes()) {
+        auto invokeImageApi = dataProvider.PickValueInArray<const std::function<void()>>({
+                [&]() {
+                    int32_t testHeight =
+                            dataProvider.ConsumeIntegralInRange<int32_t>(kMinDimension,
+                                                                         kMaxDimension);
+                    int32_t testWidth = dataProvider.ConsumeIntegralInRange<int32_t>(kMinDimension,
+                                                                                     kMaxDimension);
+                    int32_t result = AImageDecoder_setTargetSize(decoder, testHeight, testWidth);
+                    if (result == ANDROID_IMAGE_DECODER_SUCCESS) {
+                        height = testHeight;
+                        width = testWidth;
+                    }
+                },
+                [&]() {
+                    const bool required = dataProvider.ConsumeBool();
+                    AImageDecoder_setUnpremultipliedRequired(decoder, required);
+                },
+                [&]() {
+                    AImageDecoder_setAndroidBitmapFormat(
+                            decoder,
+                            dataProvider.ConsumeIntegralInRange<
+                                    int32_t>(ANDROID_BITMAP_FORMAT_NONE,
+                                             ANDROID_BITMAP_FORMAT_RGBA_1010102) /* format */);
+                },
+                [&]() {
+                    AImageDecoder_setDataSpace(decoder,
+                                               dataProvider
+                                                       .ConsumeIntegral<int32_t>() /* dataspace */);
+                },
+                [&]() {
+                    ARect rect{dataProvider.ConsumeIntegral<int32_t>() /* left */,
+                               dataProvider.ConsumeIntegral<int32_t>() /* top */,
+                               dataProvider.ConsumeIntegral<int32_t>() /* right */,
+                               dataProvider.ConsumeIntegral<int32_t>() /* bottom */};
+                    AImageDecoder_setCrop(decoder, rect);
+                },
+                [&]() { AImageDecoderHeaderInfo_getWidth(headerInfo); },
+                [&]() { AImageDecoderHeaderInfo_getMimeType(headerInfo); },
+                [&]() { AImageDecoderHeaderInfo_getAlphaFlags(headerInfo); },
+                [&]() { AImageDecoderHeaderInfo_getAndroidBitmapFormat(headerInfo); },
+                [&]() {
+                    int32_t tempHeight;
+                    int32_t tempWidth;
+                    AImageDecoder_computeSampledSize(decoder,
+                                                     dataProvider.ConsumeIntegral<
+                                                             int>() /* sampleSize */,
+                                                     &tempWidth, &tempHeight);
+                },
+                [&]() { AImageDecoderHeaderInfo_getDataSpace(headerInfo); },
+                [&]() { AImageDecoder_getRepeatCount(decoder); },
+                [&]() { AImageDecoder_getFrameInfo(decoder, frameInfo); },
+                [&]() { AImageDecoderFrameInfo_getDuration(frameInfo); },
+                [&]() { AImageDecoderFrameInfo_hasAlphaWithinBounds(frameInfo); },
+                [&]() { AImageDecoderFrameInfo_getDisposeOp(frameInfo); },
+                [&]() { AImageDecoderFrameInfo_getBlendOp(frameInfo); },
+                [&]() {
+                    AImageDecoder_setInternallyHandleDisposePrevious(
+                            decoder, dataProvider.ConsumeBool() /* handle */);
+                },
+                [&]() { AImageDecoder_rewind(decoder); },
+                [&]() { AImageDecoder_advanceFrame(decoder); },
+                [&]() {
+                    size_t stride = AImageDecoder_getMinimumStride(decoder);
+                    size_t pixelSize = height * stride;
+                    auto pixels = PixelPointer(std::malloc(pixelSize));
+                    if (!pixels.get()) {
+                        return;
+                    }
+                    AImageDecoder_decodeImage(decoder, pixels.get(), stride, pixelSize);
+                },
+        });
+        invokeImageApi();
     }
 
-    size_t stride = AImageDecoder_getMinimumStride(decoder.get());
-    size_t pixelSize = height * stride;
-    auto pixels = PixelPointer(std::malloc(pixelSize));
-    if (!pixels.get()) {
-        return 0;
-    }
-
-    while (true) {
-        int result = AImageDecoder_decodeImage(decoder.get(), pixels.get(), stride, pixelSize);
-        if (result != ANDROID_IMAGE_DECODER_SUCCESS) break;
-
-        result = AImageDecoder_advanceFrame(decoder.get());
-        if (result != ANDROID_IMAGE_DECODER_SUCCESS) break;
-    }
+    AImageDecoderFrameInfo_delete(frameInfo);
+    AImageDecoder_delete(decoder);
     return 0;
 }
diff --git a/native/graphics/jni/fuzz/imagedecoder_fuzzer.dict b/native/graphics/jni/fuzz/imagedecoder_fuzzer.dict
new file mode 100644
index 0000000..5b54a0e
--- /dev/null
+++ b/native/graphics/jni/fuzz/imagedecoder_fuzzer.dict
@@ -0,0 +1,7 @@
+kw1="\x89\x50\x4E\x47"
+kw2="\xff\xD8\xFF"
+kw4="\x52\x49\x46\x46"
+kw5="\x00\x00\x01\x00"
+kw6="\x47\x49\x46\x08"
+kw7="ftyp"
+kw8="\x04\x00\x00\x00"
diff --git a/packages/CtsShim/apk/arm/CtsShim.apk b/packages/CtsShim/apk/arm/CtsShim.apk
index 38a6d13..5ab190d 100644
--- a/packages/CtsShim/apk/arm/CtsShim.apk
+++ b/packages/CtsShim/apk/arm/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/arm/CtsShimPriv.apk b/packages/CtsShim/apk/arm/CtsShimPriv.apk
index bcd9836..51a8c46 100644
--- a/packages/CtsShim/apk/arm/CtsShimPriv.apk
+++ b/packages/CtsShim/apk/arm/CtsShimPriv.apk
Binary files differ
diff --git a/packages/CtsShim/apk/x86/CtsShim.apk b/packages/CtsShim/apk/x86/CtsShim.apk
index 38a6d13..5ab190d 100644
--- a/packages/CtsShim/apk/x86/CtsShim.apk
+++ b/packages/CtsShim/apk/x86/CtsShim.apk
Binary files differ
diff --git a/packages/CtsShim/apk/x86/CtsShimPriv.apk b/packages/CtsShim/apk/x86/CtsShimPriv.apk
index f778904..fcd0273 100644
--- a/packages/CtsShim/apk/x86/CtsShimPriv.apk
+++ b/packages/CtsShim/apk/x86/CtsShimPriv.apk
Binary files differ
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
index fc66ad6..d14234e 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
@@ -20,7 +20,7 @@
 import kotlin.random.Random
 
 // artificially speed up or slow down the simulation
-const val TIME_SCALE = 1f
+const val TIME_SCALE = 1f // simulation seconds per wall clock second
 
 // if it's been over 1 real second since our last timestep, don't simulate that elapsed time.
 // this allows the simulation to "pause" when, for example, the activity pauses
@@ -36,6 +36,19 @@
     fun postUpdate(sim: Simulator, dt: Float)
 }
 
+interface Removable {
+    fun canBeRemoved(): Boolean
+}
+
+class Fuse(var lifetime: Float) : Removable {
+    fun update(dt: Float) {
+        lifetime -= dt
+    }
+    override fun canBeRemoved(): Boolean {
+        return lifetime < 0
+    }
+}
+
 open class Body(var name: String = "Unknown") : Entity {
     var pos = Vec2.Zero
     var opos = Vec2.Zero
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt b/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt
index 1e54569..d6fbc11 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Universe.kt
@@ -43,11 +43,9 @@
 const val MAIN_ENGINE_ACCEL = 1000f // thrust effect, pixels per second squared
 const val LAUNCH_MECO = 2f // how long to suspend gravity when launching
 
-const val SCALED_THRUST = true
+const val LANDING_REMOVAL_TIME = 3600f // one hour of simulation time
 
-interface Removable {
-    fun canBeRemoved(): Boolean
-}
+const val SCALED_THRUST = true
 
 open class Planet(
     val orbitCenter: Vec2,
@@ -321,7 +319,7 @@
                         //
                         (1..10).forEach {
                             Spark(
-                                    lifetime = rng.nextFloatInRange(0.5f, 2f),
+                                    ttl = rng.nextFloatInRange(0.5f, 2f),
                                     style = Spark.Style.DOT,
                                     color = Color.White,
                                     size = 1f
@@ -359,13 +357,22 @@
         entities
             .filterIsInstance<Removable>()
             .filter(predicate = Removable::canBeRemoved)
-            .filterIsInstance<Entity>()
-            .forEach { remove(it) }
+            .forEach { remove(it as Entity) }
+
+        constraints
+            .filterIsInstance<Removable>()
+            .filter(predicate = Removable::canBeRemoved)
+            .forEach { remove(it as Constraint) }
     }
 }
 
-class Landing(var ship: Spacecraft?, val planet: Planet, val angle: Float, val text: String = "") :
-    Constraint {
+class Landing(
+    var ship: Spacecraft?,
+    val planet: Planet,
+    val angle: Float,
+    val text: String = "",
+    private val fuse: Fuse = Fuse(LANDING_REMOVAL_TIME)
+) : Constraint, Removable by fuse {
     override fun solve(sim: Simulator, dt: Float) {
         ship?.let { ship ->
             val landingVector = Vec2.makeWithAngleMag(angle, ship.radius + planet.radius)
@@ -373,17 +380,20 @@
             ship.pos = (ship.pos * 0.5f) + (desiredPos * 0.5f) // @@@ FIXME
             ship.angle = angle
         }
+
+        fuse.update(dt)
     }
 }
 
 class Spark(
-    var lifetime: Float,
+    var ttl: Float,
     collides: Boolean = false,
     mass: Float = 0f,
     val style: Style = Style.LINE,
     val color: Color = Color.Gray,
-    val size: Float = 2f
-) : Removable, Body() {
+    val size: Float = 2f,
+    val fuse: Fuse = Fuse(ttl)
+) : Removable by fuse, Body(name = "Spark") {
     enum class Style {
         LINE,
         LINE_ABSOLUTE,
@@ -398,10 +408,7 @@
     }
     override fun update(sim: Simulator, dt: Float) {
         super.update(sim, dt)
-        lifetime -= dt
-    }
-    override fun canBeRemoved(): Boolean {
-        return lifetime < 0
+        fuse.update(dt)
     }
 }
 
@@ -486,11 +493,11 @@
             // exhaust
             sim.add(
                 Spark(
-                        lifetime = sim.rng.nextFloatInRange(0.5f, 1f),
+                        ttl = sim.rng.nextFloatInRange(0.5f, 1f),
                         collides = true,
                         mass = 1f,
                         style = Spark.Style.RING,
-                        size = 3f,
+                        size = 1f,
                         color = Color(0x40FFFFFF)
                     )
                     .also { spark ->
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
index 974784d..ed3ebc7 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
@@ -30,6 +30,7 @@
 import androidx.core.math.MathUtils.clamp
 import com.android.egg.flags.Flags.flagFlag
 import java.lang.Float.max
+import kotlin.math.exp
 import kotlin.math.sqrt
 
 const val DRAW_ORBITS = true
@@ -289,7 +290,8 @@
 
 fun ZoomedDrawScope.drawSpark(spark: Spark) {
     with(spark) {
-        if (lifetime < 0) return
+        if (fuse.lifetime < 0) return
+        val life = 1f - fuse.lifetime / ttl
         when (style) {
             Spark.Style.LINE ->
                 if (opos != Vec2.Zero) drawLine(color, opos, pos, strokeWidth = size)
@@ -297,7 +299,13 @@
                 if (opos != Vec2.Zero) drawLine(color, opos, pos, strokeWidth = size / zoom)
             Spark.Style.DOT -> drawCircle(color, size, pos)
             Spark.Style.DOT_ABSOLUTE -> drawCircle(color, size, pos / zoom)
-            Spark.Style.RING -> drawCircle(color, size, pos, style = Stroke(width = 1f / zoom))
+            Spark.Style.RING ->
+                drawCircle(
+                    color = color.copy(alpha = color.alpha * (1f - life)),
+                    radius = exp(lerp(size, 3f * size, life)) - 1f,
+                    center = pos,
+                    style = Stroke(width = 1f / zoom)
+                )
         }
     }
 }
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/MDnsUtils.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/MDnsUtils.java
old mode 100755
new mode 100644
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/ServiceResolver.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/ServiceResolver.java
old mode 100755
new mode 100644
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/VendorInfo.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/VendorInfo.java
old mode 100755
new mode 100644
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/XeroxPrintServiceRecommendationPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/xerox/XeroxPrintServiceRecommendationPlugin.java
old mode 100755
new mode 100644
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index f3ff0fe..717a8ee 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -34,6 +34,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRoute2Info;
+import android.os.SystemProperties;
 import android.util.SparseIntArray;
 
 import androidx.annotation.NonNull;
@@ -42,6 +43,7 @@
 import com.android.settingslib.R;
 import com.android.settingslib.media.flags.Flags;
 
+import java.util.Arrays;
 import java.util.Objects;
 
 /** A util class to get the appropriate icon for different device types. */
@@ -50,18 +52,23 @@
     private static final SparseIntArray AUDIO_DEVICE_TO_MEDIA_ROUTE_TYPE = new SparseIntArray();
 
     private final boolean mIsTv;
+    private final boolean mIsTablet;
     private final Context mContext;
     public DeviceIconUtil(@NonNull Context context) {
         mContext = Objects.requireNonNull(context);
         mIsTv =
                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
                         && Flags.enableTvMediaOutputDialog();
+        mIsTablet =
+                Arrays.asList(SystemProperties.get("ro.build.characteristics").split(","))
+                        .contains("tablet");
     }
 
     @VisibleForTesting
     /* package */ DeviceIconUtil(boolean isTv) {
         mContext = null;
         mIsTv = isTv;
+        mIsTablet = false;
     }
 
     /** Returns a drawable for an icon representing the given audioDeviceType. */
@@ -80,12 +87,17 @@
     /** Returns a drawable res ID for an icon representing the given mediaRouteType. */
     @DrawableRes
     public int getIconResIdFromMediaRouteType(@MediaRoute2Info.Type int type) {
-        return mIsTv ? getIconResourceIdForTv(type) : getIconResourceIdForPhone(type);
+        return mIsTv
+                ? getIconResourceIdForTv(type)
+                : getIconResourceIdForPhoneOrTablet(type, mIsTablet);
     }
 
     @SuppressLint("SwitchIntDef")
     @DrawableRes
-    private static int getIconResourceIdForPhone(@MediaRoute2Info.Type int type) {
+    private static int getIconResourceIdForPhoneOrTablet(
+            @MediaRoute2Info.Type int type, boolean isTablet) {
+        int defaultResId = isTablet ? R.drawable.ic_media_tablet : R.drawable.ic_smartphone;
+
         return switch (type) {
             case MediaRoute2Info.TYPE_USB_DEVICE,
                             MediaRoute2Info.TYPE_USB_HEADSET,
@@ -98,7 +110,7 @@
                             MediaRoute2Info.TYPE_HDMI_ARC,
                             MediaRoute2Info.TYPE_HDMI_EARC ->
                     R.drawable.ic_external_display;
-            default -> R.drawable.ic_smartphone; // Includes TYPE_BUILTIN_SPEAKER.
+            default -> defaultResId; // Includes TYPE_BUILTIN_SPEAKER.
         };
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
index 8edda1a..883640d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/DeviceIconUtilTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.Context;
 import android.media.AudioDeviceInfo;
 import android.media.MediaRoute2Info;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -30,6 +31,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowSystemProperties;
 
 @RunWith(RobolectricTestRunner.class)
 public class DeviceIconUtilTest {
@@ -37,9 +40,12 @@
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
+    private Context mContext;
+
     @Before
     public void setup() {
         mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TV_MEDIA_OUTPUT_DIALOG);
+        mContext = RuntimeEnvironment.getApplication();
     }
 
     @Test
@@ -171,6 +177,14 @@
     }
 
     @Test
+    public void getIconResIdFromMediaRouteType_onTablet_builtinSpeaker_isTablet() {
+        ShadowSystemProperties.override("ro.build.characteristics", "tablet");
+        assertThat(new DeviceIconUtil(mContext)
+                .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER))
+                .isEqualTo(R.drawable.ic_media_tablet);
+    }
+
+    @Test
     public void getIconResIdFromMediaRouteType_unsupportedType_isSmartphone() {
         assertThat(new DeviceIconUtil(/* isTv */ false)
                 .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN))
@@ -178,6 +192,14 @@
     }
 
     @Test
+    public void getIconResIdFromMediaRouteType_onTablet_unsupportedType_isTablet() {
+        ShadowSystemProperties.override("ro.build.characteristics", "tablet");
+        assertThat(new DeviceIconUtil(mContext)
+                .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN))
+                .isEqualTo(R.drawable.ic_media_tablet);
+    }
+
+    @Test
     public void getIconResIdFromMediaRouteType_tv_unsupportedType_isSpeaker() {
         assertThat(new DeviceIconUtil(/* isTv */ true)
                 .getIconResIdFromMediaRouteType(MediaRoute2Info.TYPE_UNKNOWN))
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 29b57c9..85aa33a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -981,6 +981,16 @@
 }
 
 flag {
+  name: "media_controls_lockscreen_shade_bug_fix"
+  namespace: "systemui"
+  description: "Use ShadeInteractor for media location changes"
+  bug: "319244625"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   namespace: "systemui"
   name: "enable_view_capture_tracing"
   description: "Enables view capture tracing in System UI."
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 3cc8431..6001f1f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -19,8 +19,6 @@
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
 import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
-import androidx.compose.foundation.gestures.horizontalDrag
-import androidx.compose.foundation.gestures.verticalDrag
 import androidx.compose.runtime.Stable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -32,7 +30,9 @@
 import androidx.compose.ui.input.pointer.PointerInputScope
 import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
 import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
+import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
 import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.input.pointer.positionChangeIgnoreConsumed
 import androidx.compose.ui.input.pointer.util.VelocityTracker
 import androidx.compose.ui.input.pointer.util.addPointerInputChange
 import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
@@ -46,6 +46,8 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.util.fastAll
+import androidx.compose.ui.util.fastAny
+import androidx.compose.ui.util.fastFirstOrNull
 import androidx.compose.ui.util.fastForEach
 import kotlin.coroutines.cancellation.CancellationException
 import kotlin.math.sign
@@ -236,8 +238,23 @@
         onDragCancel: (controller: DragController) -> Unit,
         swipeDetector: SwipeDetector,
     ) {
-        // Wait for a consumable event in [PointerEventPass.Main] pass
-        val consumablePointer = awaitConsumableEvent().changes.first()
+        val consumablePointer =
+            awaitConsumableEvent {
+                    // We are searching for an event that can be used as the starting point for the
+                    // drag gesture. Our options are:
+                    // - Initial: These events should never be consumed by the MultiPointerDraggable
+                    //   since our ancestors can consume the gesture, but we would eliminate this
+                    //   possibility for our descendants.
+                    // - Main: These events are consumed during the drag gesture, and they are a
+                    //   good place to start if the previous event has not been consumed.
+                    // - Final: If the previous event has been consumed, we can wait for the Main
+                    //   pass to finish. If none of our ancestors were interested in the event, we
+                    //   can wait for an unconsumed event in the Final pass.
+                    val previousConsumed = currentEvent.changes.fastAny { it.isConsumed }
+                    if (previousConsumed) PointerEventPass.Final else PointerEventPass.Main
+                }
+                .changes
+                .first()
 
         var overSlop = 0f
         val drag =
@@ -297,18 +314,22 @@
                 onDrag(controller, drag, overSlop)
 
                 successful =
-                    when (orientation) {
-                        Orientation.Horizontal ->
-                            horizontalDrag(drag.id) {
-                                onDrag(controller, it, it.positionChange().toFloat())
-                                it.consume()
-                            }
-                        Orientation.Vertical ->
-                            verticalDrag(drag.id) {
-                                onDrag(controller, it, it.positionChange().toFloat())
-                                it.consume()
-                            }
-                    }
+                    drag(
+                        initialPointerId = drag.id,
+                        hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f },
+                        onDrag = {
+                            onDrag(controller, it, it.positionChange().toFloat())
+                            it.consume()
+                        },
+                        onIgnoredEvent = {
+                            // We are still dragging an object, but this event is not of interest to
+                            // the caller.
+                            // This event will not trigger the onDrag event, but we will consume the
+                            // event to prevent another pointerInput from interrupting the current
+                            // gesture just because the event was ignored.
+                            it.consume()
+                        },
+                    )
             } catch (t: Throwable) {
                 onDragCancel(controller)
                 throw t
@@ -322,7 +343,9 @@
         }
     }
 
-    private suspend fun AwaitPointerEventScope.awaitConsumableEvent(): PointerEvent {
+    private suspend fun AwaitPointerEventScope.awaitConsumableEvent(
+        pass: () -> PointerEventPass,
+    ): PointerEvent {
         fun canBeConsumed(changes: List<PointerInputChange>): Boolean {
             // All pointers must be:
             return changes.fastAll {
@@ -337,9 +360,7 @@
 
         var event: PointerEvent
         do {
-            // To allow the descendants with the opportunity to consume the event, we wait for it in
-            // the Main pass.
-            event = awaitPointerEvent()
+            event = awaitPointerEvent(pass = pass())
         } while (!canBeConsumed(event.changes))
 
         // We found a consumable event in the Main pass
@@ -352,4 +373,82 @@
             Orientation.Horizontal -> x
         }
     }
+
+    /**
+     * Continues to read drag events until all pointers are up or the drag event is canceled. The
+     * initial pointer to use for driving the drag is [initialPointerId]. [hasDragged] passes the
+     * result whether a change was detected from the drag function or not.
+     *
+     * Whenever the pointer moves, if [hasDragged] returns true, [onDrag] is called; otherwise,
+     * [onIgnoredEvent] is called.
+     *
+     * @return true when gesture ended with all pointers up and false when the gesture was canceled.
+     *
+     * Note: Inspired by DragGestureDetector.kt
+     */
+    private suspend inline fun AwaitPointerEventScope.drag(
+        initialPointerId: PointerId,
+        hasDragged: (PointerInputChange) -> Boolean,
+        onDrag: (PointerInputChange) -> Unit,
+        onIgnoredEvent: (PointerInputChange) -> Unit,
+    ): Boolean {
+        val pointer = currentEvent.changes.fastFirstOrNull { it.id == initialPointerId }
+        val isPointerUp = pointer?.pressed != true
+        if (isPointerUp) {
+            return false // The pointer has already been lifted, so the gesture is canceled
+        }
+        var pointerId = initialPointerId
+        while (true) {
+            val change = awaitDragOrUp(pointerId, hasDragged, onIgnoredEvent) ?: return false
+
+            if (change.isConsumed) {
+                return false
+            }
+
+            if (change.changedToUpIgnoreConsumed()) {
+                return true
+            }
+
+            onDrag(change)
+            pointerId = change.id
+        }
+    }
+
+    /**
+     * Waits for a single drag in one axis, final pointer up, or all pointers are up. When
+     * [initialPointerId] has lifted, another pointer that is down is chosen to be the finger
+     * governing the drag. When the final pointer is lifted, that [PointerInputChange] is returned.
+     * When a drag is detected, that [PointerInputChange] is returned. A drag is only detected when
+     * [hasDragged] returns `true`. Events that should not be captured are passed to
+     * [onIgnoredEvent].
+     *
+     * `null` is returned if there was an error in the pointer input stream and the pointer that was
+     * down was dropped before the 'up' was received.
+     *
+     * Note: Inspired by DragGestureDetector.kt
+     */
+    private suspend inline fun AwaitPointerEventScope.awaitDragOrUp(
+        initialPointerId: PointerId,
+        hasDragged: (PointerInputChange) -> Boolean,
+        onIgnoredEvent: (PointerInputChange) -> Unit,
+    ): PointerInputChange? {
+        var pointerId = initialPointerId
+        while (true) {
+            val event = awaitPointerEvent()
+            val dragEvent = event.changes.fastFirstOrNull { it.id == pointerId } ?: return null
+            if (dragEvent.changedToUpIgnoreConsumed()) {
+                val otherDown = event.changes.fastFirstOrNull { it.pressed }
+                if (otherDown == null) {
+                    // This is the last "up"
+                    return dragEvent
+                } else {
+                    pointerId = otherDown.id
+                }
+            } else if (hasDragged(dragEvent)) {
+                return dragEvent
+            } else {
+                onIgnoredEvent(dragEvent)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 4bb643f..1a0740b 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -349,6 +349,121 @@
     }
 
     @Test
+    fun multiPointerDuringAnotherGestureWaitAConsumableEventAfterMainPass() {
+        val size = 200f
+        val middle = Offset(size / 2f, size / 2f)
+
+        var verticalStarted = false
+        var verticalDragged = false
+        var verticalStopped = false
+        var horizontalStarted = false
+        var horizontalDragged = false
+        var horizontalStopped = false
+
+        var touchSlop = 0f
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Box(
+                Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+                    .multiPointerDraggable(
+                        orientation = Orientation.Vertical,
+                        enabled = { true },
+                        startDragImmediately = { false },
+                        onDragStarted = { _, _, _ ->
+                            verticalStarted = true
+                            object : DragController {
+                                override fun onDrag(delta: Float) {
+                                    verticalDragged = true
+                                }
+
+                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
+                                    verticalStopped = true
+                                }
+                            }
+                        },
+                    )
+                    .multiPointerDraggable(
+                        orientation = Orientation.Horizontal,
+                        enabled = { true },
+                        startDragImmediately = { false },
+                        onDragStarted = { _, _, _ ->
+                            horizontalStarted = true
+                            object : DragController {
+                                override fun onDrag(delta: Float) {
+                                    horizontalDragged = true
+                                }
+
+                                override fun onStop(velocity: Float, canChangeScene: Boolean) {
+                                    horizontalStopped = true
+                                }
+                            }
+                        },
+                    )
+            )
+        }
+
+        fun startDraggingDown() {
+            rule.onRoot().performTouchInput {
+                down(middle)
+                moveBy(Offset(0f, touchSlop))
+            }
+        }
+
+        fun startDraggingRight() {
+            rule.onRoot().performTouchInput {
+                down(middle)
+                moveBy(Offset(touchSlop, 0f))
+            }
+        }
+
+        fun stopDragging() {
+            rule.onRoot().performTouchInput { up() }
+        }
+
+        fun continueDown() {
+            rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) }
+        }
+
+        fun continueRight() {
+            rule.onRoot().performTouchInput { moveBy(Offset(touchSlop, 0f)) }
+        }
+
+        startDraggingDown()
+        assertThat(verticalStarted).isTrue()
+        assertThat(verticalDragged).isTrue()
+        assertThat(verticalStopped).isFalse()
+
+        // Ignore right swipe, do not interrupt the dragging gesture.
+        continueRight()
+        assertThat(horizontalStarted).isFalse()
+        assertThat(horizontalDragged).isFalse()
+        assertThat(horizontalStopped).isFalse()
+        assertThat(verticalStopped).isFalse()
+
+        stopDragging()
+        assertThat(verticalStopped).isTrue()
+
+        verticalStarted = false
+        verticalDragged = false
+        verticalStopped = false
+
+        startDraggingRight()
+        assertThat(horizontalStarted).isTrue()
+        assertThat(horizontalDragged).isTrue()
+        assertThat(horizontalStopped).isFalse()
+
+        // Ignore down swipe, do not interrupt the dragging gesture.
+        continueDown()
+        assertThat(verticalStarted).isFalse()
+        assertThat(verticalDragged).isFalse()
+        assertThat(verticalStopped).isFalse()
+        assertThat(horizontalStopped).isFalse()
+
+        stopDragging()
+        assertThat(horizontalStopped).isTrue()
+    }
+
+    @Test
     fun multiPointerSwipeDetectorInteraction() {
         val size = 200f
         val middle = Offset(size / 2f, size / 2f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
similarity index 87%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
index f561c53..d84d151 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewControllerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams;
+package com.android.systemui.ambient.statusbar.ui;
 
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
@@ -44,6 +44,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.core.FakeLogBuffer;
 import com.android.systemui.res.R;
@@ -54,7 +58,6 @@
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateListener;
-import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.time.DateFormatUtil;
 
 import org.junit.Before;
@@ -72,14 +75,12 @@
 @SmallTest
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 @RunWith(AndroidJUnit4.class)
-public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
+public class AmbientStatusBarViewControllerTest extends SysuiTestCase {
     private static final String NOTIFICATION_INDICATOR_FORMATTER_STRING =
             "{count, plural, =1 {# notification} other {# notifications}}";
 
     @Mock
-    MockDreamOverlayStatusBarView mView;
-    @Mock
-    TouchInsetManager.TouchInsetSession mTouchSession;
+    MockAmbientStatusBarView mView;
     @Mock
     Resources mResources;
     @Mock
@@ -114,9 +115,11 @@
 
     private final Executor mMainExecutor = Runnable::run;
 
-    private final FakeWifiRepository mWifiRepository = new FakeWifiRepository();
+    private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
 
-    DreamOverlayStatusBarViewController mController;
+    private final FakeWifiRepository mWifiRepository = mKosmos.getFakeWifiRepository();
+
+    AmbientStatusBarViewController mController;
 
     @Before
     public void setup() {
@@ -128,11 +131,10 @@
         doCallRealMethod().when(mView).getVisibility();
         when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
 
-        mController = new DreamOverlayStatusBarViewController(
+        mController = new AmbientStatusBarViewController(
                 mView,
                 mResources,
                 mMainExecutor,
-                mTouchSession,
                 mAlarmManager,
                 mNextAlarmController,
                 mDateFormatUtil,
@@ -143,7 +145,7 @@
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
                 mUserTracker,
-                mWifiRepository,
+                mKosmos.getWifiInteractor(),
                 mLogBuffer);
     }
 
@@ -164,7 +166,7 @@
         mController.updateWifiUnavailableStatusIcon(false);
 
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, true, null);
+                AmbientStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, true, null);
     }
 
     @Test
@@ -173,7 +175,7 @@
         mController.updateWifiUnavailableStatusIcon(true);
 
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, false, null);
+                AmbientStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, false, null);
     }
 
     @Test
@@ -183,7 +185,7 @@
         when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(alarmClockInfo);
         mController.onViewAttached();
         verify(mView).showIcon(
-                eq(DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET), eq(true), any());
+                eq(AmbientStatusBarView.STATUS_ICON_ALARM_SET), eq(true), any());
     }
 
     @Test
@@ -191,7 +193,7 @@
         when(mAlarmManager.getNextAlarmClock(anyInt())).thenReturn(null);
         mController.onViewAttached();
         verify(mView).showIcon(
-                eq(DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET), eq(false), isNull());
+                eq(AmbientStatusBarView.STATUS_ICON_ALARM_SET), eq(false), isNull());
     }
 
     @Test
@@ -202,7 +204,7 @@
                 .thenReturn(false);
         mController.onViewAttached();
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED, true, null);
+                AmbientStatusBarView.STATUS_ICON_MIC_DISABLED, true, null);
     }
 
     @Test
@@ -213,7 +215,7 @@
                 .thenReturn(true);
         mController.onViewAttached();
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED, true, null);
+                AmbientStatusBarView.STATUS_ICON_CAMERA_DISABLED, true, null);
     }
 
     @Test
@@ -224,7 +226,7 @@
                 .thenReturn(true);
         mController.onViewAttached();
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true, null);
+                AmbientStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true, null);
     }
 
     @Test
@@ -237,7 +239,7 @@
         callbackCapture.getValue().onNotificationCountChanged(1);
 
         verify(mView).showIcon(
-                eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
+                eq(AmbientStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
     }
 
     @Test
@@ -250,16 +252,15 @@
         callbackCapture.getValue().onNotificationCountChanged(0);
 
         verify(mView).showIcon(
-                eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(false), isNull());
+                eq(AmbientStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(false), isNull());
     }
 
     @Test
     public void testNotificationsIconNotShownWhenCountProviderAbsent() {
-        DreamOverlayStatusBarViewController controller = new DreamOverlayStatusBarViewController(
+        AmbientStatusBarViewController controller = new AmbientStatusBarViewController(
                 mView,
                 mResources,
                 mMainExecutor,
-                mTouchSession,
                 mAlarmManager,
                 mNextAlarmController,
                 mDateFormatUtil,
@@ -270,11 +271,11 @@
                 mDreamOverlayStatusBarItemsProvider,
                 mDreamOverlayStateController,
                 mUserTracker,
-                mWifiRepository,
+                mKosmos.getWifiInteractor(),
                 mLogBuffer);
         controller.onViewAttached();
         verify(mView, never()).showIcon(
-                eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
+                eq(AmbientStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
     }
 
     @Test
@@ -283,7 +284,7 @@
                 Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
         mController.onViewAttached();
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, true, null);
+                AmbientStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, true, null);
     }
 
     @Test
@@ -292,7 +293,7 @@
                 Settings.Global.ZEN_MODE_OFF);
         mController.onViewAttached();
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, false, null);
+                AmbientStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, false, null);
     }
 
     @Test
@@ -322,7 +323,7 @@
         callbackCapture.getValue().onNotificationCountChanged(1);
 
         verify(mView).showIcon(
-                eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
+                eq(AmbientStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
     }
 
     @Test
@@ -335,7 +336,7 @@
         callbackCapture.getValue().onNotificationCountChanged(0);
 
         verify(mView).showIcon(
-                eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(false), any());
+                eq(AmbientStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(false), any());
     }
 
     @Test
@@ -354,7 +355,7 @@
                 SensorPrivacyManager.Sensors.MICROPHONE, true);
 
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true, null);
+                AmbientStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true, null);
     }
 
     @Test
@@ -369,7 +370,7 @@
         callbackCapture.getValue().onZenChanged(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS);
 
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, true, null);
+                AmbientStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, true, null);
     }
 
     @Test
@@ -384,7 +385,7 @@
         callbackCapture.getValue().onZenChanged(Settings.Global.ZEN_MODE_OFF);
 
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, false, null);
+                AmbientStatusBarView.STATUS_ICON_PRIORITY_MODE_ON, false, null);
     }
 
     @Test
@@ -399,7 +400,7 @@
         callbackCapture.getValue().onStateChanged();
 
         verify(mView).showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE, true, null);
+                AmbientStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE, true, null);
     }
 
     @Test
@@ -460,7 +461,7 @@
 
         final ArgumentCaptor<DreamOverlayStatusBarItemsProvider.Callback>
                 callbackCapture = ArgumentCaptor.forClass(
-                        DreamOverlayStatusBarItemsProvider.Callback.class);
+                DreamOverlayStatusBarItemsProvider.Callback.class);
         verify(mDreamOverlayStatusBarItemsProvider).addCallback(callbackCapture.capture());
         callbackCapture.getValue().onStatusBarItemsChanged(List.of(mStatusBarItem));
 
@@ -532,10 +533,10 @@
         callback.onStateChanged();
     }
 
-    private static class MockDreamOverlayStatusBarView extends DreamOverlayStatusBarView {
+    private static class MockAmbientStatusBarView extends AmbientStatusBarView {
         private int mVisibility = View.VISIBLE;
 
-        private MockDreamOverlayStatusBarView(Context context) {
+        private MockAmbientStatusBarView(Context context) {
             super(context);
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 9c2791f..75a77cf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -131,8 +131,9 @@
     negativeButton: String = "neg",
 ): PromptInfo {
     val info = PromptInfo()
-    info.logoRes = logoRes
-    info.logoBitmap = logoBitmap
+    if (logoBitmap != null) {
+        info.setLogo(logoRes, logoBitmap)
+    }
     info.logoDescription = logoDescription
     info.title = title
     info.subtitle = subtitle
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 86fdaa5..73ef775 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -7,6 +7,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarViewController
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
 import com.android.systemui.log.core.FakeLogBuffer
@@ -43,7 +44,7 @@
     @Mock private lateinit var mockAnimator: AnimatorSet
     @Mock private lateinit var blurUtils: BlurUtils
     @Mock private lateinit var hostViewController: ComplicationHostViewController
-    @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
+    @Mock private lateinit var statusBarViewController: AmbientStatusBarViewController
     @Mock private lateinit var stateController: DreamOverlayStateController
     @Mock private lateinit var transitionViewModel: DreamViewModel
     private val logBuffer = FakeLogBuffer.Factory.create()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index f5c86e0..4c8c113 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -47,6 +47,7 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarViewController;
 import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
@@ -55,6 +56,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.touch.TouchInsetManager;
 
 import kotlinx.coroutines.CoroutineDispatcher;
 
@@ -80,7 +82,7 @@
     ViewTreeObserver mViewTreeObserver;
 
     @Mock
-    DreamOverlayStatusBarViewController mDreamOverlayStatusBarViewController;
+    AmbientStatusBarViewController mAmbientStatusBarViewController;
 
     @Mock
     LowLightTransitionCoordinator mLowLightTransitionCoordinator;
@@ -131,6 +133,8 @@
     CommunalInteractor mCommunalInteractor;
     @Mock
     private DreamManager mDreamManager;
+    @Mock
+    private TouchInsetManager.TouchInsetSession mTouchInsetSession;
 
     DreamOverlayContainerViewController mController;
 
@@ -150,8 +154,9 @@
                 mComplicationHostViewController,
                 mDreamOverlayContentView,
                 mHubGestureIndicatorView,
-                mDreamOverlayStatusBarViewController,
+                mAmbientStatusBarViewController,
                 mLowLightTransitionCoordinator,
+                mTouchInsetSession,
                 mBlurUtils,
                 mHandler,
                 mDispatcher,
@@ -190,7 +195,7 @@
     @Test
     public void testDreamOverlayStatusBarViewControllerInitialized() {
         mController.init();
-        verify(mDreamOverlayStatusBarViewController).init();
+        verify(mAmbientStatusBarViewController).init();
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index c51413a..3d3c778 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -21,26 +21,35 @@
 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.haptics.vibratorHelper
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.statusbar.policy.keyguardStateController
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @RunWithLooper(setAsMainLooper = true)
 class QSLongPressEffectTest : SysuiTestCase() {
 
+    @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule()
     private val kosmos = testKosmos()
     private val vibratorHelper = kosmos.vibratorHelper
+    private val qsTile = kosmos.qsTileFactory.createTile("Test Tile")
+    @Mock private lateinit var callback: QSLongPressEffect.Callback
 
     private val effectDuration = 400
     private val lowTickDuration = 12
@@ -54,13 +63,15 @@
             lowTickDuration
         vibratorHelper.primitiveDurations[VibrationEffect.Composition.PRIMITIVE_SPIN] = spinDuration
 
-        kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+        whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(true)
 
         longPressEffect =
             QSLongPressEffect(
                 vibratorHelper,
-                kosmos.keyguardInteractor,
+                kosmos.keyguardStateController,
             )
+        longPressEffect.callback = callback
+        longPressEffect.qsTile = qsTile
     }
 
     @Test
@@ -107,28 +118,13 @@
         }
 
     @Test
-    fun onActionUp_whileWaiting_performsClick() =
-        testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
-            // GIVEN an action is being collected
-            val action by collectLastValue(longPressEffect.actionType)
-
-            // GIVEN an action up occurs
-            longPressEffect.handleActionUp()
-
-            // THEN the action to invoke is the click action and the effect does not start
-            assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
-            assertEffectDidNotStart()
-        }
-
-    @Test
     fun onWaitComplete_whileWaiting_beginsEffect() =
         testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
             // GIVEN the pressed timeout is complete
             longPressEffect.handleTimeoutComplete()
 
             // THEN the effect emits the action to start an animator
-            val action by collectLastValue(longPressEffect.actionType)
-            assertThat(action).isEqualTo(QSLongPressEffect.ActionType.START_ANIMATOR)
+            verify(callback, times(1)).onStartAnimator()
         }
 
     @Test
@@ -179,26 +175,28 @@
         }
 
     @Test
-    fun onAnimationComplete_keyguardDismissible_effectEndsWithLongPress() =
+    fun onAnimationComplete_keyguardDismissible_effectEndsWithPrepare() =
         testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
             // GIVEN that the animation completes
             longPressEffect.handleAnimationComplete()
 
-            // THEN the long-press effect completes with a LONG_PRESS
-            assertEffectCompleted(QSLongPressEffect.ActionType.LONG_PRESS)
+            // THEN the long-press effect completes and the view is called to prepare
+            assertEffectCompleted()
+            verify(callback, times(1)).onPrepareForLaunch()
         }
 
     @Test
-    fun onAnimationComplete_keyguardNotDismissible_effectEndsWithResetAndLongPress() =
+    fun onAnimationComplete_keyguardNotDismissible_effectEndsWithReset() =
         testWhileInState(QSLongPressEffect.State.RUNNING_FORWARD) {
             // GIVEN that the keyguard is not dismissible
-            kosmos.fakeKeyguardRepository.setKeyguardDismissible(false)
+            whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false)
 
             // GIVEN that the animation completes
             longPressEffect.handleAnimationComplete()
 
-            // THEN the long-press effect completes with RESET_AND_LONG_PRESS
-            assertEffectCompleted(QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS)
+            // THEN the long-press effect completes and the properties are called to reset
+            assertEffectCompleted()
+            verify(callback, times(1)).onResetProperties()
         }
 
     @Test
@@ -211,8 +209,7 @@
             longPressEffect.handleActionDown()
 
             // THEN the effect posts an action to cancel the animator
-            val action by collectLastValue(longPressEffect.actionType)
-            assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CANCEL_ANIMATOR)
+            verify(callback, times(1)).onCancelAnimator()
         }
 
     @Test
@@ -238,6 +235,29 @@
             assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
         }
 
+    @Test
+    fun onTileClick_whileWaiting_withQSTile_clicks() =
+        testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
+            // GIVEN that a click was detected
+            val couldClick = longPressEffect.onTileClick()
+
+            // THEN the click is successful
+            assertThat(couldClick).isTrue()
+        }
+
+    @Test
+    fun onTileClick_whileWaiting_withoutQSTile_cannotClick() =
+        testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
+            // GIVEN that no QSTile has been set
+            longPressEffect.qsTile = null
+
+            // GIVEN that a click was detected
+            val couldClick = longPressEffect.onTileClick()
+
+            // THEN the click is not successful
+            assertThat(couldClick).isFalse()
+        }
+
     private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
         with(kosmos) {
             testScope.runTest {
@@ -300,16 +320,13 @@
      * Asserts that the effect completes by checking that:
      * 1. The final snap haptics are played
      * 2. The internal state goes back to [QSLongPressEffect.State.IDLE]
-     * 3. The action to perform on the tile is the action given as a parameter
      */
-    private fun TestScope.assertEffectCompleted(expectedAction: QSLongPressEffect.ActionType) {
-        val action by collectLastValue(longPressEffect.actionType)
+    private fun assertEffectCompleted() {
         val snapEffect = LongPressHapticBuilder.createSnapEffect()
 
         assertThat(snapEffect).isNotNull()
         assertThat(vibratorHelper.hasVibratedWithEffects(snapEffect!!)).isTrue()
         assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
-        assertThat(action).isEqualTo(expectedAction)
     }
 
     /**
@@ -317,10 +334,8 @@
      * 1. The internal state is [QSLongPressEffect.State.RUNNING_BACKWARDS]
      * 2. An action to reverse the animator is emitted
      */
-    private fun TestScope.assertEffectReverses() {
-        val action by collectLastValue(longPressEffect.actionType)
-
+    private fun assertEffectReverses() {
         assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.RUNNING_BACKWARDS)
-        assertThat(action).isEqualTo(QSLongPressEffect.ActionType.REVERSE_ANIMATOR)
+        verify(callback, times(1)).onReverseAnimator()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index f46ca00..61d8216 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -50,6 +50,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.pow
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
 import org.junit.BeforeClass
 import org.junit.Test
@@ -205,8 +206,13 @@
                         pointerCount = if (downWithTwoPointers) 2 else 1,
                     )
                 )
-
-            assertThat(downDestination?.toScene)
+            val downScene by
+                collectLastValue(
+                    downDestination?.let {
+                        kosmos.sceneInteractor.resolveSceneFamily(downDestination.toScene)
+                    } ?: flowOf(null)
+                )
+            assertThat(downScene)
                 .isEqualTo(
                     expectedDownDestination(
                         downFromEdge = downFromEdge,
@@ -223,7 +229,14 @@
                     )
                 )
 
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
+            val upScene by
+                collectLastValue(
+                    destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene?.let { scene ->
+                        kosmos.sceneInteractor.resolveSceneFamily(scene)
+                    } ?: flowOf(null)
+                )
+
+            assertThat(upScene)
                 .isEqualTo(
                     expectedUpDestination(
                         canSwipeToEnter = canSwipeToEnter,
@@ -231,7 +244,14 @@
                     )
                 )
 
-            assertThat(destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene)
+            val leftScene by
+                collectLastValue(
+                    destinationScenes?.get(Swipe(SwipeDirection.Left))?.toScene?.let { scene ->
+                        kosmos.sceneInteractor.resolveSceneFamily(scene)
+                    } ?: flowOf(null)
+                )
+
+            assertThat(leftScene)
                 .isEqualTo(
                     expectedLeftDestination(
                         isCommunalAvailable = isCommunalAvailable,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
index 6b1794e2..cb4e2d3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt
@@ -30,8 +30,8 @@
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.domain.interactor.homeSceneFamilyResolver
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 7ee20e5..5b6fea5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -41,10 +41,10 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.homeSceneFamilyResolver
 import com.android.systemui.scene.domain.interactor.sceneBackInteractor
 import com.android.systemui.scene.domain.interactor.sceneContainerStartable
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
index f28ddeb..ac67ac8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt
@@ -30,8 +30,8 @@
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.domain.interactor.homeSceneFamilyResolver
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index f8a62cb..4d5d22c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -58,9 +58,9 @@
 import com.android.systemui.qs.footerActionsController
 import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.domain.interactor.homeSceneFamilyResolver
 import com.android.systemui.scene.domain.interactor.sceneContainerStartable
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.fakeSceneDataSource
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 92e6b16..ec7150b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.scene.data.repository.Transition
 import com.android.systemui.scene.data.repository.sceneContainerRepository
 import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.sceneContainerConfig
 import com.android.systemui.scene.sceneKeys
 import com.android.systemui.scene.shared.model.SceneFamilies
@@ -41,6 +42,7 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.test.runCurrent
@@ -399,8 +401,8 @@
     @Test
     fun resolveSceneFamily_home() =
         testScope.runTest {
-            assertThat(underTest.resolveSceneFamily(SceneFamilies.Home))
-                .isEqualTo(kosmos.homeSceneFamilyResolver.resolvedScene)
+            assertThat(underTest.resolveSceneFamily(SceneFamilies.Home).first())
+                .isEqualTo(kosmos.homeSceneFamilyResolver.resolvedScene.value)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
index 3a5ff00..fa4da42 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt
@@ -29,8 +29,8 @@
 import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
-import com.android.systemui.scene.domain.interactor.homeSceneFamilyResolver
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shared.recents.utilities.Utilities
 import com.android.systemui.testKosmos
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index f88d102..c53cdf8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -19,6 +19,8 @@
 import android.testing.TestableLooper
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.SwipeDirection
 import com.android.systemui.SysuiTestCase
@@ -27,6 +29,7 @@
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -39,8 +42,8 @@
 import com.android.systemui.qs.footerActionsViewModelFactory
 import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
 import com.android.systemui.res.R
-import com.android.systemui.scene.domain.interactor.homeSceneFamilyResolver
 import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
@@ -57,7 +60,9 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.Locale
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -126,9 +131,7 @@
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin
             )
-            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                SuccessFingerprintAuthenticationStatus(0, true)
-            )
+            setDeviceEntered(true)
 
             assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene)
                 .isEqualTo(SceneFamilies.Home)
@@ -196,9 +199,7 @@
             kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pin
             )
-            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
-                SuccessFingerprintAuthenticationStatus(0, true)
-            )
+            setDeviceEntered(true)
             runCurrent()
 
             assertThat(isClickable).isFalse()
@@ -345,6 +346,32 @@
         return maxTranslation
     }
 
+    private fun TestScope.setDeviceEntered(isEntered: Boolean) {
+        if (isEntered) {
+            // Unlock the device marking the device has entered.
+            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+        }
+        setScene(
+            if (isEntered) {
+                Scenes.Gone
+            } else {
+                Scenes.Lockscreen
+            }
+        )
+        assertThat(kosmos.deviceEntryInteractor.isDeviceEntered.value).isEqualTo(isEntered)
+    }
+
+    private fun TestScope.setScene(key: SceneKey) {
+        sceneInteractor.changeScene(key, "test")
+        sceneInteractor.setTransitionState(
+            MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+        )
+        runCurrent()
+    }
+
     private data class Translations(
         val start: Float,
         val end: Float,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
index 10a4eb7..7385a47 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
@@ -71,6 +71,7 @@
                 addOverride(R.drawable.ic_headphone, testIcon)
                 addOverride(R.drawable.ic_smartphone, testIcon)
                 addOverride(R.drawable.ic_media_speaker_device, testIcon)
+                addOverride(R.drawable.ic_media_tablet, testIcon)
 
                 addOverride(com.android.internal.R.drawable.ic_bt_hearing_aid, testIcon)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
index 8921a23..0f56d0b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
@@ -65,6 +65,7 @@
 
             with(context.orCreateTestableResources) {
                 addOverride(R.drawable.ic_smartphone, testIcon)
+                addOverride(R.drawable.ic_media_tablet, testIcon)
 
                 addOverride(R.string.media_transfer_this_device_name_tv, builtInDeviceName)
                 addOverride(R.string.media_transfer_this_device_name_tablet, builtInDeviceName)
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 83658d3..9ad4012 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -134,6 +134,12 @@
         default void setScreenOn(boolean screenOn) {}
 
         /**
+         * Sets a delegate to handle clock event registration. Should be called immediately after
+         * the view is created.
+         */
+        default void setTimeChangedDelegate(TimeChangedDelegate delegate) {}
+
+        /**
          * Set if dozing is true or false
          */
         default void setDozing(boolean dozing) {}
@@ -228,4 +234,13 @@
         /** Start the PendingIntent */
         void startPendingIntent(View v, PendingIntent pi, boolean showOnLockscreen);
     }
+
+    /** Interface for delegating time updates */
+    interface TimeChangedDelegate {
+        /** Register the callback to be called when time is updated **/
+        void register(Runnable callback);
+
+        /** Unegister the callback **/
+        void unregister();
+    }
 }
diff --git a/packages/SystemUI/res/drawable/ic_bt_le_audio_sharing_18dp.xml b/packages/SystemUI/res/drawable/ic_bt_le_audio_sharing_18dp.xml
new file mode 100644
index 0000000..dd3d9e3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_bt_le_audio_sharing_18dp.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:drawable="@drawable/ic_bt_le_audio_sharing"
+        android:width="18dp"
+        android:height="18dp" />
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/ambient_status_bar_view.xml
similarity index 97%
rename from packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
rename to packages/SystemUI/res/layout/ambient_status_bar_view.xml
index ec2edb5..7d765ce 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/ambient_status_bar_view.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.dreams.DreamOverlayStatusBarView
+<com.android.systemui.ambient.statusbar.ui.AmbientStatusBarView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/dream_overlay_status_bar"
@@ -118,4 +118,4 @@
             android:contentDescription="@string/assistant_attention_content_description" />
 
     </LinearLayout>
-</com.android.systemui.dreams.DreamOverlayStatusBarView>
+</com.android.systemui.ambient.statusbar.ui.AmbientStatusBarView>
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index a598007..27b8006 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -268,6 +268,12 @@
                 android:ellipsize="end"
                 android:maxLines="1"
                 android:text="@string/quick_settings_bluetooth_audio_sharing_button"
+                android:drawableStart="@drawable/ic_bt_le_audio_sharing_18dp"
+                android:drawablePadding="10dp"
+                android:drawableTint="?android:attr/textColorPrimary"
+                app:layout_constrainedWidth="true"
+                app:layout_constraintHorizontal_bias="0"
+                app:layout_constraintEnd_toStartOf="@+id/done_button"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintTop_toBottomOf="@+id/barrier"
diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml
index 4234fca5..dcd3fa6 100644
--- a/packages/SystemUI/res/layout/dream_overlay_container.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_container.xml
@@ -44,5 +44,5 @@
         app:layout_constraintBottom_toBottomOf="parent"
         />
 
-    <include layout="@layout/dream_overlay_status_bar_view" />
+    <include layout="@layout/ambient_status_bar_view" />
 </com.android.systemui.dreams.DreamOverlayContainerView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c2ca4da..0017db6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -699,9 +699,9 @@
     <!-- QuickSettings: Bluetooth auto on info text when enabled [CHAR LIMIT=NONE]-->
     <string name="turn_on_bluetooth_auto_info_enabled">Bluetooth will turn on tomorrow morning</string>
     <!-- QuickSettings: Bluetooth dialog audio sharing button text [CHAR LIMIT=50]-->
-    <string name="quick_settings_bluetooth_audio_sharing_button">Audio Sharing</string>
+    <string name="quick_settings_bluetooth_audio_sharing_button">Share audio</string>
     <!-- QuickSettings: Bluetooth dialog audio sharing button text when sharing audio [CHAR LIMIT=50]-->
-    <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing Audio</string>
+    <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing audio</string>
 
     <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
     <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
index 8979ef1..12d881b 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
@@ -25,7 +25,11 @@
 import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
 import android.content.Context
 import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.Canvas
 import android.graphics.Insets
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
 import android.hardware.biometrics.BiometricManager.Authenticators
 import android.hardware.biometrics.PromptInfo
 import android.hardware.biometrics.SensorPropertiesInternal
@@ -122,4 +126,26 @@
         return windowMetrics?.windowInsets?.getInsets(WindowInsets.Type.navigationBars())
             ?: Insets.NONE
     }
+
+    /** Converts `drawable` to a [Bitmap]. */
+    @JvmStatic
+    fun Drawable?.toBitmap(): Bitmap? {
+        if (this == null) {
+            return null
+        }
+        if (this is BitmapDrawable) {
+            return bitmap
+        }
+        val bitmap: Bitmap =
+            if (intrinsicWidth <= 0 || intrinsicHeight <= 0) {
+                Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+                // Single color bitmap will be created of 1x1 pixel
+            } else {
+                Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888)
+            }
+        val canvas = Canvas(bitmap)
+        setBounds(0, 0, canvas.width, canvas.height)
+        draw(canvas)
+        return bitmap
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
index 0bd6d6e..3c4c003 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/FullscreenMagnificationController.java
@@ -30,7 +30,12 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.Handler;
+import android.util.Log;
 import android.view.AttachedSurfaceControl;
+import android.view.Display;
+import android.view.IRotationWatcher;
+import android.view.IWindowManager;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
@@ -46,15 +51,18 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.res.R;
+import com.android.systemui.util.leak.RotationUtils;
 
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
 
 class FullscreenMagnificationController implements ComponentCallbacks {
 
+    private static final String TAG = "FullscreenMagnificationController";
     private final Context mContext;
     private final AccessibilityManager mAccessibilityManager;
     private final WindowManager mWindowManager;
+    private final IWindowManager mIWindowManager;
     private Supplier<SurfaceControlViewHost> mScvhSupplier;
     private SurfaceControlViewHost mSurfaceControlViewHost = null;
     private SurfaceControl mBorderSurfaceControl = null;
@@ -65,33 +73,50 @@
     private final int mDisplayId;
     private static final Region sEmptyRegion = new Region();
     private ValueAnimator mShowHideBorderAnimator;
+    private Handler mHandler;
     private Executor mExecutor;
     private boolean mFullscreenMagnificationActivated = false;
     private final Configuration mConfiguration;
+    private final Runnable mShowBorderRunnable = this::showBorderWithNullCheck;
+    private int mRotation;
+    private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() {
+        @Override
+        public void onRotationChanged(final int rotation) {
+            handleScreenRotation();
+        }
+    };
+    private final long mLongAnimationTimeMs;
 
     FullscreenMagnificationController(
             @UiContext Context context,
-            Executor executor,
+            @Main Handler handler,
+            @Main Executor executor,
             AccessibilityManager accessibilityManager,
             WindowManager windowManager,
+            IWindowManager iWindowManager,
             Supplier<SurfaceControlViewHost> scvhSupplier) {
-        this(context, executor, accessibilityManager, windowManager, scvhSupplier,
-                new SurfaceControl.Transaction(), createNullTargetObjectAnimator(context));
+        this(context, handler, executor, accessibilityManager,
+                windowManager, iWindowManager, scvhSupplier,
+                new SurfaceControl.Transaction(), null);
     }
 
     @VisibleForTesting
     FullscreenMagnificationController(
             @UiContext Context context,
+            @Main Handler handler,
             @Main Executor executor,
             AccessibilityManager accessibilityManager,
             WindowManager windowManager,
+            IWindowManager iWindowManager,
             Supplier<SurfaceControlViewHost> scvhSupplier,
             SurfaceControl.Transaction transaction,
             ValueAnimator valueAnimator) {
         mContext = context;
+        mHandler = handler;
         mExecutor = executor;
         mAccessibilityManager = accessibilityManager;
         mWindowManager = windowManager;
+        mIWindowManager = iWindowManager;
         mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
         mTransaction = transaction;
         mScvhSupplier = scvhSupplier;
@@ -101,7 +126,10 @@
                 R.dimen.magnifier_border_width_fullscreen);
         mDisplayId = mContext.getDisplayId();
         mConfiguration = new Configuration(context.getResources().getConfiguration());
-        mShowHideBorderAnimator = valueAnimator;
+        mLongAnimationTimeMs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_longAnimTime);
+        mShowHideBorderAnimator = (valueAnimator == null)
+                ? createNullTargetObjectAnimator() : valueAnimator;
         mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
@@ -114,15 +142,13 @@
         });
     }
 
-    private static ValueAnimator createNullTargetObjectAnimator(Context context) {
+    private ValueAnimator createNullTargetObjectAnimator() {
         final ValueAnimator valueAnimator =
                 ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
         Interpolator interpolator = new AccelerateDecelerateInterpolator();
-        final long longAnimationDuration = context.getResources().getInteger(
-                com.android.internal.R.integer.config_longAnimTime);
 
         valueAnimator.setInterpolator(interpolator);
-        valueAnimator.setDuration(longAnimationDuration);
+        valueAnimator.setDuration(mLongAnimationTimeMs);
         return valueAnimator;
     }
 
@@ -149,7 +175,11 @@
      */
     @UiThread
     private void removeFullscreenMagnificationBorder() {
+        if (mHandler.hasCallbacks(mShowBorderRunnable)) {
+            mHandler.removeCallbacks(mShowBorderRunnable);
+        }
         mContext.unregisterComponentCallbacks(this);
+
         mShowHideBorderAnimator.reverse();
     }
 
@@ -161,6 +191,11 @@
 
         if (mFullscreenBorder != null) {
             mFullscreenBorder = null;
+            try {
+                mIWindowManager.removeRotationWatcher(mRotationWatcher);
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to remove rotation watcher", e);
+            }
         }
     }
 
@@ -186,6 +221,11 @@
             mSurfaceControlViewHost = mScvhSupplier.get();
             mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
             mBorderSurfaceControl = mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
+            try {
+                mIWindowManager.watchRotation(mRotationWatcher, Display.DEFAULT_DISPLAY);
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to register rotation watcher", e);
+            }
         }
 
         mTransaction
@@ -256,11 +296,55 @@
             reCreateWindow = true;
         }
 
-        if (mFullscreenBorder != null && reCreateWindow) {
+        if (mFullscreenBorder == null) {
+            return;
+        }
+
+        if (reCreateWindow) {
             final int newWidth = mWindowBounds.width() + 2 * mBorderOffset;
             final int newHeight = mWindowBounds.height() + 2 * mBorderOffset;
             mSurfaceControlViewHost.relayout(newWidth, newHeight);
         }
+
+        // Rotating from Landscape to ReverseLandscape will not trigger the config changes in
+        // CONFIG_SCREEN_SIZE and CONFIG_ORIENTATION. Therefore, we would like to check the device
+        // rotation separately.
+        // Since there's a possibility that {@link onConfigurationChanged} comes before
+        // {@link onRotationChanged}, we would like to handle screen rotation in either case that
+        // happens earlier.
+        int newRotation = RotationUtils.getRotation(mContext);
+        if (newRotation != mRotation) {
+            mRotation = newRotation;
+            handleScreenRotation();
+        }
+    }
+
+    private boolean isActivated() {
+        return mFullscreenBorder != null;
+    }
+
+    private void handleScreenRotation() {
+        if (!isActivated()) {
+            return;
+        }
+
+        if (mHandler.hasCallbacks(mShowBorderRunnable)) {
+            mHandler.removeCallbacks(mShowBorderRunnable);
+        }
+
+        // We hide the border immediately as early as possible to beat the redrawing of window
+        // in response to the orientation change so users won't see a weird shape border.
+        mHandler.postAtFrontOfQueue(() -> {
+            mFullscreenBorder.setAlpha(0f);
+        });
+
+        mHandler.postDelayed(mShowBorderRunnable, mLongAnimationTimeMs);
+    }
+
+    private void showBorderWithNullCheck() {
+        if (mShowHideBorderAnimator != null) {
+            mShowHideBorderAnimator.start();
+        }
     }
 
     private void updateDimensions() {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
index 35c2024..e22a4e4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/Magnification.java
@@ -34,6 +34,7 @@
 import android.os.Message;
 import android.util.SparseArray;
 import android.view.Display;
+import android.view.IWindowManager;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.WindowManager;
@@ -148,13 +149,19 @@
             DisplayIdIndexSupplier<FullscreenMagnificationController> {
 
         private final Context mContext;
+        private final Handler mHandler;
         private final Executor mExecutor;
+        private final IWindowManager mIWindowManager;
 
-        FullscreenMagnificationControllerSupplier(Context context, DisplayManager displayManager,
-                Executor executor) {
+        FullscreenMagnificationControllerSupplier(Context context,
+                DisplayManager displayManager,
+                Handler handler,
+                Executor executor, IWindowManager iWindowManager) {
             super(displayManager);
             mContext = context;
+            mHandler = handler;
             mExecutor = executor;
+            mIWindowManager = iWindowManager;
         }
 
         @Override
@@ -166,9 +173,11 @@
             windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
             return new FullscreenMagnificationController(
                     windowContext,
+                    mHandler,
                     mExecutor,
                     windowContext.getSystemService(AccessibilityManager.class),
                     windowContext.getSystemService(WindowManager.class),
+                    mIWindowManager,
                     scvhSupplier);
         }
     }
@@ -211,14 +220,16 @@
     DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;
 
     @Inject
-    public Magnification(Context context, @Main Handler mainHandler, @Main Executor executor,
+    public Magnification(Context context,
+            @Main Handler mainHandler, @Main Executor executor,
             CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
             SysUiState sysUiState, OverviewProxyService overviewProxyService,
             SecureSettings secureSettings, DisplayTracker displayTracker,
-            DisplayManager displayManager, AccessibilityLogger a11yLogger) {
+            DisplayManager displayManager, AccessibilityLogger a11yLogger,
+            IWindowManager iWindowManager) {
         this(context, mainHandler.getLooper(), executor, commandQueue,
                 modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
-                displayTracker, displayManager, a11yLogger);
+                displayTracker, displayManager, a11yLogger, iWindowManager);
     }
 
     @VisibleForTesting
@@ -226,7 +237,8 @@
             CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
             SysUiState sysUiState, OverviewProxyService overviewProxyService,
             SecureSettings secureSettings, DisplayTracker displayTracker,
-            DisplayManager displayManager, AccessibilityLogger a11yLogger) {
+            DisplayManager displayManager, AccessibilityLogger a11yLogger,
+            IWindowManager iWindowManager) {
         mContext = context;
         mHandler = new Handler(looper) {
             @Override
@@ -248,7 +260,7 @@
                 mHandler, mWindowMagnifierCallback,
                 displayManager, sysUiState, secureSettings);
         mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
-                context, displayManager, mExecutor);
+                context, displayManager, mHandler, mExecutor, iWindowManager);
         mMagnificationSettingsSupplier = new SettingsSupplier(context,
                 mMagnificationSettingsControllerCallback, displayManager, secureSettings);
 
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
index ea00398..b0314d8 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/ambient/dagger/AmbientModule.kt
@@ -16,11 +16,19 @@
 
 package com.android.systemui.ambient.dagger
 
+import com.android.systemui.ambient.statusbar.dagger.AmbientStatusBarComponent
 import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
 import com.android.systemui.ambient.touch.dagger.InputSessionComponent
 import dagger.Module
 
-@Module(subcomponents = [AmbientTouchComponent::class, InputSessionComponent::class])
+@Module(
+    subcomponents =
+        [
+            AmbientStatusBarComponent::class,
+            AmbientTouchComponent::class,
+            InputSessionComponent::class,
+        ]
+)
 interface AmbientModule {
     companion object {
         const val TOUCH_HANDLERS = "touch_handlers"
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/statusbar/dagger/AmbientStatusBarComponent.kt b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/dagger/AmbientStatusBarComponent.kt
new file mode 100644
index 0000000..8ad4d00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/dagger/AmbientStatusBarComponent.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.ambient.statusbar.dagger
+
+import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarView
+import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarViewController
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+/**
+ * [AmbientStatusBarComponent] can be used for displaying a status bar over ambient surfaces like
+ * the dream or communal hub.
+ */
+@Subcomponent
+interface AmbientStatusBarComponent {
+    @Subcomponent.Factory
+    interface Factory {
+        fun create(
+            @BindsInstance view: AmbientStatusBarView,
+        ): AmbientStatusBarComponent
+    }
+
+    /** Builds a [AmbientStatusBarViewController] */
+    fun getController(): AmbientStatusBarViewController
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
rename to packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
index 8e77079..aa96231 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams;
+package com.android.systemui.ambient.statusbar.ui;
 
 import android.annotation.IntDef;
 import android.annotation.Nullable;
@@ -39,10 +39,10 @@
 import java.util.Objects;
 
 /**
- * {@link DreamOverlayStatusBarView} is the view responsible for displaying the status bar in a
+ * {@link AmbientStatusBarView} is the view responsible for displaying the status bar in a
  * dream. The status bar displays conditional status icons such as "priority mode" and "no wifi".
  */
-public class DreamOverlayStatusBarView extends ConstraintLayout {
+public class AmbientStatusBarView extends ConstraintLayout {
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = { "STATUS_ICON_" }, value = {
@@ -76,20 +76,20 @@
     private static final float KEY_SHADOW_ALPHA = 0.35f;
     private static final float AMBIENT_SHADOW_ALPHA = 0.4f;
 
-    public DreamOverlayStatusBarView(Context context) {
+    public AmbientStatusBarView(Context context) {
         this(context, null);
     }
 
-    public DreamOverlayStatusBarView(Context context, AttributeSet attrs) {
+    public AmbientStatusBarView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public DreamOverlayStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
+    public AmbientStatusBarView(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
         mContext = context;
     }
 
-    public DreamOverlayStatusBarView(
+    public AmbientStatusBarView(
             Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
rename to packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
index da72a56..a242d5a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/statusbar/ui/AmbientStatusBarViewController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.dreams;
+package com.android.systemui.ambient.statusbar.ui;
 
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
@@ -31,20 +31,22 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamLogger;
+import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider;
 import com.android.systemui.dreams.DreamOverlayStatusBarItemsProvider.StatusBarItem;
-import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.dagger.DreamLog;
 import com.android.systemui.res.R;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.CrossFadeHelper;
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor;
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.statusbar.policy.NextAlarmController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
-import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.time.DateFormatUtil;
 
@@ -59,13 +61,11 @@
 import javax.inject.Inject;
 
 /**
- * View controller for {@link DreamOverlayStatusBarView}.
+ * View controller for {@link AmbientStatusBarView}.
  */
-@DreamOverlayComponent.DreamOverlayScope
-public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+public class AmbientStatusBarViewController extends ViewController<AmbientStatusBarView> {
     private static final String TAG = "DreamStatusBarCtrl";
 
-    private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
     private final NextAlarmController mNextAlarmController;
     private final AlarmManager mAlarmManager;
     private final Resources mResources;
@@ -76,7 +76,7 @@
     private final ZenModeController mZenModeController;
     private final DreamOverlayStateController mDreamOverlayStateController;
     private final UserTracker mUserTracker;
-    private final WifiRepository mWifiRepository;
+    private final WifiInteractor mWifiInteractor;
     private final StatusBarWindowStateController mStatusBarWindowStateController;
     private final DreamOverlayStatusBarItemsProvider mStatusBarItemsProvider;
     private final Executor mMainExecutor;
@@ -115,7 +115,7 @@
 
     private final DreamOverlayNotificationCountProvider.Callback mNotificationCountCallback =
             notificationCount -> showIcon(
-                    DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS,
+                    AmbientStatusBarView.STATUS_ICON_NOTIFICATIONS,
                     notificationCount > 0,
                     notificationCount > 0
                             ? buildNotificationsContentDescription(notificationCount)
@@ -125,11 +125,10 @@
             this::onStatusBarItemsChanged;
 
     @Inject
-    public DreamOverlayStatusBarViewController(
-            DreamOverlayStatusBarView view,
+    public AmbientStatusBarViewController(
+            AmbientStatusBarView view,
             @Main Resources resources,
             @Main Executor mainExecutor,
-            TouchInsetManager.TouchInsetSession touchInsetSession,
             AlarmManager alarmManager,
             NextAlarmController nextAlarmController,
             DateFormatUtil dateFormatUtil,
@@ -140,12 +139,11 @@
             DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
             DreamOverlayStateController dreamOverlayStateController,
             UserTracker userTracker,
-            WifiRepository wifiRepository,
+            WifiInteractor wifiInteractor,
             @DreamLog LogBuffer logBuffer) {
         super(view);
         mResources = resources;
         mMainExecutor = mainExecutor;
-        mTouchInsetSession = touchInsetSession;
         mAlarmManager = alarmManager;
         mNextAlarmController = nextAlarmController;
         mDateFormatUtil = dateFormatUtil;
@@ -156,7 +154,7 @@
         mZenModeController = zenModeController;
         mDreamOverlayStateController = dreamOverlayStateController;
         mUserTracker = userTracker;
-        mWifiRepository = wifiRepository;
+        mWifiInteractor = wifiInteractor;
         mLogger = new DreamLogger(logBuffer, TAG);
 
         // Register to receive show/hide updates for the system status bar. Our custom status bar
@@ -170,7 +168,7 @@
 
         collectFlow(
                 mView,
-                mWifiRepository.getWifiNetwork(),
+                mWifiInteractor.getWifiNetwork(),
                 network -> updateWifiUnavailableStatusIcon(
                         network instanceof WifiNetworkModel.Active));
 
@@ -202,7 +200,6 @@
         mView.removeAllExtraStatusBarItemViews();
         mDreamOverlayStateController.setDreamOverlayStatusBarVisible(false);
         mDreamOverlayStateController.removeCallback(mDreamOverlayStateCallback);
-        mTouchInsetSession.clear();
 
         mIsAttached = false;
     }
@@ -212,7 +209,7 @@
      *
      * No-op if the dream overlay status bar should not be shown.
      */
-    protected void setFadeAmount(float fadeAmount, boolean fadingOut) {
+    public void setFadeAmount(float fadeAmount, boolean fadingOut) {
         updateVisibility();
 
         if (mView.getVisibility() != View.VISIBLE) {
@@ -240,7 +237,7 @@
 
     @VisibleForTesting
     void updateWifiUnavailableStatusIcon(boolean available) {
-        showIcon(DreamOverlayStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available,
+        showIcon(AmbientStatusBarView.STATUS_ICON_WIFI_UNAVAILABLE, !available,
                 R.string.wifi_unavailable_dream_overlay_content_description);
     }
 
@@ -249,13 +246,13 @@
                 mAlarmManager.getNextAlarmClock(mUserTracker.getUserId());
         final boolean hasAlarm = alarm != null && alarm.getTriggerTime() > 0;
         showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_ALARM_SET,
+                AmbientStatusBarView.STATUS_ICON_ALARM_SET,
                 hasAlarm,
                 hasAlarm ? buildAlarmContentDescription(alarm) : null);
     }
 
     private void updateAssistantAttentionIcon() {
-        showIcon(DreamOverlayStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
+        showIcon(AmbientStatusBarView.STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE,
                 mDreamOverlayStateController.hasAssistantAttention(),
                 R.string.assistant_attention_content_description);
     }
@@ -284,17 +281,17 @@
                 .isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE);
         final boolean cameraBlocked = mSensorPrivacyController
                 .isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA);
-        @DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL;
+        @AmbientStatusBarView.StatusIconType int iconType = Resources.ID_NULL;
         showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED,
+                AmbientStatusBarView.STATUS_ICON_CAMERA_DISABLED,
                 !micBlocked && cameraBlocked,
                 R.string.camera_blocked_dream_overlay_content_description);
         showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED,
+                AmbientStatusBarView.STATUS_ICON_MIC_DISABLED,
                 micBlocked && !cameraBlocked,
                 R.string.microphone_blocked_dream_overlay_content_description);
         showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED,
+                AmbientStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED,
                 micBlocked && cameraBlocked,
                 R.string.camera_and_microphone_blocked_dream_overlay_content_description);
     }
@@ -308,24 +305,24 @@
 
     private void updatePriorityModeStatusIcon() {
         showIcon(
-                DreamOverlayStatusBarView.STATUS_ICON_PRIORITY_MODE_ON,
+                AmbientStatusBarView.STATUS_ICON_PRIORITY_MODE_ON,
                 mZenModeController.getZen() != Settings.Global.ZEN_MODE_OFF,
                 R.string.priority_mode_dream_overlay_content_description);
     }
 
-    private void showIcon(@DreamOverlayStatusBarView.StatusIconType int iconType, boolean show,
+    private void showIcon(@AmbientStatusBarView.StatusIconType int iconType, boolean show,
             int contentDescriptionResId) {
         showIcon(iconType, show, mResources.getString(contentDescriptionResId));
     }
 
     private void showIcon(
-            @DreamOverlayStatusBarView.StatusIconType int iconType,
+            @AmbientStatusBarView.StatusIconType int iconType,
             boolean show,
             @Nullable String contentDescription) {
         mMainExecutor.execute(() -> {
             if (mIsAttached) {
                 mLogger.logShowOrHideStatusBarItem(
-                        show, DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
+                        show, AmbientStatusBarView.getLoggableStatusIconType(iconType));
                 mView.showIcon(iconType, show, contentDescription);
             }
         });
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
index 9b14d6f..2fa4a89 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt
@@ -139,9 +139,10 @@
             }
             .stateIn(
                 backgroundScope,
-                started = SharingStarted.WhileSubscribed(),
+                started = SharingStarted.Eagerly,
                 initialValue = false,
             )
+
     private fun dpiFromPx(size: Float, densityDpi: Int): Float {
         val densityRatio = densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT
         return size / densityRatio
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 4f96c1e..348b423 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -40,8 +40,7 @@
             operationInfo = operationInfo,
             showEmergencyCallButton = info.isShowEmergencyCallButton
         ) {
-        val logoRes: Int = info.logoRes
-        val logoBitmap: Bitmap? = info.logoBitmap
+        val logoBitmap: Bitmap? = info.logo
         val logoDescription: String? = info.logoDescription
         val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
         val componentNameForConfirmDeviceCredentialActivity: ComponentName? =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index a39a74f..d95a893 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -504,7 +504,6 @@
             .map {
                 when {
                     !(customBiometricPrompt() && constraintBp()) || it == null -> null
-                    it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
                     it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
                     else -> context.getUserBadgedIcon(it, iconProvider, activityTaskManager)
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/CommonSystemUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/CommonSystemUIUnfoldModule.kt
new file mode 100644
index 0000000..a91ce16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dagger/CommonSystemUIUnfoldModule.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.dagger
+
+import com.android.systemui.unfold.SysUIUnfoldComponent
+import com.android.systemui.unfold.SysUIUnfoldModule.BoundFromSysUiUnfoldModule
+import dagger.BindsOptionalOf
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+import kotlin.jvm.optionals.getOrElse
+
+
+/**
+ * Module for foldable-related classes that is available in all SystemUI variants.
+ * Provides `Optional<SysUIUnfoldComponent>` which is present when the device is a foldable
+ * device that has fold/unfold animation enabled.
+ */
+@Module
+abstract class CommonSystemUIUnfoldModule {
+
+    /* Note this will be injected as @BoundFromSysUiUnfoldModule Optional<Optional<...>> */
+    @BindsOptionalOf
+    @BoundFromSysUiUnfoldModule
+    abstract fun optionalSysUiUnfoldComponent(): Optional<SysUIUnfoldComponent>
+
+    companion object {
+        @Provides
+        @SysUISingleton
+        fun sysUiUnfoldComponent(
+            /**
+             * This will be empty when [com.android.systemui.unfold.SysUIUnfoldModule] is not part
+             * of the graph, and contain the optional when it is.
+             */
+            @BoundFromSysUiUnfoldModule
+            optionalOfOptional: Optional<Optional<SysUIUnfoldComponent>>
+        ): Optional<SysUIUnfoldComponent> = optionalOfOptional.getOrElse { Optional.empty() }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
index a431a59..b71af69 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java
@@ -19,6 +19,7 @@
 import com.android.systemui.keyguard.CustomizationProvider;
 import com.android.systemui.statusbar.NotificationInsetsModule;
 import com.android.systemui.statusbar.QsFrameTranslateModule;
+import com.android.systemui.unfold.SysUIUnfoldModule;
 
 import dagger.Subcomponent;
 
@@ -34,6 +35,7 @@
         SystemUIBinder.class,
         SystemUIModule.class,
         SystemUICoreStartableModule.class,
+        SysUIUnfoldModule.class,
         ReferenceSystemUIModule.class})
 public interface ReferenceSysUIComponent extends SysUIComponent {
 
@@ -51,3 +53,4 @@
      */
     void inject(CustomizationProvider customizationProvider);
 }
+
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 2ebb94f..a7ff3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -143,7 +143,6 @@
 import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
 import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule;
 import com.android.systemui.tuner.dagger.TunerModule;
-import com.android.systemui.unfold.SysUIUnfoldModule;
 import com.android.systemui.user.UserModule;
 import com.android.systemui.user.domain.UserDomainLayerModule;
 import com.android.systemui.util.EventLogModule;
@@ -254,7 +253,7 @@
         SystemPropertiesFlagsModule.class,
         SysUIConcurrencyModule.class,
         SysUICoroutinesModule.class,
-        SysUIUnfoldModule.class,
+        CommonSystemUIUnfoldModule.class,
         TelephonyRepositoryModule.class,
         TemporaryDisplayModule.class,
         TunerModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index f860893..3294c81 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -27,11 +27,13 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
 import com.android.dream.lowlight.util.TruncatedInterpolator
+import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarViewController
 import com.android.systemui.complication.ComplicationHostViewController
 import com.android.systemui.complication.ComplicationLayoutParams
 import com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM
 import com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP
 import com.android.systemui.complication.ComplicationLayoutParams.Position
+import com.android.systemui.dreams.dagger.DreamOverlayComponent.DreamOverlayScope
 import com.android.systemui.dreams.dagger.DreamOverlayModule
 import com.android.systemui.dreams.ui.viewmodel.DreamViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -45,12 +47,13 @@
 import kotlinx.coroutines.launch
 
 /** Controller for dream overlay animations. */
+@DreamOverlayScope
 class DreamOverlayAnimationsController
 @Inject
 constructor(
     private val mBlurUtils: BlurUtils,
     private val mComplicationHostViewController: ComplicationHostViewController,
-    private val mStatusBarViewController: DreamOverlayStatusBarViewController,
+    private val mStatusBarViewController: AmbientStatusBarViewController,
     private val mOverlayStateController: DreamOverlayStateController,
     @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
     private val dreamViewModel: DreamViewModel,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 1e725eb..245def8 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -40,6 +40,7 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.dream.lowlight.LowLightTransitionCoordinator;
+import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarViewController;
 import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback;
@@ -55,6 +56,7 @@
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
 import com.android.systemui.statusbar.BlurUtils;
+import com.android.systemui.touch.TouchInsetManager;
 import com.android.systemui.util.ViewController;
 
 import kotlinx.coroutines.CoroutineDispatcher;
@@ -72,10 +74,12 @@
 public class DreamOverlayContainerViewController extends
         ViewController<DreamOverlayContainerView> implements
         LowLightTransitionCoordinator.LowLightEnterListener {
-    private final DreamOverlayStatusBarViewController mStatusBarViewController;
+    private final AmbientStatusBarViewController mStatusBarViewController;
+    private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
     private final BlurUtils mBlurUtils;
     private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
     private final DreamOverlayStateController mStateController;
+
     private final LowLightTransitionCoordinator mLowLightTransitionCoordinator;
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final ShadeInteractor mShadeInteractor;
@@ -188,8 +192,9 @@
             ComplicationHostViewController complicationHostViewController,
             @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView,
             @Named(DreamOverlayModule.HUB_GESTURE_INDICATOR_VIEW) View hubGestureIndicatorView,
-            DreamOverlayStatusBarViewController statusBarViewController,
+            AmbientStatusBarViewController statusBarViewController,
             LowLightTransitionCoordinator lowLightTransitionCoordinator,
+            TouchInsetManager.TouchInsetSession touchInsetSession,
             BlurUtils blurUtils,
             @Main Handler handler,
             @Background CoroutineDispatcher backgroundDispatcher,
@@ -209,6 +214,7 @@
         super(containerView);
         mDreamOverlayContentView = contentView;
         mStatusBarViewController = statusBarViewController;
+        mTouchInsetSession = touchInsetSession;
         mBlurUtils = blurUtils;
         mDreamOverlayAnimationsController = animationsController;
         mStateController = stateController;
@@ -294,6 +300,7 @@
         mHandler.removeCallbacksAndMessages(null);
         mPrimaryBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
         mBouncerlessScrimController.removeCallback(mBouncerlessExpansionCallback);
+        mTouchInsetSession.clear();
 
         mDreamOverlayAnimationsController.cancelAnimations();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java
index 409b196..0833518 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayDotImageView.java
@@ -31,12 +31,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarView;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.AlphaOptimizedImageView;
 
 /**
  * An {@link AlphaOptimizedImageView} that is responsible for rendering a dot. Used by
- * {@link DreamOverlayStatusBarView}.
+ * {@link AmbientStatusBarView}.
  */
 public class DreamOverlayDotImageView extends AlphaOptimizedImageView {
     private final @ColorInt int mDotColor;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 789b7f8..76fcabd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -25,9 +25,11 @@
 import androidx.lifecycle.LifecycleOwner;
 
 import com.android.internal.util.Preconditions;
+import com.android.systemui.ambient.statusbar.dagger.AmbientStatusBarComponent;
+import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarView;
+import com.android.systemui.ambient.statusbar.ui.AmbientStatusBarViewController;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.DreamOverlayContainerView;
-import com.android.systemui.dreams.DreamOverlayStatusBarView;
 import com.android.systemui.res.R;
 import com.android.systemui.touch.TouchInsetManager;
 
@@ -60,7 +62,7 @@
     public static DreamOverlayContainerView providesDreamOverlayContainerView(
             LayoutInflater layoutInflater) {
         return Preconditions.checkNotNull((DreamOverlayContainerView)
-                layoutInflater.inflate(R.layout.dream_overlay_container, null),
+                        layoutInflater.inflate(R.layout.dream_overlay_container, null),
                 "R.layout.dream_layout_container could not be properly inflated");
     }
 
@@ -95,13 +97,23 @@
     /** */
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
-    public static DreamOverlayStatusBarView providesDreamOverlayStatusBarView(
+    public static AmbientStatusBarView providesDreamOverlayStatusBarView(
             DreamOverlayContainerView view) {
         return Preconditions.checkNotNull(view.findViewById(R.id.dream_overlay_status_bar),
                 "R.id.status_bar must not be null");
     }
 
-    /** */
+    /**
+     * Provides the view controller for the {@link AmbientStatusBarView}
+     */
+    @Provides
+    @DreamOverlayComponent.DreamOverlayScope
+    public static AmbientStatusBarViewController providesStatusBarViewController(
+            AmbientStatusBarView view, AmbientStatusBarComponent.Factory factory) {
+        return factory.create(view).getController();
+    }
+
+    /**  */
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
     @Named(MAX_BURN_IN_OFFSET)
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index ea8d7d7..be4c903 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -17,21 +17,19 @@
 package com.android.systemui.haptics.qs
 
 import android.os.VibrationEffect
-import android.view.View
 import androidx.annotation.VisibleForTesting
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.animation.Expandable
+import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
 
 /**
  * A class that handles the long press visuo-haptic effect for a QS tile.
  *
- * The class is also a [View.OnTouchListener] to handle the touch events, clicks and long-press
- * gestures of the tile. The class also provides a [State] tha can be used to determine the current
- * state of the long press effect.
+ * The class can contain references to a [QSTile] and an [Expandable] to perform clicks and
+ * long-clicks on the tile. The class also provides a [State] tha can be used to determine the
+ * current state of the long press effect.
  *
  * @property[vibratorHelper] The [VibratorHelper] to deliver haptic effects.
  * @property[effectDuration] The duration of the effect in ms.
@@ -41,7 +39,7 @@
 @Inject
 constructor(
     private val vibratorHelper: VibratorHelper?,
-    keyguardInteractor: KeyguardInteractor,
+    private val keyguardStateController: KeyguardStateController,
 ) {
 
     var effectDuration = 0
@@ -51,19 +49,12 @@
     var state = State.IDLE
         private set
 
-    /** Flow for view control and action */
-    private val _postedActionType = MutableStateFlow<ActionType?>(null)
-    val actionType: Flow<ActionType?> =
-        combine(
-            _postedActionType,
-            keyguardInteractor.isKeyguardDismissible,
-        ) { action, isDismissible ->
-            if (!isDismissible && action == ActionType.LONG_PRESS) {
-                ActionType.RESET_AND_LONG_PRESS
-            } else {
-                action
-            }
-        }
+    /** Callback object for effect actions */
+    var callback: Callback? = null
+
+    /** The [QSTile] and [Expandable] used to perform a long-click and click actions */
+    var qsTile: QSTile? = null
+    var expandable: Expandable? = null
 
     /** Haptic effects */
     private val durations =
@@ -106,33 +97,24 @@
             State.IDLE -> {
                 setState(State.TIMEOUT_WAIT)
             }
-            State.RUNNING_BACKWARDS -> _postedActionType.value = ActionType.CANCEL_ANIMATOR
+            State.RUNNING_BACKWARDS -> callback?.onCancelAnimator()
             else -> {}
         }
     }
 
     fun handleActionUp() {
-        when (state) {
-            State.TIMEOUT_WAIT -> {
-                _postedActionType.value = ActionType.CLICK
-                setState(State.IDLE)
-            }
-            State.RUNNING_FORWARD -> {
-                _postedActionType.value = ActionType.REVERSE_ANIMATOR
-                setState(State.RUNNING_BACKWARDS)
-            }
-            else -> {}
+        if (state == State.RUNNING_FORWARD) {
+            setState(State.RUNNING_BACKWARDS)
+            callback?.onReverseAnimator()
         }
     }
 
     fun handleActionCancel() {
         when (state) {
-            State.TIMEOUT_WAIT -> {
-                setState(State.IDLE)
-            }
+            State.TIMEOUT_WAIT -> setState(State.IDLE)
             State.RUNNING_FORWARD -> {
-                _postedActionType.value = ActionType.REVERSE_ANIMATOR
                 setState(State.RUNNING_BACKWARDS)
+                callback?.onReverseAnimator()
             }
             else -> {}
         }
@@ -146,8 +128,15 @@
     /** This function is called both when an animator completes or gets cancelled */
     fun handleAnimationComplete() {
         if (state == State.RUNNING_FORWARD) {
+            setState(State.IDLE)
             vibrate(snapEffect)
-            _postedActionType.value = ActionType.LONG_PRESS
+            if (keyguardStateController.isUnlocked) {
+                callback?.onPrepareForLaunch()
+                qsTile?.longClick(expandable)
+            } else {
+                callback?.onResetProperties()
+                qsTile?.longClick(expandable)
+            }
         }
         if (state != State.TIMEOUT_WAIT) {
             // This will happen if the animator did not finish by being cancelled
@@ -161,12 +150,19 @@
 
     fun handleTimeoutComplete() {
         if (state == State.TIMEOUT_WAIT) {
-            _postedActionType.value = ActionType.START_ANIMATOR
+            callback?.onStartAnimator()
         }
     }
 
-    fun clearActionType() {
-        _postedActionType.value = null
+    fun onTileClick(): Boolean {
+        if (state == State.TIMEOUT_WAIT) {
+            setState(State.IDLE)
+            qsTile?.let {
+                it.click(expandable)
+                return true
+            }
+        }
+        return false
     }
 
     /**
@@ -200,13 +196,22 @@
         RUNNING_BACKWARDS, /* The effect was interrupted and is now running backwards */
     }
 
-    /* A type of action to perform on the view depending on the effect's state and logic */
-    enum class ActionType {
-        CLICK,
-        LONG_PRESS,
-        RESET_AND_LONG_PRESS,
-        START_ANIMATOR,
-        REVERSE_ANIMATOR,
-        CANCEL_ANIMATOR,
+    /** Callbacks to notify view and animator actions */
+    interface Callback {
+
+        /** Prepare for an activity launch */
+        fun onPrepareForLaunch()
+
+        /** Reset the tile visual properties */
+        fun onResetProperties()
+
+        /** Start the effect animator */
+        fun onStartAnimator()
+
+        /** Reverse the effect animator */
+        fun onReverseAnimator()
+
+        /** Cancel the effect animator */
+        fun onCancelAnimator()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
deleted file mode 100644
index 4875f48..0000000
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.haptics.qs
-
-import android.animation.ValueAnimator
-import android.annotation.SuppressLint
-import android.view.MotionEvent
-import android.view.ViewConfiguration
-import android.view.animation.AccelerateDecelerateInterpolator
-import androidx.core.animation.doOnCancel
-import androidx.core.animation.doOnEnd
-import androidx.core.animation.doOnStart
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.app.tracing.coroutines.launch
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.qs.tileimpl.QSTileViewImpl
-import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.flow.filterNotNull
-
-object QSLongPressEffectViewBinder {
-
-    fun bind(
-        tile: QSTileViewImpl,
-        qsLongPressEffect: QSLongPressEffect?,
-        tileSpec: String?,
-    ): DisposableHandle? {
-        if (qsLongPressEffect == null) return null
-
-        // Set the touch listener as the long-press effect
-        setTouchListener(tile, qsLongPressEffect)
-
-        return tile.repeatWhenAttached {
-            repeatOnLifecycle(Lifecycle.State.CREATED) {
-                // Action to perform
-                launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#action" }) {
-                    var effectAnimator: ValueAnimator? = null
-
-                    qsLongPressEffect.actionType.filterNotNull().collect { action ->
-                        when (action) {
-                            QSLongPressEffect.ActionType.CLICK -> {
-                                tile.performClick()
-                                qsLongPressEffect.clearActionType()
-                            }
-                            QSLongPressEffect.ActionType.LONG_PRESS -> {
-                                tile.prepareForLaunch()
-                                tile.performLongClick()
-                                qsLongPressEffect.clearActionType()
-                            }
-                            QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> {
-                                tile.resetLongPressEffectProperties()
-                                tile.performLongClick()
-                                qsLongPressEffect.clearActionType()
-                            }
-                            QSLongPressEffect.ActionType.START_ANIMATOR -> {
-                                if (effectAnimator?.isRunning != true) {
-                                    effectAnimator =
-                                        ValueAnimator.ofFloat(0f, 1f).apply {
-                                            this.duration =
-                                                qsLongPressEffect.effectDuration.toLong()
-                                            interpolator = AccelerateDecelerateInterpolator()
-
-                                            doOnStart { qsLongPressEffect.handleAnimationStart() }
-                                            addUpdateListener {
-                                                val value = animatedValue as Float
-                                                if (value == 0f) {
-                                                    tile.bringToFront()
-                                                } else {
-                                                    tile.updateLongPressEffectProperties(value)
-                                                }
-                                            }
-                                            doOnEnd { qsLongPressEffect.handleAnimationComplete() }
-                                            doOnCancel { qsLongPressEffect.handleAnimationCancel() }
-                                            start()
-                                        }
-                                }
-                            }
-                            QSLongPressEffect.ActionType.REVERSE_ANIMATOR -> {
-                                effectAnimator?.let {
-                                    val pausedProgress = it.animatedFraction
-                                    qsLongPressEffect.playReverseHaptics(pausedProgress)
-                                    it.reverse()
-                                }
-                            }
-                            QSLongPressEffect.ActionType.CANCEL_ANIMATOR -> {
-                                tile.resetLongPressEffectProperties()
-                                effectAnimator?.cancel()
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    @SuppressLint("ClickableViewAccessibility")
-    private fun setTouchListener(tile: QSTileViewImpl, longPressEffect: QSLongPressEffect?) {
-        tile.setOnTouchListener { _, event ->
-            when (event.actionMasked) {
-                MotionEvent.ACTION_DOWN -> {
-                    tile.postDelayed(
-                        { longPressEffect?.handleTimeoutComplete() },
-                        ViewConfiguration.getTapTimeout().toLong(),
-                    )
-                    longPressEffect?.handleActionDown()
-                }
-                MotionEvent.ACTION_UP -> longPressEffect?.handleActionUp()
-                MotionEvent.ACTION_CANCEL -> longPressEffect?.handleActionCancel()
-            }
-            true
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 02e48fc..10cfd6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -27,11 +27,13 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import com.android.systemui.util.kotlin.filterValuesNotNull
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -89,37 +91,36 @@
         isCommunalAvailable: Boolean,
         shadeMode: ShadeMode,
     ): Map<UserAction, UserActionResult> {
-        val shadeSceneKey =
+        val notifShadeSceneKey =
             UserActionResult(
-                toScene =
-                    if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
+                toScene = SceneFamilies.NotifShade,
                 transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split },
             )
 
-        val quickSettingsIfSingleShade =
-            if (shadeMode is ShadeMode.Single) UserActionResult(Scenes.QuickSettings)
-            else shadeSceneKey
-
         return mapOf(
                 Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable },
                 Swipe.Up to if (isDeviceUnlocked) Scenes.Gone else Scenes.Bouncer,
 
                 // Swiping down from the top edge goes to QS (or shade if in split shade mode).
-                swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade,
-                swipeDownFromTop(pointerCount = 2) to
-                    // TODO(b/338577208): Remove 'Dual' once we add Dual Shade invocation zones.
-                    if (shadeMode is ShadeMode.Dual) {
-                        UserActionResult(Scenes.QuickSettingsShade)
+                swipeDownFromTop(pointerCount = 1) to
+                    if (shadeMode is ShadeMode.Single) {
+                        UserActionResult(Scenes.QuickSettings)
                     } else {
-                        quickSettingsIfSingleShade
+                        notifShadeSceneKey
                     },
 
-                // Swiping down, not from the edge, always navigates to the shade scene.
-                swipeDown(pointerCount = 1) to shadeSceneKey,
-                swipeDown(pointerCount = 2) to shadeSceneKey,
+                // TODO(b/338577208): Remove once we add Dual Shade invocation zones.
+                swipeDownFromTop(pointerCount = 2) to
+                    UserActionResult(
+                        toScene = SceneFamilies.QuickSettings,
+                        transitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+                    ),
+
+                // Swiping down, not from the edge, always navigates to the notif shade scene.
+                swipeDown(pointerCount = 1) to notifShadeSceneKey,
+                swipeDown(pointerCount = 2) to notifShadeSceneKey,
             )
-            .filterValues { it != null }
-            .mapValues { checkNotNull(it.value) }
+            .filterValuesNotNull()
     }
 
     private fun swipeDownFromTop(pointerCount: Int): Swipe {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index 601d563..88a28bf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -36,6 +36,7 @@
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.traceSection
 import com.android.keyguard.KeyguardViewController
+import com.android.systemui.Flags.mediaControlsLockscreenShadeBugFix
 import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -483,8 +484,7 @@
             object : StatusBarStateController.StateListener {
                 override fun onStatePreChange(oldState: Int, newState: Int) {
                     // We're updating the location before the state change happens, since we want
-                    // the
-                    // location of the previous state to still be up to date when the animation
+                    // the location of the previous state to still be up to date when the animation
                     // starts
                     if (
                         newState == StatusBarState.SHADE_LOCKED &&
@@ -588,6 +588,17 @@
             }
         }
 
+        if (mediaControlsLockscreenShadeBugFix()) {
+            coroutineScope.launch {
+                shadeInteractor.shadeExpansion.collect { expansion ->
+                    if (expansion >= 1f || expansion <= 0f) {
+                        // Shade has fully expanded or collapsed: force transition amount update
+                        setTransitionToFullShadeAmount(expansion)
+                    }
+                }
+            }
+        }
+
         val settingsObserver: ContentObserver =
             object : ContentObserver(handler) {
                 override fun onChange(selfChange: Boolean, uri: Uri?) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index f3cc35ba..abc2b7f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -217,10 +217,13 @@
     private void setBrightnessViewMargin() {
         if (mBrightnessView != null) {
             MarginLayoutParams lp = (MarginLayoutParams) mBrightnessView.getLayoutParams();
+            // For Brightness Slider to extend its boundary to draw focus background
+            int offset = getResources()
+                    .getDimensionPixelSize(R.dimen.rounded_slider_boundary_offset);
             lp.topMargin = mContext.getResources()
-                    .getDimensionPixelSize(R.dimen.qs_brightness_margin_top);
+                    .getDimensionPixelSize(R.dimen.qs_brightness_margin_top) - offset;
             lp.bottomMargin = mContext.getResources()
-                    .getDimensionPixelSize(R.dimen.qs_brightness_margin_bottom);
+                    .getDimensionPixelSize(R.dimen.qs_brightness_margin_bottom) - offset;
             mBrightnessView.setLayoutParams(lp);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 1143c30..b1b67cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -19,6 +19,7 @@
 import android.animation.ArgbEvaluator
 import android.animation.PropertyValuesHolder
 import android.animation.ValueAnimator
+import android.annotation.SuppressLint
 import android.content.Context
 import android.content.res.ColorStateList
 import android.content.res.Configuration
@@ -37,27 +38,31 @@
 import android.util.TypedValue
 import android.view.Gravity
 import android.view.LayoutInflater
+import android.view.MotionEvent
 import android.view.View
+import android.view.ViewConfiguration
 import android.view.ViewGroup
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
+import android.view.animation.AccelerateDecelerateInterpolator
 import android.widget.Button
 import android.widget.ImageView
 import android.widget.LinearLayout
 import android.widget.Switch
 import android.widget.TextView
 import androidx.annotation.VisibleForTesting
+import androidx.core.animation.doOnCancel
+import androidx.core.animation.doOnEnd
+import androidx.core.animation.doOnStart
 import androidx.core.graphics.drawable.updateBounds
 import com.android.app.tracing.traceSection
 import com.android.settingslib.Utils
 import com.android.systemui.Flags
-import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress
 import com.android.systemui.FontSizeUtils
 import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.LaunchableView
 import com.android.systemui.animation.LaunchableViewDelegate
 import com.android.systemui.haptics.qs.QSLongPressEffect
-import com.android.systemui.haptics.qs.QSLongPressEffectViewBinder
 import com.android.systemui.plugins.qs.QSIconView
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.plugins.qs.QSTile.AdapterState
@@ -65,11 +70,13 @@
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSIconViewImpl.QS_ANIM_LENGTH
 import com.android.systemui.res.R
-import kotlinx.coroutines.DisposableHandle
 import java.util.Objects
 
 private const val TAG = "QSTileViewImpl"
-open class QSTileViewImpl @JvmOverloads constructor(
+
+open class QSTileViewImpl
+@JvmOverloads
+constructor(
     context: Context,
     private val collapsed: Boolean = false,
     private val longPressEffect: QSLongPressEffect? = null,
@@ -83,12 +90,9 @@
         private const val CHEVRON_NAME = "chevron"
         private const val OVERLAY_NAME = "overlay"
         const val UNAVAILABLE_ALPHA = 0.3f
-        @VisibleForTesting
-        internal const val TILE_STATE_RES_PREFIX = "tile_states_"
-        @VisibleForTesting
-        internal const val LONG_PRESS_EFFECT_WIDTH_SCALE = 1.1f
-        @VisibleForTesting
-        internal const val LONG_PRESS_EFFECT_HEIGHT_SCALE = 1.2f
+        @VisibleForTesting internal const val TILE_STATE_RES_PREFIX = "tile_states_"
+        @VisibleForTesting internal const val LONG_PRESS_EFFECT_WIDTH_SCALE = 1.1f
+        @VisibleForTesting internal const val LONG_PRESS_EFFECT_HEIGHT_SCALE = 1.2f
     }
 
     private val icon: QSIconViewImpl = QSIconViewImpl(context)
@@ -116,22 +120,25 @@
     private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive)
     private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled)
 
-    private val overlayColorActive = Utils.applyAlpha(
-        /* alpha= */ 0.11f,
-        Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive))
-    private val overlayColorInactive = Utils.applyAlpha(
-        /* alpha= */ 0.08f,
-        Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive))
+    private val overlayColorActive =
+        Utils.applyAlpha(
+            /* alpha= */ 0.11f,
+            Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)
+        )
+    private val overlayColorInactive =
+        Utils.applyAlpha(
+            /* alpha= */ 0.08f,
+            Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)
+        )
 
     private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive)
     private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive)
-    private val colorLabelUnavailable =
-        Utils.getColorAttrDefaultColor(context, R.attr.outline)
+    private val colorLabelUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.outline)
 
     private val colorSecondaryLabelActive =
         Utils.getColorAttrDefaultColor(context, R.attr.onShadeActiveVariant)
     private val colorSecondaryLabelInactive =
-            Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant)
+        Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant)
     private val colorSecondaryLabelUnavailable =
         Utils.getColorAttrDefaultColor(context, R.attr.outline)
 
@@ -143,9 +150,7 @@
     private lateinit var chevronView: ImageView
     private var mQsLogger: QSLogger? = null
 
-    /**
-     * Controls if tile background is set to a [RippleDrawable] see [setClickable]
-     */
+    /** Controls if tile background is set to a [RippleDrawable] see [setClickable] */
     protected var showRippleEffect = true
 
     private lateinit var qsTileBackground: RippleDrawable
@@ -157,20 +162,21 @@
     private var backgroundColor: Int = 0
     private var backgroundOverlayColor: Int = 0
 
-    private val singleAnimator: ValueAnimator = ValueAnimator().apply {
-        setDuration(QS_ANIM_LENGTH)
-        addUpdateListener { animation ->
-            setAllColors(
-                // These casts will throw an exception if some property is missing. We should
-                // always have all properties.
-                animation.getAnimatedValue(BACKGROUND_NAME) as Int,
-                animation.getAnimatedValue(LABEL_NAME) as Int,
-                animation.getAnimatedValue(SECONDARY_LABEL_NAME) as Int,
-                animation.getAnimatedValue(CHEVRON_NAME) as Int,
-                animation.getAnimatedValue(OVERLAY_NAME) as Int,
-            )
+    private val singleAnimator: ValueAnimator =
+        ValueAnimator().apply {
+            setDuration(QS_ANIM_LENGTH)
+            addUpdateListener { animation ->
+                setAllColors(
+                    // These casts will throw an exception if some property is missing. We should
+                    // always have all properties.
+                    animation.getAnimatedValue(BACKGROUND_NAME) as Int,
+                    animation.getAnimatedValue(LABEL_NAME) as Int,
+                    animation.getAnimatedValue(SECONDARY_LABEL_NAME) as Int,
+                    animation.getAnimatedValue(CHEVRON_NAME) as Int,
+                    animation.getAnimatedValue(OVERLAY_NAME) as Int,
+                )
+            }
         }
-    }
 
     private var accessibilityClass: String? = null
     private var stateDescriptionDeltas: CharSequence? = null
@@ -178,32 +184,37 @@
     private var tileState = false
     private var lastState = INVALID
     private var lastIconTint = 0
-    private val launchableViewDelegate = LaunchableViewDelegate(
-        this,
-        superSetVisibility = { super.setVisibility(it) },
-    )
+    private val launchableViewDelegate =
+        LaunchableViewDelegate(
+            this,
+            superSetVisibility = { super.setVisibility(it) },
+        )
     private var lastDisabledByPolicy = false
 
     private val locInScreen = IntArray(2)
 
     /** Visuo-haptic long-press effects */
+    private var longPressEffectAnimator: ValueAnimator? = null
     var haveLongPressPropertiesBeenReset = true
         private set
+
     private var paddingForLaunch = Rect()
     private var initialLongPressProperties: QSLongPressProperties? = null
     private var finalLongPressProperties: QSLongPressProperties? = null
     private val colorEvaluator = ArgbEvaluator.getInstance()
     val isLongPressEffectInitialized: Boolean
         get() = longPressEffect?.hasInitialized == true
-    private var longPressEffectHandle: DisposableHandle? = null
-    val isLongPressEffectBound: Boolean
-        get() = longPressEffectHandle != null
+
+    val areLongPressEffectPropertiesSet: Boolean
+        get() = initialLongPressProperties != null && finalLongPressProperties != null
 
     init {
         val typedValue = TypedValue()
         if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) {
-            throw IllegalStateException("QSViewImpl must be inflated with a theme that contains " +
-                    "Theme.SystemUI.QuickSettings")
+            throw IllegalStateException(
+                "QSViewImpl must be inflated with a theme that contains " +
+                    "Theme.SystemUI.QuickSettings"
+            )
         }
         setId(generateViewId())
         orientation = LinearLayout.HORIZONTAL
@@ -261,13 +272,9 @@
         setPaddingRelative(startPadding, padding, padding, padding)
 
         val labelMargin = resources.getDimensionPixelSize(R.dimen.qs_label_container_margin)
-        (labelContainer.layoutParams as MarginLayoutParams).apply {
-            marginStart = labelMargin
-        }
+        (labelContainer.layoutParams as MarginLayoutParams).apply { marginStart = labelMargin }
 
-        (sideView.layoutParams as MarginLayoutParams).apply {
-            marginStart = labelMargin
-        }
+        (sideView.layoutParams as MarginLayoutParams).apply { marginStart = labelMargin }
         (chevronView.layoutParams as MarginLayoutParams).apply {
             height = iconSize
             width = iconSize
@@ -285,8 +292,9 @@
     }
 
     private fun createAndAddLabels() {
-        labelContainer = LayoutInflater.from(context)
-                .inflate(R.layout.qs_tile_label, this, false) as IgnorableChildLinearLayout
+        labelContainer =
+            LayoutInflater.from(context).inflate(R.layout.qs_tile_label, this, false)
+                as IgnorableChildLinearLayout
         label = labelContainer.requireViewById(R.id.tile_label)
         secondaryLabel = labelContainer.requireViewById(R.id.app_label)
         if (collapsed) {
@@ -304,8 +312,9 @@
     }
 
     private fun createAndAddSideView() {
-        sideView = LayoutInflater.from(context)
-                .inflate(R.layout.qs_tile_side_icon, this, false) as ViewGroup
+        sideView =
+            LayoutInflater.from(context).inflate(R.layout.qs_tile_side_icon, this, false)
+                as ViewGroup
         customDrawableView = sideView.requireViewById(R.id.customDrawable)
         chevronView = sideView.requireViewById(R.id.chevron)
         setChevronColor(getChevronColorForState(QSTile.State.DEFAULT_STATE))
@@ -313,11 +322,12 @@
     }
 
     private fun createTileBackground(): Drawable {
-        qsTileBackground = if (Flags.qsTileFocusState()) {
-            mContext.getDrawable(R.drawable.qs_tile_background_flagged) as RippleDrawable
-        } else {
-            mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
-        }
+        qsTileBackground =
+            if (Flags.qsTileFocusState()) {
+                mContext.getDrawable(R.drawable.qs_tile_background_flagged) as RippleDrawable
+            } else {
+                mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable
+            }
         qsTileFocusBackground = mContext.getDrawable(R.drawable.qs_tile_focused_background)!!
         backgroundDrawable =
             qsTileBackground.findDrawableByLayerId(R.id.background) as LayerDrawable
@@ -332,21 +342,21 @@
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
         super.onLayout(changed, l, t, r, b)
         updateHeight()
-        maybeUpdateLongPressEffectDimensions()
+        maybeUpdateLongPressEffectWidth(measuredWidth.toFloat())
     }
 
-    private fun maybeUpdateLongPressEffectDimensions() {
+    private fun maybeUpdateLongPressEffectWidth(width: Float) {
         if (!isLongClickable || longPressEffect == null) return
 
-        val actualHeight = if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
-            heightOverride
-        } else {
-            measuredHeight
-        }
-        initialLongPressProperties?.height = actualHeight.toFloat()
-        initialLongPressProperties?.width = measuredWidth.toFloat()
-        finalLongPressProperties?.height = LONG_PRESS_EFFECT_HEIGHT_SCALE * actualHeight
-        finalLongPressProperties?.width = LONG_PRESS_EFFECT_WIDTH_SCALE * measuredWidth
+        initialLongPressProperties?.width = width
+        finalLongPressProperties?.width = LONG_PRESS_EFFECT_WIDTH_SCALE * width
+    }
+
+    private fun maybeUpdateLongPressEffectHeight(height: Float) {
+        if (!isLongClickable || longPressEffect == null) return
+
+        initialLongPressProperties?.height = height
+        finalLongPressProperties?.height = LONG_PRESS_EFFECT_HEIGHT_SCALE * height
     }
 
     override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
@@ -360,6 +370,7 @@
             }
         }
     }
+
     private fun updateHeight() {
         // TODO(b/332900989): Find a more robust way of resetting the tile if not reset by the
         //  launch animation.
@@ -368,16 +379,18 @@
             // we must do it here
             resetLongPressEffectProperties()
         }
-        val actualHeight = if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
-            heightOverride
-        } else {
-            measuredHeight
-        }
+        val actualHeight =
+            if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
+                heightOverride
+            } else {
+                measuredHeight
+            }
         // Limit how much we affect the height, so we don't have rounding artifacts when the tile
         // is too short.
         val constrainedSquishiness = constrainSquishiness(squishinessFraction)
         bottom = top + (actualHeight * constrainedSquishiness).toInt()
         scrollY = (actualHeight - height) / 2
+        maybeUpdateLongPressEffectHeight(actualHeight.toFloat())
     }
 
     override fun updateAccessibilityOrder(previousView: View?): View {
@@ -395,22 +408,77 @@
 
     override fun init(tile: QSTile) {
         val expandable = Expandable.fromView(this)
-        init(
+        if (longPressEffect != null) {
+            isHapticFeedbackEnabled = false
+            longPressEffect.qsTile = tile
+            longPressEffect.expandable = expandable
+            initLongPressEffectCallback()
+            init(
+                { _: View -> longPressEffect.onTileClick() },
+                null, // Haptics and long-clicks will be handled by the [QSLongPressEffect]
+            )
+        } else {
+            init(
                 { _: View? -> tile.click(expandable) },
                 { _: View? ->
                     tile.longClick(expandable)
                     true
-                }
-        )
-        if (quickSettingsVisualHapticsLongpress()) {
-            isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
+                },
+            )
         }
     }
 
-    private fun init(
-        click: OnClickListener?,
-        longClick: OnLongClickListener?
-    ) {
+    private fun initLongPressEffectCallback() {
+        longPressEffect?.callback =
+            object : QSLongPressEffect.Callback {
+
+                override fun onPrepareForLaunch() {
+                    prepareForLaunch()
+                }
+
+                override fun onResetProperties() {
+                    resetLongPressEffectProperties()
+                }
+
+                override fun onStartAnimator() {
+                    if (longPressEffectAnimator?.isRunning != true) {
+                        longPressEffectAnimator =
+                            ValueAnimator.ofFloat(0f, 1f).apply {
+                                this.duration = longPressEffect?.effectDuration?.toLong() ?: 0L
+                                interpolator = AccelerateDecelerateInterpolator()
+
+                                doOnStart { longPressEffect?.handleAnimationStart() }
+                                addUpdateListener {
+                                    val value = animatedValue as Float
+                                    if (value == 0f) {
+                                        bringToFront()
+                                    } else {
+                                        updateLongPressEffectProperties(value)
+                                    }
+                                }
+                                doOnEnd { longPressEffect?.handleAnimationComplete() }
+                                doOnCancel { longPressEffect?.handleAnimationCancel() }
+                                start()
+                            }
+                    }
+                }
+
+                override fun onReverseAnimator() {
+                    longPressEffectAnimator?.let {
+                        val pausedProgress = it.animatedFraction
+                        longPressEffect?.playReverseHaptics(pausedProgress)
+                        it.reverse()
+                    }
+                }
+
+                override fun onCancelAnimator() {
+                    resetLongPressEffectProperties()
+                    longPressEffectAnimator?.cancel()
+                }
+            }
+    }
+
+    private fun init(click: OnClickListener?, longClick: OnLongClickListener?) {
         setOnClickListener(click)
         onLongClickListener = longClick
     }
@@ -438,16 +506,18 @@
 
     override fun setClickable(clickable: Boolean) {
         super.setClickable(clickable)
-        if (!Flags.qsTileFocusState()){
-            background = if (clickable && showRippleEffect) {
-                qsTileBackground.also {
-                    // In case that the colorBackgroundDrawable was used as the background, make sure
-                    // it has the correct callback instead of null
-                    backgroundDrawable.callback = it
+        if (!Flags.qsTileFocusState()) {
+            background =
+                if (clickable && showRippleEffect) {
+                    qsTileBackground.also {
+                        // In case that the colorBackgroundDrawable was used as the background, make
+                        // sure
+                        // it has the correct callback instead of null
+                        backgroundDrawable.callback = it
+                    }
+                } else {
+                    backgroundDrawable
                 }
-            } else {
-                backgroundDrawable
-            }
         }
     }
 
@@ -482,8 +552,10 @@
         if (!TextUtils.isEmpty(accessibilityClass)) {
             event.className = accessibilityClass
         }
-        if (event.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION &&
-                stateDescriptionDeltas != null) {
+        if (
+            event.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_STATE_DESCRIPTION &&
+                stateDescriptionDeltas != null
+        ) {
             event.text.add(stateDescriptionDeltas)
             stateDescriptionDeltas = null
         }
@@ -493,36 +565,39 @@
         super.onInitializeAccessibilityNodeInfo(info)
         // Clear selected state so it is not announce by talkback.
         info.isSelected = false
-        info.text = if (TextUtils.isEmpty(secondaryLabel.text)) {
-            "${label.text}"
-        } else {
-            "${label.text}, ${secondaryLabel.text}"
-        }
+        info.text =
+            if (TextUtils.isEmpty(secondaryLabel.text)) {
+                "${label.text}"
+            } else {
+                "${label.text}, ${secondaryLabel.text}"
+            }
         if (lastDisabledByPolicy) {
             info.addAction(
-                    AccessibilityNodeInfo.AccessibilityAction(
-                            AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
-                            resources.getString(
-                                R.string.accessibility_tile_disabled_by_policy_action_description
-                            )
+                AccessibilityNodeInfo.AccessibilityAction(
+                    AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id,
+                    resources.getString(
+                        R.string.accessibility_tile_disabled_by_policy_action_description
                     )
+                )
             )
         }
         if (!TextUtils.isEmpty(accessibilityClass)) {
-            info.className = if (lastDisabledByPolicy) {
-                Button::class.java.name
-            } else {
-                accessibilityClass
-            }
+            info.className =
+                if (lastDisabledByPolicy) {
+                    Button::class.java.name
+                } else {
+                    accessibilityClass
+                }
             if (Switch::class.java.name == accessibilityClass) {
                 info.isChecked = tileState
                 info.isCheckable = true
                 if (isLongClickable) {
                     info.addAction(
-                            AccessibilityNodeInfo.AccessibilityAction(
-                                    AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
-                                    resources.getString(
-                                            R.string.accessibility_long_click_tile)))
+                        AccessibilityNodeInfo.AccessibilityAction(
+                            AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK.id,
+                            resources.getString(R.string.accessibility_long_click_tile)
+                        )
+                    )
                 }
             }
         }
@@ -541,6 +616,28 @@
         return sb.toString()
     }
 
+    @SuppressLint("ClickableViewAccessibility")
+    override fun onTouchEvent(event: MotionEvent?): Boolean {
+        // let the View run the onTouch logic for click and long-click detection
+        val result = super.onTouchEvent(event)
+        if (longPressEffect != null) {
+            when (event?.actionMasked) {
+                MotionEvent.ACTION_DOWN -> {
+                    longPressEffect.handleActionDown()
+                    if (isLongClickable) {
+                        postDelayed(
+                            { longPressEffect.handleTimeoutComplete() },
+                            ViewConfiguration.getTapTimeout().toLong(),
+                        )
+                    }
+                }
+                MotionEvent.ACTION_UP -> longPressEffect.handleActionUp()
+                MotionEvent.ACTION_CANCEL -> longPressEffect.handleActionCancel()
+            }
+        }
+        return result
+    }
+
     // HANDLE STATE CHANGES RELATED METHODS
 
     protected open fun handleStateChanged(state: QSTile.State) {
@@ -565,8 +662,11 @@
         if (!TextUtils.isEmpty(state.stateDescription)) {
             stateDescription.append(", ")
             stateDescription.append(state.stateDescription)
-            if (lastState != INVALID && state.state == lastState &&
-                    state.stateDescription != lastStateDescription) {
+            if (
+                lastState != INVALID &&
+                    state.state == lastState &&
+                    state.stateDescription != lastStateDescription
+            ) {
                 stateDescriptionDeltas = state.stateDescription
             }
         }
@@ -574,11 +674,12 @@
         setStateDescription(stateDescription.toString())
         lastStateDescription = state.stateDescription
 
-        accessibilityClass = if (state.state == Tile.STATE_UNAVAILABLE) {
-            null
-        } else {
-            state.expandedAccessibilityClassName
-        }
+        accessibilityClass =
+            if (state.state == Tile.STATE_UNAVAILABLE) {
+                null
+            } else {
+                state.expandedAccessibilityClassName
+            }
 
         if (state is AdapterState) {
             val newState = state.value
@@ -593,49 +694,51 @@
         }
         if (!Objects.equals(secondaryLabel.text, state.secondaryLabel)) {
             secondaryLabel.text = state.secondaryLabel
-            secondaryLabel.visibility = if (TextUtils.isEmpty(state.secondaryLabel)) {
-                GONE
-            } else {
-                VISIBLE
-            }
+            secondaryLabel.visibility =
+                if (TextUtils.isEmpty(state.secondaryLabel)) {
+                    GONE
+                } else {
+                    VISIBLE
+                }
         }
 
         // Colors
         if (state.state != lastState || state.disabledByPolicy != lastDisabledByPolicy) {
             singleAnimator.cancel()
             mQsLogger?.logTileBackgroundColorUpdateIfInternetTile(
-                    state.spec,
-                    state.state,
-                    state.disabledByPolicy,
-                    getBackgroundColorForState(state.state, state.disabledByPolicy))
+                state.spec,
+                state.state,
+                state.disabledByPolicy,
+                getBackgroundColorForState(state.state, state.disabledByPolicy)
+            )
             if (allowAnimations) {
                 singleAnimator.setValues(
-                        colorValuesHolder(
-                                BACKGROUND_NAME,
-                                backgroundColor,
-                                getBackgroundColorForState(state.state, state.disabledByPolicy)
-                        ),
-                        colorValuesHolder(
-                                LABEL_NAME,
-                                label.currentTextColor,
-                                getLabelColorForState(state.state, state.disabledByPolicy)
-                        ),
-                        colorValuesHolder(
-                                SECONDARY_LABEL_NAME,
-                                secondaryLabel.currentTextColor,
-                                getSecondaryLabelColorForState(state.state, state.disabledByPolicy)
-                        ),
-                        colorValuesHolder(
-                                CHEVRON_NAME,
-                                chevronView.imageTintList?.defaultColor ?: 0,
-                                getChevronColorForState(state.state, state.disabledByPolicy)
-                        ),
-                        colorValuesHolder(
-                                OVERLAY_NAME,
-                                backgroundOverlayColor,
-                                getOverlayColorForState(state.state)
-                        )
+                    colorValuesHolder(
+                        BACKGROUND_NAME,
+                        backgroundColor,
+                        getBackgroundColorForState(state.state, state.disabledByPolicy)
+                    ),
+                    colorValuesHolder(
+                        LABEL_NAME,
+                        label.currentTextColor,
+                        getLabelColorForState(state.state, state.disabledByPolicy)
+                    ),
+                    colorValuesHolder(
+                        SECONDARY_LABEL_NAME,
+                        secondaryLabel.currentTextColor,
+                        getSecondaryLabelColorForState(state.state, state.disabledByPolicy)
+                    ),
+                    colorValuesHolder(
+                        CHEVRON_NAME,
+                        chevronView.imageTintList?.defaultColor ?: 0,
+                        getChevronColorForState(state.state, state.disabledByPolicy)
+                    ),
+                    colorValuesHolder(
+                        OVERLAY_NAME,
+                        backgroundOverlayColor,
+                        getOverlayColorForState(state.state)
                     )
+                )
                 singleAnimator.start()
             } else {
                 setAllColors(
@@ -658,25 +761,16 @@
         lastIconTint = icon.getColor(state)
 
         // Long-press effects
-        if (state.handlesLongClick &&
-            longPressEffect?.initializeEffect(longPressEffectDuration) == true) {
-            // bind the long-press effect and set it as the touch listener
-            if (!isLongPressEffectBound) {
-                longPressEffectHandle =
-                    QSLongPressEffectViewBinder.bind(
-                        this,
-                        longPressEffect,
-                        state.spec,
-                    )
-            }
+        if (
+            state.handlesLongClick &&
+                longPressEffect?.initializeEffect(longPressEffectDuration) == true
+        ) {
             showRippleEffect = false
             initializeLongPressProperties(measuredHeight, measuredWidth)
         } else {
             // Long-press effects might have been enabled before but the new state does not
             // handle a long-press. In this case, we go back to the behaviour of a regular tile
             // and clean-up the resources
-            setOnTouchListener(null)
-            unbindLongPressEffect()
             showRippleEffect = isClickable
             initialLongPressProperties = null
             finalLongPressProperties = null
@@ -791,7 +885,7 @@
     }
 
     private fun getChevronColorForState(state: Int, disabledByPolicy: Boolean = false): Int =
-            getSecondaryLabelColorForState(state, disabledByPolicy)
+        getSecondaryLabelColorForState(state, disabledByPolicy)
 
     private fun getOverlayColorForState(state: Int): Int {
         return when (state) {
@@ -828,16 +922,18 @@
         // Dimensions change
         val newHeight =
             interpolateFloat(
-                effectProgress,
-                initialLongPressProperties?.height ?: 0f,
-                finalLongPressProperties?.height ?: 0f,
-            ).toInt()
+                    effectProgress,
+                    initialLongPressProperties?.height ?: 0f,
+                    finalLongPressProperties?.height ?: 0f,
+                )
+                .toInt()
         val newWidth =
             interpolateFloat(
-                effectProgress,
-                initialLongPressProperties?.width ?: 0f,
-                finalLongPressProperties?.width ?: 0f,
-            ).toInt()
+                    effectProgress,
+                    initialLongPressProperties?.width ?: 0f,
+                    finalLongPressProperties?.width ?: 0f,
+                )
+                .toInt()
 
         val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0
         val startingWidth = initialLongPressProperties?.width?.toInt() ?: 0
@@ -898,11 +994,6 @@
         )
     }
 
-    private fun unbindLongPressEffect() {
-        longPressEffectHandle?.dispose()
-        longPressEffectHandle = null
-    }
-
     private fun interpolateFloat(fraction: Float, start: Float, end: Float): Float =
         start + fraction * (end - start)
 
@@ -964,12 +1055,13 @@
     }
 
     @VisibleForTesting
-    internal fun getCurrentColors(): List<Int> = listOf(
+    internal fun getCurrentColors(): List<Int> =
+        listOf(
             backgroundColor,
             label.currentTextColor,
             secondaryLabel.currentTextColor,
             chevronView.imageTintList?.defaultColor ?: 0
-    )
+        )
 
     inner class StateChangeRunnable(private val state: QSTile.State) : Runnable {
         override fun run() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 0327ec7..23faf7d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -73,7 +73,6 @@
 
 import androidx.annotation.NonNull;
 
-import com.android.compose.animation.scene.SceneKey;
 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
@@ -99,12 +98,10 @@
 import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.scene.domain.interactor.SceneInteractor;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
-import com.android.systemui.scene.shared.model.Scenes;
+import com.android.systemui.scene.shared.model.SceneFamilies;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.ShadeViewController;
-import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.shade.shared.model.ShadeMode;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -158,7 +155,6 @@
     private final ScreenPinningRequest mScreenPinningRequest;
     private final NotificationShadeWindowController mStatusBarWinController;
     private final Provider<SceneInteractor> mSceneInteractor;
-    private final Provider<ShadeInteractor> mShadeInteractor;
 
     private final Runnable mConnectionRunnable = () ->
             internalConnectToCurrentUser("runnable: startConnectionToCurrentUser");
@@ -247,7 +243,7 @@
                             // Gesture was too short to be picked up by scene container touch
                             // handling; programmatically start the transition to shade scene.
                             mSceneInteractor.get().changeScene(
-                                    getShadeSceneKey(),
+                                    SceneFamilies.NotifShade,
                                     "short launcher swipe"
                             );
                         }
@@ -267,7 +263,7 @@
                                 "trackpad swipe");
                     } else if (action == ACTION_UP) {
                         mSceneInteractor.get().changeScene(
-                                getShadeSceneKey(),
+                                SceneFamilies.NotifShade,
                                 "short trackpad swipe"
                         );
                     }
@@ -632,7 +628,6 @@
             NotificationShadeWindowController statusBarWinController,
             SysUiState sysUiState,
             Provider<SceneInteractor> sceneInteractor,
-            Provider<ShadeInteractor> shadeInteractor,
             UserTracker userTracker,
             WakefulnessLifecycle wakefulnessLifecycle,
             UiEventLogger uiEventLogger,
@@ -659,7 +654,6 @@
         mScreenPinningRequest = screenPinningRequest;
         mStatusBarWinController = statusBarWinController;
         mSceneInteractor = sceneInteractor;
-        mShadeInteractor = shadeInteractor;
         mUserTracker = userTracker;
         mConnectionBackoffAttempts = 0;
         mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
@@ -925,12 +919,6 @@
         }
     }
 
-    private SceneKey getShadeSceneKey() {
-        return mShadeInteractor.get().getShadeMode().getValue() == ShadeMode.dual()
-                ? Scenes.NotificationsShade
-                : Scenes.Shade;
-    }
-
     private void notifyHomeRotationEnabled(boolean enabled) {
         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
             mConnectionCallbacks.get(i).onHomeRotationEnabled(enabled);
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index da23936..323ca87 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -20,6 +20,9 @@
 import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
 import com.android.systemui.scene.domain.SceneDomainModule
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule
+import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolverModule
+import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.domain.startable.ScrimStartable
 import com.android.systemui.scene.shared.model.SceneContainerConfig
@@ -42,6 +45,11 @@
             QuickSettingsSceneModule::class,
             ShadeSceneModule::class,
             SceneDomainModule::class,
+
+            // List SceneResolver modules for supported SceneFamilies
+            HomeSceneFamilyResolverModule::class,
+            NotifShadeSceneFamilyResolverModule::class,
+            QuickSettingsSceneFamilyResolverModule::class,
         ],
 )
 interface KeyguardlessSceneContainerFrameworkModule {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index a0cf82a..4691eba 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -21,6 +21,9 @@
 import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule
 import com.android.systemui.scene.domain.SceneDomainModule
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule
+import com.android.systemui.scene.domain.resolver.NotifShadeSceneFamilyResolverModule
+import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule
 import com.android.systemui.scene.domain.startable.SceneContainerStartable
 import com.android.systemui.scene.domain.startable.ScrimStartable
 import com.android.systemui.scene.shared.model.SceneContainerConfig
@@ -48,6 +51,11 @@
             NotificationsShadeSceneModule::class,
             NotificationsShadeSessionModule::class,
             SceneDomainModule::class,
+
+            // List SceneResolver modules for supported SceneFamilies
+            HomeSceneFamilyResolverModule::class,
+            NotifShadeSceneFamilyResolverModule::class,
+            QuickSettingsSceneFamilyResolverModule::class,
         ],
 )
 interface SceneContainerFrameworkModule {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
index a326ec1..9a7eef8 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ShadelessSceneContainerFrameworkModule.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.scene
 
 import com.android.systemui.scene.domain.SceneDomainModule
+import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.Scenes
 import dagger.Module
@@ -31,6 +32,9 @@
             GoneSceneModule::class,
             LockscreenSceneModule::class,
             SceneDomainModule::class,
+
+            // List SceneResolver modules for supported SceneFamilies
+            HomeSceneFamilyResolverModule::class,
         ],
 )
 object ShadelessSceneContainerFrameworkModule {
@@ -49,11 +53,12 @@
                     Scenes.Bouncer,
                 ),
             initialSceneKey = Scenes.Lockscreen,
-            mapOf(
-                Scenes.Gone to 0,
-                Scenes.Lockscreen to 0,
-                Scenes.Bouncer to 1,
-            )
+            navigationDistances =
+                mapOf(
+                    Scenes.Gone to 0,
+                    Scenes.Lockscreen to 0,
+                    Scenes.Bouncer to 1,
+                )
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt
index 9b2a6dd..be792df 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt
@@ -16,14 +16,12 @@
 
 package com.android.systemui.scene.domain
 
-import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolverModule
 import com.android.systemui.scene.domain.resolver.SceneResolverModule
 import dagger.Module
 
 @Module(
     includes =
         [
-            HomeSceneFamilyResolverModule::class,
             SceneResolverModule::class,
         ]
 )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 998537c..c98a49b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -36,7 +36,9 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.emitAll
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
@@ -239,7 +241,14 @@
         loggingReason: String,
     ) {
         val currentSceneKey = currentScene.value
-        val resolvedScene = sceneFamilyResolvers.get()[toScene]?.resolvedScene?.value ?: toScene
+        val resolvedScene =
+            sceneFamilyResolvers.get()[toScene]?.let { familyResolver ->
+                if (familyResolver.includesScene(currentSceneKey)) {
+                    return
+                } else {
+                    familyResolver.resolvedScene.value
+                }
+            } ?: toScene
         if (
             !validateSceneChange(
                 from = currentSceneKey,
@@ -320,8 +329,9 @@
      * Returns the [concrete scene][Scenes] for [sceneKey] if it is a [scene family][SceneFamilies],
      * otherwise returns a singleton [Flow] containing [sceneKey].
      */
-    fun resolveSceneFamily(sceneKey: SceneKey): Flow<SceneKey> =
-        sceneFamilyResolvers.get()[sceneKey]?.resolvedScene ?: flowOf(sceneKey)
+    fun resolveSceneFamily(sceneKey: SceneKey): Flow<SceneKey> = flow {
+        emitAll(sceneFamilyResolvers.get()[sceneKey]?.resolvedScene ?: flowOf(sceneKey))
+    }
 
     private fun isVisibleInternal(
         raw: Boolean = repository.isVisible.value,
@@ -365,4 +375,12 @@
 
         return from != to
     }
+
+    /** Returns a flow indicating if the currently visible scene can be resolved from [family]. */
+    fun isCurrentSceneInFamily(family: SceneKey): Flow<Boolean> =
+        currentScene.map { currentScene -> isSceneInFamily(currentScene, family) }
+
+    /** Returns `true` if [scene] can be resolved from [family]. */
+    fun isSceneInFamily(scene: SceneKey, family: SceneKey): Boolean =
+        sceneFamilyResolvers.get()[family]?.includesScene(scene) == true
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
index f19929c..9e91b66 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/HomeSceneFamilyResolver.kt
@@ -51,25 +51,42 @@
     override val resolvedScene: StateFlow<SceneKey> =
         combine(
                 deviceEntryInteractor.canSwipeToEnter,
+                deviceEntryInteractor.isDeviceEntered,
                 deviceEntryInteractor.isUnlocked,
                 transform = ::homeScene,
             )
             .stateIn(
                 scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
+                started = SharingStarted.Eagerly,
                 initialValue =
                     homeScene(
                         deviceEntryInteractor.canSwipeToEnter.value,
+                        deviceEntryInteractor.isDeviceEntered.value,
                         deviceEntryInteractor.isUnlocked.value,
                     )
             )
 
-    private fun homeScene(canSwipeToEnter: Boolean?, isUnlocked: Boolean): SceneKey =
+    override fun includesScene(scene: SceneKey): Boolean = scene in homeScenes
+
+    private fun homeScene(
+        canSwipeToEnter: Boolean?,
+        isDeviceEntered: Boolean,
+        isUnlocked: Boolean,
+    ): SceneKey =
         when {
             canSwipeToEnter == true -> Scenes.Lockscreen
-            isUnlocked -> Scenes.Gone
-            else -> Scenes.Lockscreen
+            !isDeviceEntered -> Scenes.Lockscreen
+            !isUnlocked -> Scenes.Lockscreen
+            else -> Scenes.Gone
         }
+
+    companion object {
+        val homeScenes =
+            setOf(
+                Scenes.Gone,
+                Scenes.Lockscreen,
+            )
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt
new file mode 100644
index 0000000..99e554e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/NotifShadeSceneFamilyResolver.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.resolver
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class NotifShadeSceneFamilyResolver
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    shadeInteractor: ShadeInteractor,
+) : SceneResolver {
+    override val targetFamily: SceneKey = SceneFamilies.NotifShade
+
+    override val resolvedScene: StateFlow<SceneKey> =
+        shadeInteractor.shadeMode
+            .map(::notifShadeScene)
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = notifShadeScene(shadeInteractor.shadeMode.value),
+            )
+
+    override fun includesScene(scene: SceneKey): Boolean = scene in notifShadeScenes
+
+    private fun notifShadeScene(shadeMode: ShadeMode) =
+        when (shadeMode) {
+            is ShadeMode.Single -> Scenes.Shade
+            is ShadeMode.Dual -> Scenes.NotificationsShade
+            is ShadeMode.Split -> Scenes.Shade
+        }
+
+    companion object {
+        val notifShadeScenes =
+            setOf(
+                Scenes.NotificationsShade,
+                Scenes.Shade,
+            )
+    }
+}
+
+@Module
+interface NotifShadeSceneFamilyResolverModule {
+    @Binds @IntoSet fun bindResolver(interactor: NotifShadeSceneFamilyResolver): SceneResolver
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt
new file mode 100644
index 0000000..2962a3e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/QuickSettingsSceneFamilyResolver.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.domain.resolver
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.shared.model.ShadeMode
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+
+@SysUISingleton
+class QuickSettingsSceneFamilyResolver
+@Inject
+constructor(
+    @Application applicationScope: CoroutineScope,
+    shadeInteractor: ShadeInteractor,
+) : SceneResolver {
+    override val targetFamily: SceneKey = SceneFamilies.QuickSettings
+
+    override val resolvedScene: StateFlow<SceneKey> =
+        shadeInteractor.shadeMode
+            .map(::quickSettingsScene)
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = quickSettingsScene(shadeInteractor.shadeMode.value),
+            )
+
+    override fun includesScene(scene: SceneKey): Boolean = scene in quickSettingsScenes
+
+    private fun quickSettingsScene(shadeMode: ShadeMode) =
+        when (shadeMode) {
+            is ShadeMode.Single -> Scenes.QuickSettings
+            is ShadeMode.Dual -> Scenes.QuickSettingsShade
+            is ShadeMode.Split -> Scenes.Shade
+        }
+
+    companion object {
+        val quickSettingsScenes =
+            setOf(
+                Scenes.QuickSettings,
+                Scenes.QuickSettingsShade,
+                Scenes.Shade,
+            )
+    }
+}
+
+@Module
+interface QuickSettingsSceneFamilyResolverModule {
+    @Binds @IntoSet fun bindResolver(interactor: QuickSettingsSceneFamilyResolver): SceneResolver
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/SceneResolver.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/SceneResolver.kt
index 8372529..8d7247b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/SceneResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/resolver/SceneResolver.kt
@@ -29,6 +29,9 @@
 
     /** The concrete scene that [targetFamily] is currently resolved to. */
     val resolvedScene: StateFlow<SceneKey>
+
+    /** Returns `true` if [scene] can be resolved from [targetFamily]. */
+    fun includesScene(scene: SceneKey): Boolean
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
index 99118bc..c34a6cd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/GoneSceneViewModel.kt
@@ -23,7 +23,7 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
@@ -43,39 +43,42 @@
 ) {
     val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
         shadeInteractor.shadeMode
-            .map { shadeMode -> destinationScenes(shadeMode = shadeMode) }
+            .map(::destinationScenes)
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = destinationScenes(shadeMode = shadeInteractor.shadeMode.value)
+                initialValue =
+                    destinationScenes(
+                        shadeMode = shadeInteractor.shadeMode.value,
+                    )
             )
 
-    private fun destinationScenes(shadeMode: ShadeMode): Map<UserAction, UserActionResult> {
+    private fun destinationScenes(
+        shadeMode: ShadeMode,
+    ): Map<UserAction, UserActionResult> {
         return buildMap {
-            if (shadeMode is ShadeMode.Single) {
-                this[
+            if (
+                shadeMode is ShadeMode.Single ||
+                    // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
+                    shadeMode is ShadeMode.Dual
+            ) {
+                put(
                     Swipe(
                         pointerCount = 2,
                         fromSource = Edge.Top,
                         direction = SwipeDirection.Down,
-                    )] = UserActionResult(Scenes.QuickSettings)
+                    ),
+                    UserActionResult(SceneFamilies.QuickSettings)
+                )
             }
 
-            // TODO(b/338577208): Remove this once we add Dual Shade invocation zones.
-            if (shadeMode is ShadeMode.Dual) {
-                this[
-                    Swipe(
-                        pointerCount = 2,
-                        fromSource = Edge.Top,
-                        direction = SwipeDirection.Down,
-                    )] = UserActionResult(Scenes.QuickSettingsShade)
-            }
-
-            val downSceneKey =
-                if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade
-            val downTransitionKey = ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
-            this[Swipe(direction = SwipeDirection.Down)] =
-                UserActionResult(downSceneKey, downTransitionKey)
+            put(
+                Swipe(direction = SwipeDirection.Down),
+                UserActionResult(
+                    SceneFamilies.NotifShade,
+                    ToSplitShade.takeIf { shadeMode is ShadeMode.Split }
+                )
+            )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index ac1f971..004db16 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -21,15 +21,12 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.dagger.ShadeTouchLog
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse
 import com.android.systemui.shade.ShadeController.ShadeVisibilityListener
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
 import com.android.systemui.statusbar.VibratorHelper
@@ -60,7 +57,6 @@
     private val shadeInteractor: ShadeInteractor,
     private val sceneInteractor: SceneInteractor,
     private val notificationStackScrollLayout: NotificationStackScrollLayout,
-    @ShadeTouchLog private val touchLog: LogBuffer,
     private val vibratorHelper: VibratorHelper,
     commandQueue: CommandQueue,
     statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
@@ -176,19 +172,14 @@
     }
 
     override fun expandToNotifications() {
-        val shadeMode = shadeInteractor.shadeMode.value
         sceneInteractor.changeScene(
-            if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
-            "ShadeController.animateExpandShade"
+            SceneFamilies.NotifShade,
+            "ShadeController.animateExpandShade",
         )
     }
 
     override fun expandToQs() {
-        val shadeMode = shadeInteractor.shadeMode.value
-        sceneInteractor.changeScene(
-            if (shadeMode is ShadeMode.Dual) Scenes.QuickSettingsShade else Scenes.QuickSettings,
-            "ShadeController.animateExpandQs"
-        )
+        sceneInteractor.changeScene(SceneFamilies.QuickSettings, "ShadeController.animateExpandQs")
     }
 
     override fun setVisibilityListener(listener: ShadeVisibilityListener) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index fe16fc0..d5b4f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -21,25 +21,23 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.shade.shared.model.ShadeMode
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** ShadeInteractor implementation for Scene Container. */
-@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class ShadeInteractorSceneContainerImpl
 @Inject
@@ -52,10 +50,11 @@
     override val shadeMode: StateFlow<ShadeMode> = shadeRepository.shadeMode
 
     override val shadeExpansion: StateFlow<Float> =
-        sceneBasedExpansion(sceneInteractor, notificationsScene)
+        sceneBasedExpansion(sceneInteractor, SceneFamilies.NotifShade)
             .stateIn(scope, SharingStarted.Eagerly, 0f)
 
-    private val sceneBasedQsExpansion = sceneBasedExpansion(sceneInteractor, quickSettingsScene)
+    private val sceneBasedQsExpansion =
+        sceneBasedExpansion(sceneInteractor, SceneFamilies.QuickSettings)
 
     override val qsExpansion: StateFlow<Float> =
         combine(
@@ -78,23 +77,38 @@
             .stateIn(scope, SharingStarted.Eagerly, false)
 
     override val isQsBypassingShade: Flow<Boolean> =
-        sceneInteractor.transitionState
-            .map { state ->
-                when (state) {
-                    is ObservableTransitionState.Idle -> false
-                    is ObservableTransitionState.Transition ->
-                        state.toScene == quickSettingsScene && state.fromScene != notificationsScene
-                }
+        combine(
+                sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
+                sceneInteractor.resolveSceneFamily(SceneFamilies.NotifShade),
+                ::Pair
+            )
+            .flatMapLatestConflated { (quickSettingsScene, notificationsScene) ->
+                sceneInteractor.transitionState
+                    .map { state ->
+                        when (state) {
+                            is ObservableTransitionState.Idle -> false
+                            is ObservableTransitionState.Transition ->
+                                state.toScene == quickSettingsScene &&
+                                    state.fromScene != notificationsScene
+                        }
+                    }
+                    .distinctUntilChanged()
             }
             .distinctUntilChanged()
 
     override val isQsFullscreen: Flow<Boolean> =
-        sceneInteractor.transitionState
-            .map { state ->
-                when (state) {
-                    is ObservableTransitionState.Idle -> state.currentScene == quickSettingsScene
-                    is ObservableTransitionState.Transition -> false
-                }
+        sceneInteractor
+            .resolveSceneFamily(SceneFamilies.QuickSettings)
+            .flatMapLatestConflated { quickSettingsScene ->
+                sceneInteractor.transitionState
+                    .map { state ->
+                        when (state) {
+                            is ObservableTransitionState.Idle ->
+                                state.currentScene == quickSettingsScene
+                            is ObservableTransitionState.Transition -> false
+                        }
+                    }
+                    .distinctUntilChanged()
             }
             .distinctUntilChanged()
 
@@ -108,34 +122,39 @@
             .stateIn(scope, SharingStarted.Eagerly, false)
 
     override val isUserInteractingWithShade: Flow<Boolean> =
-        sceneBasedInteracting(sceneInteractor, notificationsScene)
+        sceneBasedInteracting(sceneInteractor, SceneFamilies.NotifShade)
 
     override val isUserInteractingWithQs: Flow<Boolean> =
-        sceneBasedInteracting(sceneInteractor, quickSettingsScene)
+        sceneBasedInteracting(sceneInteractor, SceneFamilies.QuickSettings)
 
     /**
      * Returns a flow that uses scene transition progress to and from a scene that is pulled down
      * from the top of the screen to a 0-1 expansion amount float.
      */
     fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
-        sceneInteractor.transitionState
-            .flatMapLatest { state ->
-                when (state) {
-                    is ObservableTransitionState.Idle ->
-                        if (state.currentScene == sceneKey) {
-                            flowOf(1f)
-                        } else {
-                            flowOf(0f)
+        sceneInteractor
+            .resolveSceneFamily(sceneKey)
+            .flatMapLatestConflated { resolvedSceneKey ->
+                sceneInteractor.transitionState
+                    .flatMapLatestConflated { state ->
+                        when (state) {
+                            is ObservableTransitionState.Idle ->
+                                if (state.currentScene == resolvedSceneKey) {
+                                    flowOf(1f)
+                                } else {
+                                    flowOf(0f)
+                                }
+                            is ObservableTransitionState.Transition ->
+                                if (state.toScene == resolvedSceneKey) {
+                                    state.progress
+                                } else if (state.fromScene == resolvedSceneKey) {
+                                    state.progress.map { progress -> 1 - progress }
+                                } else {
+                                    flowOf(0f)
+                                }
                         }
-                    is ObservableTransitionState.Transition ->
-                        if (state.toScene == sceneKey) {
-                            state.progress
-                        } else if (state.fromScene == sceneKey) {
-                            state.progress.map { progress -> 1 - progress }
-                        } else {
-                            flowOf(0f)
-                        }
-                }
+                    }
+                    .distinctUntilChanged()
             }
             .distinctUntilChanged()
 
@@ -145,29 +164,16 @@
      */
     fun sceneBasedInteracting(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
         sceneInteractor.transitionState
-            .map { state ->
+            .flatMapLatestConflated { state ->
                 when (state) {
-                    is ObservableTransitionState.Idle -> false
+                    is ObservableTransitionState.Idle -> flowOf(false)
                     is ObservableTransitionState.Transition ->
-                        state.isInitiatedByUserInput &&
-                            (state.toScene == sceneKey || state.fromScene == sceneKey)
+                        sceneInteractor.resolveSceneFamily(sceneKey).map { resolvedSceneKey ->
+                            state.isInitiatedByUserInput &&
+                                (state.toScene == resolvedSceneKey ||
+                                    state.fromScene == resolvedSceneKey)
+                        }
                 }
             }
             .distinctUntilChanged()
-
-    private val notificationsScene: SceneKey
-        get() =
-            if (shadeMode.value is ShadeMode.Dual) {
-                Scenes.NotificationsShade
-            } else {
-                Scenes.Shade
-            }
-
-    private val quickSettingsScene: SceneKey
-        get() =
-            if (shadeMode.value is ShadeMode.Dual) {
-                Scenes.QuickSettingsShade
-            } else {
-                Scenes.QuickSettings
-            }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index e7fc18e..558f179 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -17,12 +17,11 @@
 package com.android.systemui.shade.domain.interactor
 
 import com.android.keyguard.LockIconViewController
-import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.shade.shared.model.ShadeMode
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
@@ -31,7 +30,6 @@
 class ShadeLockscreenInteractorImpl
 @Inject
 constructor(
-    @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundScope: CoroutineScope,
     private val shadeInteractor: ShadeInteractor,
     private val sceneInteractor: SceneInteractor,
@@ -69,6 +67,7 @@
     override fun setPulsing(pulsing: Boolean) {
         // Now handled elsewhere. Do nothing.
     }
+
     override fun transitionToExpandedShade(delay: Long) {
         backgroundScope.launch {
             delay(delay)
@@ -98,12 +97,9 @@
     }
 
     private fun changeToShadeScene() {
-        applicationScope.launch {
-            val shadeMode = shadeInteractor.shadeMode.value
-            sceneInteractor.changeScene(
-                if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
-                "ShadeLockscreenInteractorImpl.expandToNotifications",
-            )
-        }
+        sceneInteractor.changeScene(
+            SceneFamilies.NotifShade,
+            "ShadeLockscreenInteractorImpl.expandToNotifications",
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 2e87a5b..ee2c9cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -38,6 +38,7 @@
 import android.view.ViewGroup
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
 import com.android.systemui.Dumpable
 import com.android.systemui.Flags.smartspaceLockscreenViewmodel
@@ -53,6 +54,7 @@
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
 import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.TimeChangedDelegate
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.clocks.WeatherData
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -411,6 +413,7 @@
         val ssView = plugin.getView(parent)
         configPlugin?.let { ssView.registerConfigProvider(it) }
         ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+        ssView.setTimeChangedDelegate(SmartspaceTimeChangedDelegate(keyguardUpdateMonitor))
         ssView.registerDataProvider(plugin)
 
         ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
@@ -682,5 +685,28 @@
             }
         }
     }
+
+    private class SmartspaceTimeChangedDelegate(
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor
+    ) : TimeChangedDelegate {
+        private var keyguardUpdateMonitorCallback: KeyguardUpdateMonitorCallback? = null
+        override fun register(callback: Runnable) {
+            if (keyguardUpdateMonitorCallback != null) {
+                unregister()
+            }
+            keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+                override fun onTimeChanged() {
+                    callback.run()
+                }
+            }
+            keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+            callback.run()
+        }
+
+        override fun unregister() {
+            keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
+            keyguardUpdateMonitorCallback = null
+        }
+    }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index e90a64a..cf5a562 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.SceneFamilies
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shade.shared.model.ShadeMode
@@ -36,11 +37,9 @@
 import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
 
 /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */
 @SysUISingleton
@@ -50,7 +49,7 @@
     dumpManager: DumpManager,
     stackAppearanceInteractor: NotificationStackAppearanceInteractor,
     shadeInteractor: ShadeInteractor,
-    sceneInteractor: SceneInteractor,
+    private val sceneInteractor: SceneInteractor,
     // TODO(b/336364825) Remove Lazy when SceneContainerFlag is released -
     // while the flag is off, creating this object too early results in a crash
     keyguardInteractor: Lazy<KeyguardInteractor>,
@@ -63,9 +62,11 @@
     val expandFraction: Flow<Float> =
         combine(
                 shadeInteractor.shadeExpansion,
+                shadeInteractor.shadeMode,
                 shadeInteractor.qsExpansion,
                 sceneInteractor.transitionState,
-            ) { shadeExpansion, qsExpansion, transitionState ->
+                sceneInteractor.resolveSceneFamily(SceneFamilies.QuickSettings),
+            ) { shadeExpansion, shadeMode, qsExpansion, transitionState, quickSettingsScene ->
                 when (transitionState) {
                     is ObservableTransitionState.Idle -> {
                         if (transitionState.currentScene == Scenes.Lockscreen) {
@@ -76,16 +77,16 @@
                     }
                     is ObservableTransitionState.Transition -> {
                         if (
-                            (transitionState.fromScene == notificationsScene &&
+                            (transitionState.fromScene in SceneFamilies.NotifShade &&
                                 transitionState.toScene == quickSettingsScene) ||
-                                (transitionState.fromScene == quickSettingsScene &&
-                                    transitionState.toScene == notificationsScene)
+                                (transitionState.fromScene in quickSettingsScene &&
+                                    transitionState.toScene in SceneFamilies.NotifShade)
                         ) {
                             1f
                         } else if (
-                            (transitionState.fromScene == Scenes.Gone ||
-                                transitionState.fromScene == Scenes.Lockscreen) &&
-                                transitionState.toScene == quickSettingsScene
+                            shadeMode != ShadeMode.Split &&
+                                transitionState.fromScene in SceneFamilies.Home &&
+                                transitionState.toScene in quickSettingsScene
                         ) {
                             // during QS expansion, increase fraction at same rate as scrim alpha,
                             // but start when scrim alpha is at EXPANSION_FOR_DELAYED_STACK_FADE_IN.
@@ -101,6 +102,9 @@
             .distinctUntilChanged()
             .dumpWhileCollecting("expandFraction")
 
+    private operator fun SceneKey.contains(scene: SceneKey) =
+        sceneInteractor.isSceneInFamily(scene, this)
+
     /** The bounds of the notification stack in the current scene. */
     private val shadeScrimClipping: Flow<ShadeScrimClipping?> =
         combine(
@@ -151,8 +155,8 @@
 
     /** Whether the notification stack is scrollable or not. */
     val isScrollable: Flow<Boolean> =
-        sceneInteractor.currentScene
-            .map { it == notificationsScene }
+        sceneInteractor
+            .isCurrentSceneInFamily(SceneFamilies.NotifShade)
             .dumpWhileCollecting("isScrollable")
 
     /** Whether the notification stack is displayed in doze mode. */
@@ -163,22 +167,4 @@
             keyguardInteractor.get().isDozing.dumpWhileCollecting("isDozing")
         }
     }
-
-    private val shadeMode: StateFlow<ShadeMode> = shadeInteractor.shadeMode
-
-    private val notificationsScene: SceneKey
-        get() =
-            if (shadeMode.value is ShadeMode.Dual) {
-                Scenes.NotificationsShade
-            } else {
-                Scenes.Shade
-            }
-
-    private val quickSettingsScene: SceneKey
-        get() =
-            if (shadeMode.value is ShadeMode.Dual) {
-                Scenes.QuickSettingsShade
-            } else {
-                Scenes.QuickSettings
-            }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 139ac7e..a27989d 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -36,6 +36,7 @@
 import dagger.multibindings.IntoSet
 import java.util.Optional
 import javax.inject.Named
+import javax.inject.Qualifier
 import javax.inject.Scope
 
 @Scope @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class SysUIUnfoldScope
@@ -54,8 +55,17 @@
 @Module(subcomponents = [SysUIUnfoldComponent::class])
 class SysUIUnfoldModule {
 
+    /**
+     * Qualifier for dependencies bound in [com.android.systemui.unfold.SysUIUnfoldModule]
+     */
+    @Qualifier
+    @MustBeDocumented
+    @Retention(AnnotationRetention.RUNTIME)
+    annotation class BoundFromSysUiUnfoldModule
+
     @Provides
     @SysUISingleton
+    @BoundFromSysUiUnfoldModule
     fun provideSysUIUnfoldComponent(
         provider: Optional<UnfoldTransitionProgressProvider>,
         rotationProvider: Optional<NaturalRotationUnfoldProgressProvider>,
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt
index 41cd95b..8d202ac 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/MapUtils.kt
@@ -30,3 +30,6 @@
     }
     return destination
 }
+
+/** Returns a map with all entries containing `null` values removed. */
+fun <K, V> Map<K, V?>.filterValuesNotNull(): Map<K, V> = mapValuesNotNull { it.value }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index e613216..f457470 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -136,6 +136,7 @@
 import com.android.systemui.util.RoundedCornerProgressDrawable;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
+import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
 import com.android.systemui.volume.ui.binder.VolumeDialogMenuIconBinder;
 import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
@@ -313,6 +314,7 @@
     private final VibratorHelper mVibratorHelper;
     private final com.android.systemui.util.time.SystemClock mSystemClock;
     private final VolumeDialogMenuIconBinder mVolumeDialogMenuIconBinder;
+    private final VolumePanelFlag mVolumePanelFlag;
 
     public VolumeDialogImpl(
             Context context,
@@ -328,6 +330,7 @@
             CsdWarningDialog.Factory csdWarningDialogFactory,
             DevicePostureController devicePostureController,
             Looper looper,
+            VolumePanelFlag volumePanelFlag,
             DumpManager dumpManager,
             Lazy<SecureSettings> secureSettings,
             VibratorHelper vibratorHelper,
@@ -366,6 +369,7 @@
         mSecureSettings = secureSettings;
         mVolumeDialogMenuIconBinder = volumeDialogMenuIconBinder;
         mDialogTimeoutMillis = DIALOG_TIMEOUT_MILLIS;
+        mVolumePanelFlag = volumePanelFlag;
 
         dumpManager.registerDumpable("VolumeDialogImpl", this);
 
@@ -1364,6 +1368,9 @@
     }
 
     private void updateODICaptionsH(boolean isServiceComponentEnabled, boolean fromTooltip) {
+        // don't show captions view when the new volume panel is enabled.
+        isServiceComponentEnabled =
+                isServiceComponentEnabled && !mVolumePanelFlag.canUseNewVolumePanel();
         if (mODICaptionsView != null) {
             mODICaptionsView.setVisibility(isServiceComponentEnabled ? VISIBLE : GONE);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index fd68bfb..f8ddc42 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -44,6 +44,7 @@
 import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
 import com.android.systemui.volume.panel.dagger.VolumePanelComponent;
 import com.android.systemui.volume.panel.dagger.factory.VolumePanelComponentFactory;
+import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
 import com.android.systemui.volume.ui.binder.VolumeDialogMenuIconBinder;
 import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
@@ -112,6 +113,7 @@
             VolumeNavigator volumeNavigator,
             CsdWarningDialog.Factory csdFactory,
             DevicePostureController devicePostureController,
+            VolumePanelFlag volumePanelFlag,
             DumpManager dumpManager,
             Lazy<SecureSettings> secureSettings,
             VibratorHelper vibratorHelper,
@@ -131,6 +133,7 @@
                 csdFactory,
                 devicePostureController,
                 Looper.getMainLooper(),
+                volumePanelFlag,
                 dumpManager,
                 secureSettings,
                 vibratorHelper,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
index 5bc9aa4..cbd535b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/FullscreenMagnificationControllerTest.java
@@ -21,8 +21,10 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
-import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -34,8 +36,12 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
+import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.Display;
+import android.view.IRotationWatcher;
+import android.view.IWindowManager;
 import android.view.SurfaceControl;
 import android.view.SurfaceControlViewHost;
 import android.view.View;
@@ -55,6 +61,8 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -73,9 +81,12 @@
     private ValueAnimator mShowHideBorderAnimator;
     private SurfaceControl.Transaction mTransaction;
     private TestableWindowManager mWindowManager;
+    @Mock
+    private IWindowManager mIWindowManager;
 
     @Before
     public void setUp() {
+        MockitoAnnotations.initMocks(this);
         getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
                 spy(new SurfaceControlViewHost(mContext, mContext.getDisplay(),
                         new InputTransferToken(), "FullscreenMagnification")));
@@ -88,9 +99,11 @@
         mShowHideBorderAnimator = spy(newNullTargetObjectAnimator());
         mFullscreenMagnificationController = new FullscreenMagnificationController(
                 mContext,
+                mContext.getMainThreadHandler(),
                 mContext.getMainExecutor(),
                 mContext.getSystemService(AccessibilityManager.class),
                 mContext.getSystemService(WindowManager.class),
+                mIWindowManager,
                 scvhSupplier,
                 mTransaction,
                 mShowHideBorderAnimator);
@@ -104,7 +117,8 @@
     }
 
     @Test
-    public void enableFullscreenMagnification_visibleBorder() throws InterruptedException {
+    public void enableFullscreenMagnification_visibleBorder()
+            throws InterruptedException, RemoteException {
         CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
         CountDownLatch animationEndLatch = new CountDownLatch(1);
         mTransaction.addTransactionCommittedListener(
@@ -119,17 +133,21 @@
                 //Enable fullscreen magnification
                 mFullscreenMagnificationController
                         .onFullscreenMagnificationActivationChanged(true));
-        assertTrue("Failed to wait for transaction committed",
-                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
-        assertTrue("Failed to wait for animation to be finished",
-                animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertWithMessage("Failed to wait for transaction committed")
+                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
+                .isTrue();
+        assertWithMessage("Failed to wait for animation to be finished")
+                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+                .isTrue();
         verify(mShowHideBorderAnimator).start();
+        verify(mIWindowManager)
+                .watchRotation(any(IRotationWatcher.class), eq(Display.DEFAULT_DISPLAY));
         assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
     }
 
     @Test
     public void disableFullscreenMagnification_reverseAnimationAndReleaseScvh()
-            throws InterruptedException {
+            throws InterruptedException, RemoteException {
         CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
         CountDownLatch enableAnimationEndLatch = new CountDownLatch(1);
         CountDownLatch disableAnimationEndLatch = new CountDownLatch(1);
@@ -149,11 +167,12 @@
                 //Enable fullscreen magnification
                 mFullscreenMagnificationController
                         .onFullscreenMagnificationActivationChanged(true));
-        assertTrue("Failed to wait for transaction committed",
-                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
-        assertTrue("Failed to wait for enabling animation to be finished",
-                enableAnimationEndLatch.await(
-                        ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertWithMessage("Failed to wait for transaction committed")
+                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
+                .isTrue();
+        assertWithMessage("Failed to wait for enabling animation to be finished")
+                .that(enableAnimationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+                .isTrue();
         verify(mShowHideBorderAnimator).start();
 
         getInstrumentation().runOnMainSync(() ->
@@ -161,11 +180,12 @@
                 mFullscreenMagnificationController
                         .onFullscreenMagnificationActivationChanged(false));
 
-        assertTrue("Failed to wait for disabling animation to be finished",
-                disableAnimationEndLatch.await(
-                        ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertWithMessage("Failed to wait for disabling animation to be finished")
+                .that(disableAnimationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+                .isTrue();
         verify(mShowHideBorderAnimator).reverse();
         verify(mSurfaceControlViewHost).release();
+        verify(mIWindowManager).removeRotationWatcher(any(IRotationWatcher.class));
     }
 
     @Test
@@ -188,10 +208,12 @@
                 () -> mFullscreenMagnificationController
                             .onFullscreenMagnificationActivationChanged(true));
 
-        assertTrue("Failed to wait for transaction committed",
-                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
-        assertTrue("Failed to wait for animation to be finished",
-                animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertWithMessage("Failed to wait for transaction committed")
+                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
+                .isTrue();
+        assertWithMessage("Failed to wait for animation to be finished")
+                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+                        .isTrue();
         verify(mShowHideBorderAnimator).reverse();
     }
 
@@ -212,10 +234,12 @@
                 //Enable fullscreen magnification
                 mFullscreenMagnificationController
                         .onFullscreenMagnificationActivationChanged(true));
-        assertTrue("Failed to wait for transaction committed",
-                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
-        assertTrue("Failed to wait for animation to be finished",
-                animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertWithMessage("Failed to wait for transaction committed")
+                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
+                .isTrue();
+        assertWithMessage("Failed to wait for animation to be finished")
+                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
+                .isTrue();
         final Rect testWindowBounds = new Rect(
                 mWindowManager.getCurrentWindowMetrics().getBounds());
         testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 5bfb3cf..361a945 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -39,6 +39,7 @@
 import android.provider.Settings;
 import android.testing.TestableLooper;
 import android.view.Display;
+import android.view.IWindowManager;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IMagnificationConnectionCallback;
@@ -99,6 +100,8 @@
     private SecureSettings mSecureSettings;
     @Mock
     private AccessibilityLogger mA11yLogger;
+    @Mock
+    private IWindowManager mIWindowManager;
 
     private IMagnificationConnection mIMagnificationConnection;
     private Magnification mMagnification;
@@ -117,9 +120,10 @@
         mTestableLooper = TestableLooper.get(this);
         assertNotNull(mTestableLooper);
         mMagnification = new Magnification(getContext(),
-                mTestableLooper.getLooper(), getContext().getMainExecutor(), mCommandQueue,
+                mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue,
                 mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
-                mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
+                mDisplayTracker, getContext().getSystemService(DisplayManager.class),
+                mA11yLogger, mIWindowManager);
         mMagnification.mWindowMagnificationControllerSupplier =
                 new FakeWindowMagnificationControllerSupplier(
                         mContext.getSystemService(DisplayManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index ffba25c..17b7e21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -42,6 +42,7 @@
 import android.os.RemoteException;
 import android.testing.TestableLooper;
 import android.view.Display;
+import android.view.IWindowManager;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.IMagnificationConnection;
 import android.view.accessibility.IMagnificationConnectionCallback;
@@ -93,6 +94,8 @@
     private MagnificationSettingsController mMagnificationSettingsController;
     @Mock
     private AccessibilityLogger mA11yLogger;
+    @Mock
+    private IWindowManager mIWindowManager;
 
     @Before
     public void setUp() throws Exception {
@@ -122,10 +125,10 @@
 
         mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
         mMagnification = new Magnification(getContext(),
-                getContext().getMainThreadHandler(), getContext().getMainExecutor(),
+                getContext().getMainThreadHandler(), mContext.getMainExecutor(),
                 mCommandQueue, mModeSwitchesController,
                 mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
-                getContext().getSystemService(DisplayManager.class), mA11yLogger);
+                getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager);
         mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
                 mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
         mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index 1f6a8b8..08f139c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -7,6 +7,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.Utils.toBitmap
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
 import com.android.systemui.biometrics.promptInfo
 import com.android.systemui.biometrics.shared.model.BiometricModalities
@@ -28,6 +29,7 @@
     @Test
     fun biometricRequestFromPromptInfo() {
         val logoRes = R.drawable.ic_cake
+        val logoBitmapFromRes = context.getDrawable(logoRes).toBitmap()
         val logoDescription = "test cake"
         val title = "what"
         val subtitle = "a"
@@ -44,6 +46,7 @@
             BiometricPromptRequest.Biometric(
                 promptInfo(
                     logoRes = logoRes,
+                    logoBitmap = logoBitmapFromRes,
                     logoDescription = logoDescription,
                     title = title,
                     subtitle = subtitle,
@@ -56,7 +59,8 @@
                 OP_PACKAGE_NAME,
             )
 
-        assertThat(request.logoRes).isEqualTo(logoRes)
+        assertThat(request.logoBitmap).isNotNull()
+        assertThat(request.logoBitmap!!.sameAs(logoBitmapFromRes)).isTrue()
         assertThat(request.logoDescription).isEqualTo(logoDescription)
         assertThat(request.title).isEqualTo(title)
         assertThat(request.subtitle).isEqualTo(subtitle)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index db6aba3..7076954 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.biometrics.UdfpsUtils
+import com.android.systemui.biometrics.Utils.toBitmap
 import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
@@ -129,7 +130,7 @@
     private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
     private val defaultLogoIconWithOverrides = context.getDrawable(R.drawable.ic_add)
     private val logoResFromApp = R.drawable.ic_cake
-    private val logoFromApp = context.getDrawable(logoResFromApp)
+    private val logoDrawableFromAppRes = context.getDrawable(logoResFromApp)
     private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
     private val defaultLogoDescription = "Test Android App"
     private val logoDescriptionFromApp = "Test Cake App"
@@ -223,7 +224,7 @@
 
         context.setMockPackageManager(packageManager)
         val resources = context.getOrCreateTestableResources()
-        resources.addOverride(logoResFromApp, logoFromApp)
+        resources.addOverride(logoResFromApp, logoDrawableFromAppRes)
         resources.addOverride(
             R.array.biometric_dialog_package_names_for_logo_with_overrides,
             arrayOf(packageNameForLogoWithOverrides)
@@ -1251,7 +1252,7 @@
     @Test
     @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun descriptionOverriddenByVerticalListContentView() =
-        runGenericTest(contentView = promptContentView, description = "test description") {
+        runGenericTest(description = "test description", contentView = promptContentView) {
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
 
@@ -1263,8 +1264,8 @@
     @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun descriptionOverriddenByContentViewWithMoreOptionsButton() =
         runGenericTest(
-            contentView = promptContentViewWithMoreOptionsButton,
-            description = "test description"
+            description = "test description",
+            contentView = promptContentViewWithMoreOptionsButton
         ) {
             val contentView by collectLastValue(viewModel.contentView)
             val description by collectLastValue(viewModel.description)
@@ -1324,8 +1325,9 @@
     @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP)
     fun logo_resSetByApp() =
         runGenericTest(logoRes = logoResFromApp) {
+            val expectedBitmap = context.getDrawable(logoResFromApp).toBitmap()
             val logo by collectLastValue(viewModel.logo)
-            assertThat(logo).isEqualTo(logoFromApp)
+            assertThat((logo as BitmapDrawable).bitmap.sameAs(expectedBitmap)).isTrue()
         }
 
     @Test
@@ -1438,7 +1440,8 @@
             descriptionFromApp = description,
             contentViewFromApp = contentView,
             logoResFromApp = logoRes,
-            logoBitmapFromApp = logoBitmap,
+            logoBitmapFromApp =
+                if (logoRes != -1) logoDrawableFromAppRes.toBitmap() else logoBitmap,
             logoDescriptionFromApp = logoDescription,
             packageName = packageName,
         )
@@ -1631,8 +1634,6 @@
 ) {
     val info =
         PromptInfo().apply {
-            logoRes = logoResFromApp
-            logoBitmap = logoBitmapFromApp
             logoDescription = logoDescriptionFromApp
             title = "t"
             subtitle = "s"
@@ -1642,6 +1643,9 @@
             isDeviceCredentialAllowed = allowCredentialFallback
             isConfirmationRequested = requireConfirmation
         }
+    if (logoBitmapFromApp != null) {
+        info.setLogo(logoResFromApp, logoBitmapFromApp)
+    }
 
     setPrompt(
         info,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
index c782e9d..459e41d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/InWindowLauncherUnlockAnimationInteractorTest.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.util.mockTopActivityClassName
 import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.user.domain.UserDomainLayerModule
 import dagger.BindsInstance
 import dagger.Component
 import junit.framework.Assert.assertEquals
@@ -443,6 +444,7 @@
             [
                 SysUITestModule::class,
                 BiometricsDomainLayerModule::class,
+                UserDomainLayerModule::class,
             ]
     )
     interface TestComponent {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
index 33e9b36..c7f4416 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/InWindowLauncherUnlockAnimationManagerTest.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager
 import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
+import com.android.systemui.user.domain.UserDomainLayerModule
 import com.android.systemui.util.mockito.any
 import dagger.BindsInstance
 import dagger.Component
@@ -120,6 +121,7 @@
             [
                 SysUITestModule::class,
                 BiometricsDomainLayerModule::class,
+                UserDomainLayerModule::class,
             ]
     )
     interface TestComponent {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index 130aafb..415cc7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -28,11 +28,12 @@
 import android.widget.TextView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.haptics.qs.QSLongPressEffect
 import com.android.systemui.haptics.qs.qsLongPressEffect
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -46,8 +47,7 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class QSTileViewImplTest : SysuiTestCase() {
 
-    @Mock
-    private lateinit var customDrawable: Drawable
+    @Mock private lateinit var customDrawable: Drawable
 
     private lateinit var tileView: FakeTileView
     private lateinit var customDrawableView: View
@@ -130,9 +130,8 @@
 
         tileView.changeState(state)
 
-        assertThat(state.secondaryLabel as CharSequence).isEqualTo(
-            context.getString(R.string.tile_unavailable)
-        )
+        assertThat(state.secondaryLabel as CharSequence)
+            .isEqualTo(context.getString(R.string.tile_unavailable))
     }
 
     @Test
@@ -143,9 +142,8 @@
 
         tileView.changeState(state)
 
-        assertThat(state.secondaryLabel as CharSequence).isEqualTo(
-            context.getString(R.string.switch_bar_off)
-        )
+        assertThat(state.secondaryLabel as CharSequence)
+            .isEqualTo(context.getString(R.string.switch_bar_off))
     }
 
     @Test
@@ -156,9 +154,8 @@
 
         tileView.changeState(state)
 
-        assertThat(state.secondaryLabel as CharSequence).isEqualTo(
-            context.getString(R.string.switch_bar_on)
-        )
+        assertThat(state.secondaryLabel as CharSequence)
+            .isEqualTo(context.getString(R.string.switch_bar_on))
     }
 
     @Test
@@ -236,11 +233,10 @@
         val offString = "${spec}_off"
         val onString = "${spec}_on"
 
-        context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf(
-            unavailableString,
-            offString,
-            onString
-        ))
+        context.orCreateTestableResources.addOverride(
+            R.array.tile_states_internet,
+            arrayOf(unavailableString, offString, onString)
+        )
 
         // State UNAVAILABLE
         state.secondaryLabel = ""
@@ -342,11 +338,10 @@
     @Test
     fun testDisabledByPolicy_secondaryLabelText() {
         val testA11yLabel = "TEST_LABEL"
-        context.orCreateTestableResources
-                .addOverride(
-                        R.string.accessibility_tile_disabled_by_policy_action_description,
-                        testA11yLabel
-                )
+        context.orCreateTestableResources.addOverride(
+            R.string.accessibility_tile_disabled_by_policy_action_description,
+            testA11yLabel
+        )
 
         val stateDisabledByPolicy = QSTile.State()
         stateDisabledByPolicy.state = Tile.STATE_INACTIVE
@@ -357,10 +352,11 @@
         val info = AccessibilityNodeInfo(tileView)
         tileView.onInitializeAccessibilityNodeInfo(info)
         assertThat(
-                info.actionList.find {
-                        it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id
-                }?.label
-        ).isEqualTo(testA11yLabel)
+                info.actionList
+                    .find { it.id == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id }
+                    ?.label
+            )
+            .isEqualTo(testA11yLabel)
     }
 
     @Test
@@ -375,11 +371,10 @@
         val offString = "${spec}_off"
         val onString = "${spec}_on"
 
-        context.orCreateTestableResources.addOverride(R.array.tile_states_internet, arrayOf(
-                unavailableString,
-                offString,
-                onString
-        ))
+        context.orCreateTestableResources.addOverride(
+            R.array.tile_states_internet,
+            arrayOf(unavailableString, offString, onString)
+        )
 
         tileView.changeState(state)
         assertThat(tileView.stateDescription?.contains(unavailableString)).isTrue()
@@ -412,7 +407,7 @@
     }
 
     @Test
-    fun onStateChange_fromLongPress_to_noLongPress_unBoundsTile() {
+    fun onStateChange_fromLongPress_to_noLongPress_clearsResources() {
         // GIVEN a state that no longer handles long-press
         val state = QSTile.State()
         state.handlesLongClick = false
@@ -420,12 +415,12 @@
         // WHEN the state changes
         tileView.changeState(state)
 
-        // THEN the view binder no longer binds the view to the long-press effect
-        assertThat(tileView.isLongPressEffectBound).isFalse()
+        // THEN the long-press effect resources are not set
+        assertThat(tileView.areLongPressEffectPropertiesSet).isFalse()
     }
 
     @Test
-    fun onStateChange_fromNoLongPress_to_longPress_bindsTile() {
+    fun onStateChange_fromNoLongPress_to_longPress_setsProperties() {
         // GIVEN that the tile has changed to a state that does not handle long-press
         val state = QSTile.State()
         state.handlesLongClick = false
@@ -435,12 +430,12 @@
         state.handlesLongClick = true
         tileView.changeState(state)
 
-        // THEN the view is bounded to the long-press effect
-        assertThat(tileView.isLongPressEffectBound).isTrue()
+        // THEN the long-press effect resources are set
+        assertThat(tileView.areLongPressEffectPropertiesSet).isTrue()
     }
 
     @Test
-    fun onStateChange_withoutLongPressEffect_fromLongPress_to_noLongPress_neverBindsEffect() {
+    fun onStateChange_withoutLongPressEffect_fromLongPress_to_noLongPress_neverSetsProperties() {
         // GIVEN a tile where the long-press effect is null
         tileView = FakeTileView(context, false, null)
 
@@ -451,13 +446,13 @@
         // WHEN the state changes
         tileView.changeState(state)
 
-        // THEN the view binder does not bind the view and no effect is initialized
-        assertThat(tileView.isLongPressEffectBound).isFalse()
+        // THEN the effect properties are not set and the effect is not initialized
+        assertThat(tileView.areLongPressEffectPropertiesSet).isFalse()
         assertThat(tileView.isLongPressEffectInitialized).isFalse()
     }
 
     @Test
-    fun onStateChange_withoutLongPressEffect_fromNoLongPress_to_longPress_neverBindsEffect() {
+    fun onStateChange_withoutLongPressEffect_fromNoLongPress_to_longPress_neverSetsProperties() {
         // GIVEN a tile where the long-press effect is null
         tileView = FakeTileView(context, false, null)
 
@@ -470,8 +465,8 @@
         state.handlesLongClick = true
         tileView.changeState(state)
 
-        // THEN the view binder does not bind the view and no effect is initialized
-        assertThat(tileView.isLongPressEffectBound).isFalse()
+        // THEN the effect properties are not set and the effect is not initialized
+        assertThat(tileView.areLongPressEffectPropertiesSet).isFalse()
         assertThat(tileView.isLongPressEffectInitialized).isFalse()
     }
 
@@ -490,14 +485,15 @@
 
         // THE animation padding corresponds to the tile's growth due to the effect
         val padding = tileView.getPaddingForLaunchAnimation()
-        assertThat(padding).isEqualTo(
-            Rect(
-                -deltaWidth.toInt() / 2,
-                -deltaHeight.toInt() / 2,
-                deltaWidth.toInt() / 2,
-                deltaHeight.toInt() / 2,
+        assertThat(padding)
+            .isEqualTo(
+                Rect(
+                    -deltaWidth.toInt() / 2,
+                    -deltaHeight.toInt() / 2,
+                    deltaWidth.toInt() / 2,
+                    deltaHeight.toInt() / 2,
+                )
             )
-        )
     }
 
     @Test
@@ -536,18 +532,44 @@
         assertThat(tileView.haveLongPressPropertiesBeenReset).isTrue()
     }
 
+    @Test
+    fun onInit_withLongPressEffect_longPressEffectHasTileAndExpandable() {
+        val tile = kosmos.qsTileFactory.createTile("Test Tile")
+        tileView.init(tile)
+
+        assertThat(tileView.isTileAddedToLongPress).isTrue()
+        assertThat(tileView.isExpandableAddedToLongPress).isTrue()
+    }
+
+    @Test
+    fun onInit_withoutLongPressEffect_longPressEffectDoesNotHaveTileAndExpandable() {
+        tileView = FakeTileView(context, false, null)
+        val tile = kosmos.qsTileFactory.createTile("Test Tile")
+        tileView.init(tile)
+
+        assertThat(tileView.isTileAddedToLongPress).isFalse()
+        assertThat(tileView.isExpandableAddedToLongPress).isFalse()
+    }
+
     class FakeTileView(
         context: Context,
         collapsed: Boolean,
-        longPressEffect: QSLongPressEffect?,
-    ) : QSTileViewImpl(
+        private val longPressEffect: QSLongPressEffect?,
+    ) :
+        QSTileViewImpl(
             ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings),
             collapsed,
             longPressEffect,
-    ) {
+        ) {
         var constantLongPressEffectDuration = 500
+        val isTileAddedToLongPress: Boolean
+            get() = longPressEffect?.qsTile != null
+
+        val isExpandableAddedToLongPress: Boolean
+            get() = longPressEffect?.expandable != null
 
         override fun getLongPressEffectDuration(): Int = constantLongPressEffectDuration
+
         fun changeState(state: QSTile.State) {
             handleStateChanged(state)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index fc74586..6e6e311 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -241,7 +241,6 @@
             statusBarWinController,
             sysUiState,
             mock(),
-            mock(),
             userTracker,
             wakefulnessLifecycle,
             uiEventLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 068e166..066ca1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -762,6 +762,7 @@
         // THEN the existing session is reused and views are registered
         verify(smartspaceManager, never()).createSmartspaceSession(any())
         verify(smartspaceView2).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+        verify(smartspaceView2).setTimeChangedDelegate(any())
         verify(smartspaceView2).registerDataProvider(plugin)
         verify(smartspaceView2).registerConfigProvider(configPlugin)
     }
@@ -836,6 +837,7 @@
 
         verify(dateSmartspaceView).setUiSurface(
                 BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+        verify(dateSmartspaceView).setTimeChangedDelegate(any())
         verify(dateSmartspaceView).registerDataProvider(datePlugin)
 
         verify(dateSmartspaceView).setPrimaryTextColor(anyInt())
@@ -848,6 +850,7 @@
 
         verify(weatherSmartspaceView).setUiSurface(
                 BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+        verify(weatherSmartspaceView).setTimeChangedDelegate(any())
         verify(weatherSmartspaceView).registerDataProvider(weatherPlugin)
 
         verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
@@ -859,6 +862,7 @@
         controller.stateChangeListener.onViewAttachedToWindow(view)
 
         verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
+        verify(smartspaceView).setTimeChangedDelegate(any())
         verify(smartspaceView).registerDataProvider(plugin)
         verify(smartspaceView).registerConfigProvider(configPlugin)
         verify(smartspaceSession)
@@ -984,6 +988,10 @@
             override fun setUiSurface(uiSurface: String) {
             }
 
+            override fun setTimeChangedDelegate(
+                delegate: BcSmartspaceDataPlugin.TimeChangedDelegate?
+            ) {}
+
             override fun setDozeAmount(amount: Float) {
             }
 
@@ -1012,6 +1020,10 @@
             override fun setUiSurface(uiSurface: String) {
             }
 
+            override fun setTimeChangedDelegate(
+                delegate: BcSmartspaceDataPlugin.TimeChangedDelegate?
+            ) {}
+
             override fun setDozeAmount(amount: Float) {
             }
 
@@ -1036,6 +1048,10 @@
             override fun setUiSurface(uiSurface: String) {
             }
 
+            override fun setTimeChangedDelegate(
+                delegate: BcSmartspaceDataPlugin.TimeChangedDelegate?
+            ) {}
+
             override fun setDozeAmount(amount: Float) {
             }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index c9f2add..26f5370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.statusbar.notification.shared.byIsSilent
 import com.android.systemui.statusbar.notification.shared.byIsSuppressedFromStatusBar
 import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.user.domain.UserDomainLayerModule
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -156,7 +157,14 @@
 
     private val bubbles: Bubbles = mock()
 
-    @Component(modules = [SysUITestModule::class, BiometricsDomainLayerModule::class])
+    @Component(
+        modules =
+            [
+                SysUITestModule::class,
+                BiometricsDomainLayerModule::class,
+                UserDomainLayerModule::class,
+            ]
+    )
     @SysUISingleton
     interface TestComponent : SysUITestComponent<AlwaysOnDisplayNotificationIconsInteractor> {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index dbdbe65..fac6a4c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -87,6 +87,7 @@
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.FakeSystemClock;
 import com.android.systemui.volume.domain.interactor.VolumePanelNavigationInteractor;
+import com.android.systemui.volume.panel.shared.flag.VolumePanelFlag;
 import com.android.systemui.volume.ui.binder.VolumeDialogMenuIconBinder;
 import com.android.systemui.volume.ui.navigation.VolumeNavigator;
 
@@ -119,7 +120,6 @@
     View mDrawerNormal;
     ViewGroup mDialogRowsView;
     CaptionsToggleImageButton mODICaptionsIcon;
-
     private TestableLooper mTestableLooper;
     private ConfigurationController mConfigurationController;
     private int mOriginalOrientation;
@@ -151,6 +151,8 @@
     private VolumeNavigator mVolumeNavigator;
     @Mock
     private VolumeDialogMenuIconBinder mVolumeDialogMenuIconBinder;
+    @Mock
+    private VolumePanelFlag mVolumePanelFlag;
 
     private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
             new CsdWarningDialog.Factory() {
@@ -211,6 +213,7 @@
                 mCsdWarningDialogFactory,
                 mPostureController,
                 mTestableLooper.getLooper(),
+                mVolumePanelFlag,
                 mDumpManager,
                 mLazySecureSettings,
                 mVibratorHelper,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
index 24603ef..eff99e04 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/haptics/qs/QSLongPressEffectKosmos.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.haptics.qs
 
 import com.android.systemui.haptics.vibratorHelper
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.keyguardStateController
 
 val Kosmos.qsLongPressEffect by
-    Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardInteractor) }
+    Kosmos.Fixture { QSLongPressEffect(vibratorHelper, keyguardStateController) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 6d2d04a..45a14ad 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -62,6 +62,8 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
 import com.android.systemui.statusbar.phone.scrimController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.fakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.wifiInteractor
 import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
 import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
 import com.android.systemui.util.time.systemClock
@@ -130,6 +132,8 @@
     val shadeController by lazy { kosmos.shadeController }
     val shadeRepository by lazy { kosmos.shadeRepository }
     val shadeInteractor by lazy { kosmos.shadeInteractor }
+    val wifiInteractor by lazy { kosmos.wifiInteractor }
+    val fakeWifiRepository by lazy { kosmos.fakeWifiRepository }
 
     val ongoingActivityChipsViewModel by lazy { kosmos.ongoingActivityChipsViewModel }
     val scrimController by lazy { kosmos.scrimController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
index d1fbb5e..066736c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneInteractorKosmos.kt
@@ -16,16 +16,12 @@
 
 package com.android.systemui.scene.domain.interactor
 
-import com.android.compose.animation.scene.SceneKey
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.domain.resolver.HomeSceneFamilyResolver
-import com.android.systemui.scene.domain.resolver.SceneResolver
+import com.android.systemui.scene.domain.resolver.sceneFamilyResolvers
 import com.android.systemui.scene.shared.logger.sceneLogger
-import com.android.systemui.scene.shared.model.SceneFamilies
 
 val Kosmos.sceneInteractor by
     Kosmos.Fixture {
@@ -37,14 +33,3 @@
             deviceUnlockedInteractor = deviceUnlockedInteractor,
         )
     }
-
-val Kosmos.sceneFamilyResolvers: Map<SceneKey, SceneResolver>
-    get() = mapOf(SceneFamilies.Home to homeSceneFamilyResolver)
-
-val Kosmos.homeSceneFamilyResolver by
-    Kosmos.Fixture {
-        HomeSceneFamilyResolver(
-            applicationScope = applicationCoroutineScope,
-            deviceEntryInteractor = deviceEntryInteractor,
-        )
-    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
new file mode 100644
index 0000000..6be1939
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/resolver/SceneResolverKosmos.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.resolver
+
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.scene.shared.model.SceneFamilies
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.sceneFamilyResolvers: Map<SceneKey, SceneResolver>
+    get() =
+        mapOf(
+            SceneFamilies.Home to homeSceneFamilyResolver,
+            SceneFamilies.NotifShade to notifShadeSceneFamilyResolver,
+            SceneFamilies.QuickSettings to quickSettingsSceneFamilyResolver,
+        )
+
+val Kosmos.homeSceneFamilyResolver by
+    Kosmos.Fixture {
+        HomeSceneFamilyResolver(
+            applicationScope = applicationCoroutineScope,
+            deviceEntryInteractor = deviceEntryInteractor,
+        )
+    }
+
+val Kosmos.notifShadeSceneFamilyResolver by
+    Kosmos.Fixture {
+        NotifShadeSceneFamilyResolver(
+            applicationScope = applicationCoroutineScope,
+            shadeInteractor = shadeInteractor,
+        )
+    }
+
+val Kosmos.quickSettingsSceneFamilyResolver by
+    Kosmos.Fixture {
+        QuickSettingsSceneFamilyResolver(
+            applicationScope = applicationCoroutineScope,
+            shadeInteractor = shadeInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
index cc836ac..0bc4d54 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt
@@ -23,7 +23,6 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.statusbar.statusBarStateController
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -50,7 +49,6 @@
             shadeInteractor = shadeInteractor,
             sceneInteractor = sceneInteractor,
             notificationStackScrollLayout = mock<NotificationStackScrollLayout>(),
-            touchLog = mock<LogBuffer>(),
             vibratorHelper = mock<VibratorHelper>(),
             commandQueue = mock<CommandQueue>(),
             statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
index 0a3a2ee..bcea983 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
@@ -25,7 +25,6 @@
 val Kosmos.shadeLockscreenInteractor by
     Kosmos.Fixture {
         ShadeLockscreenInteractorImpl(
-            applicationScope = applicationCoroutineScope,
             backgroundScope = applicationCoroutineScope,
             shadeInteractor = shadeInteractorImpl,
             sceneInteractor = sceneInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
new file mode 100644
index 0000000..8e656cf
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.shared.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeConnectivityRepository: FakeConnectivityRepository by
+    Kosmos.Fixture { FakeConnectivityRepository() }
+val Kosmos.connectivityRepository: ConnectivityRepository by
+    Kosmos.Fixture { fakeConnectivityRepository }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
similarity index 97%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 97c8d5f..709be5e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.kt
new file mode 100644
index 0000000..e44061a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryKosmos.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.statusbar.pipeline.wifi.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeWifiRepository: FakeWifiRepository by Kosmos.Fixture { FakeWifiRepository() }
+val Kosmos.wifiRepository: WifiRepository by Kosmos.Fixture { fakeWifiRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorKosmos.kt
new file mode 100644
index 0000000..7036199
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorKosmos.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.statusbar.pipeline.wifi.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.wifiRepository
+
+val Kosmos.wifiInteractor: WifiInteractor by
+    Kosmos.Fixture {
+        WifiInteractorImpl(
+            connectivityRepository,
+            wifiRepository,
+            applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
index 4f3aee9..fec6ff1 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt
@@ -27,6 +27,7 @@
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import java.util.concurrent.CopyOnWriteArrayList
 
 /**
  * Allows to subscribe to rotation changes. Updates are provided for the display associated to
@@ -41,7 +42,7 @@
     @Assisted private val callbackHandler: Handler,
 ) : CallbackController<RotationChangeProvider.RotationListener> {
 
-    private val listeners = mutableListOf<RotationListener>()
+    private val listeners = CopyOnWriteArrayList<RotationListener>()
 
     private val displayListener = RotationDisplayListener()
     private var lastRotation: Int? = null
diff --git a/packages/WAPPushManager/src/com/android/smspush/WapPushManager.java b/packages/WAPPushManager/src/com/android/smspush/WapPushManager.java
old mode 100755
new mode 100644
diff --git a/packages/WallpaperCropper/res/drawable-hdpi/ic_actionbar_accept.png b/packages/WallpaperCropper/res/drawable-hdpi/ic_actionbar_accept.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/packages/WallpaperCropper/res/drawable-mdpi/ic_actionbar_accept.png b/packages/WallpaperCropper/res/drawable-mdpi/ic_actionbar_accept.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/packages/WallpaperCropper/res/drawable-xhdpi/ic_actionbar_accept.png b/packages/WallpaperCropper/res/drawable-xhdpi/ic_actionbar_accept.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d79d198..73253b2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10207,19 +10207,6 @@
         addStartInfoTimestampInternal(key, timestampNs, userId, callingUid);
     }
 
-    @Override
-    public void reportStartInfoViewTimestamps(long renderThreadDrawStartTimeNs,
-            long framePresentedTimeNs) {
-        int callingUid = Binder.getCallingUid();
-        int userId = UserHandle.getUserId(callingUid);
-        addStartInfoTimestampInternal(
-                ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME,
-                renderThreadDrawStartTimeNs, userId, callingUid);
-        addStartInfoTimestampInternal(
-                ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE,
-                framePresentedTimeNs, userId, callingUid);
-    }
-
     private void addStartInfoTimestampInternal(int key, long timestampNs, int userId, int uid) {
         mProcessList.getAppStartInfoTracker().addTimestampToStart(
                 Settings.getPackageNameForUid(mContext, uid),
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index dc6e2fa3..3042b2a 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -89,6 +89,14 @@
 
     @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
 
+    /**
+     * The max number of records that can be present in {@link mInProgressRecords}.
+     *
+     * The magic number of 5 records is expected to be enough because this covers in progress
+     * activity starts only, of which more than a 1-2 at a time is very uncommon/unlikely.
+     */
+    @VisibleForTesting static final int MAX_IN_PROGRESS_RECORDS = 5;
+
     private static final int APP_START_INFO_MONITORING_MODE_LIST_SIZE = 100;
 
     @VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore";
@@ -147,7 +155,6 @@
     /** The path to the historical proc start info file, persisted in the storage. */
     @VisibleForTesting File mProcStartInfoFile;
 
-
     /**
      * Temporary list of records that have not been completed.
      *
@@ -155,7 +162,12 @@
      */
     @GuardedBy("mLock")
     @VisibleForTesting
-    final ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
+    final ArrayMap<Long, ApplicationStartInfo> mInProgressRecords = new ArrayMap<>();
+
+    /** Temporary list of keys present in {@link mInProgressRecords} for sorting. */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    final ArrayList<Integer> mTemporaryInProgressIndexes = new ArrayList<>();
 
     AppStartInfoTracker() {
         mCallbacks = new SparseArray<>();
@@ -193,6 +205,60 @@
         });
     }
 
+    /**
+     * Trim in progress records structure to acceptable size. To be called after each time a new
+     * record is added.
+     *
+     * This is necessary both for robustness, as well as because the call to
+     * {@link onReportFullyDrawn} which triggers the removal in the success case is not guaranteed.
+     *
+     * <p class="note"> Note: this is the expected path for removal of in progress records for
+     * successful activity triggered starts that don't report fully drawn. It is *not* only an edge
+     * case.</p>
+     */
+    @GuardedBy("mLock")
+    private void maybeTrimInProgressRecordsLocked() {
+        if (mInProgressRecords.size() <= MAX_IN_PROGRESS_RECORDS) {
+            // Size is acceptable, do nothing.
+            return;
+        }
+
+        // Make sure the temporary list is empty.
+        mTemporaryInProgressIndexes.clear();
+
+        // Populate the list with indexes for size of {@link mInProgressRecords}.
+        for (int i = 0; i < mInProgressRecords.size(); i++) {
+            mTemporaryInProgressIndexes.add(i, i);
+        }
+
+        // Sort the index collection by value of the corresponding key in {@link mInProgressRecords}
+        // from smallest to largest.
+        Collections.sort(mTemporaryInProgressIndexes, (a, b) -> Long.compare(
+                mInProgressRecords.keyAt(a), mInProgressRecords.keyAt(b)));
+
+        if (mTemporaryInProgressIndexes.size() == MAX_IN_PROGRESS_RECORDS + 1) {
+            // Only removing a single record so don't bother sorting again as we don't have to worry
+            // about indexes changing.
+            mInProgressRecords.removeAt(mTemporaryInProgressIndexes.get(0));
+        } else {
+            // Removing more than 1 record, remove the records we want to keep from the list and
+            // then sort again so we can remove in reverse order of indexes.
+            mTemporaryInProgressIndexes.subList(
+                    mTemporaryInProgressIndexes.size() - MAX_IN_PROGRESS_RECORDS,
+                    mTemporaryInProgressIndexes.size()).clear();
+            Collections.sort(mTemporaryInProgressIndexes);
+
+            // Remove all remaining record indexes in reverse order to avoid changing the already
+            // calculated indexes.
+            for (int i = mTemporaryInProgressIndexes.size() - 1; i >= 0; i--) {
+                mInProgressRecords.removeAt(mTemporaryInProgressIndexes.get(i));
+            }
+        }
+
+        // Clear the temorary list.
+        mTemporaryInProgressIndexes.clear();
+    }
+
     void onIntentStarted(@NonNull Intent intent, long timestampNanos) {
         synchronized (mLock) {
             if (!mEnabled) {
@@ -211,7 +277,8 @@
             } else {
                 start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY);
             }
-            mInProgRecords.put(timestampNanos, start);
+            mInProgressRecords.put(timestampNanos, start);
+            maybeTrimInProgressRecordsLocked();
         }
     }
 
@@ -220,17 +287,17 @@
             if (!mEnabled) {
                 return;
             }
-            int index = mInProgRecords.indexOfKey(id);
+            int index = mInProgressRecords.indexOfKey(id);
             if (index < 0) {
                 return;
             }
-            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            ApplicationStartInfo info = mInProgressRecords.valueAt(index);
             if (info == null) {
-                mInProgRecords.removeAt(index);
+                mInProgressRecords.removeAt(index);
                 return;
             }
             info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
-            mInProgRecords.removeAt(index);
+            mInProgressRecords.removeAt(index);
         }
     }
 
@@ -239,13 +306,13 @@
             if (!mEnabled) {
                 return;
             }
-            int index = mInProgRecords.indexOfKey(id);
+            int index = mInProgressRecords.indexOfKey(id);
             if (index < 0) {
                 return;
             }
-            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            ApplicationStartInfo info = mInProgressRecords.valueAt(index);
             if (info == null || app == null) {
-                mInProgRecords.removeAt(index);
+                mInProgressRecords.removeAt(index);
                 return;
             }
             info.setStartType((int) temperature);
@@ -254,9 +321,9 @@
             if (newInfo == null) {
                 // newInfo can be null if records are added before load from storage is
                 // complete. In this case the newly added record will be lost.
-                mInProgRecords.removeAt(index);
+                mInProgressRecords.removeAt(index);
             } else {
-                mInProgRecords.setValueAt(index, newInfo);
+                mInProgressRecords.setValueAt(index, newInfo);
             }
         }
     }
@@ -266,17 +333,17 @@
             if (!mEnabled) {
                 return;
             }
-            int index = mInProgRecords.indexOfKey(id);
+            int index = mInProgressRecords.indexOfKey(id);
             if (index < 0) {
                 return;
             }
-            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            ApplicationStartInfo info = mInProgressRecords.valueAt(index);
             if (info == null) {
-                mInProgRecords.removeAt(index);
+                mInProgressRecords.removeAt(index);
                 return;
             }
             info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
-            mInProgRecords.removeAt(index);
+            mInProgressRecords.removeAt(index);
         }
     }
 
@@ -286,13 +353,13 @@
             if (!mEnabled) {
                 return;
             }
-            int index = mInProgRecords.indexOfKey(id);
+            int index = mInProgressRecords.indexOfKey(id);
             if (index < 0) {
                 return;
             }
-            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            ApplicationStartInfo info = mInProgressRecords.valueAt(index);
             if (info == null) {
-                mInProgRecords.removeAt(index);
+                mInProgressRecords.removeAt(index);
                 return;
             }
             info.setLaunchMode(launchMode);
@@ -308,18 +375,18 @@
             if (!mEnabled) {
                 return;
             }
-            int index = mInProgRecords.indexOfKey(id);
+            int index = mInProgressRecords.indexOfKey(id);
             if (index < 0) {
                 return;
             }
-            ApplicationStartInfo info = mInProgRecords.valueAt(index);
+            ApplicationStartInfo info = mInProgressRecords.valueAt(index);
             if (info == null) {
-                mInProgRecords.removeAt(index);
+                mInProgressRecords.removeAt(index);
                 return;
             }
             info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN,
                     timestampNanos);
-            mInProgRecords.removeAt(index);
+            mInProgressRecords.removeAt(index);
         }
     }
 
@@ -964,7 +1031,7 @@
                 mProcStartInfoFile.delete();
             }
             mData.getMap().clear();
-            mInProgRecords.clear();
+            mInProgressRecords.clear();
         }
     }
 
@@ -1128,8 +1195,21 @@
 
             // Records are sorted newest to oldest, grab record at index 0.
             ApplicationStartInfo startInfo = mInfos.get(0);
+            int startupState = startInfo.getStartupState();
 
-            if (!isAddTimestampAllowed(startInfo, key, timestampNs)) {
+            // If startup state is error then don't accept any further timestamps.
+            if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) {
+                if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps.");
+                return;
+            }
+
+            // If startup state is first frame drawn then only accept fully drawn timestamp.
+            if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN
+                    && key != ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Startup state is first frame drawn and timestamp is not fully "
+                            + "drawn, not accepting new timestamps.");
+                }
                 return;
             }
 
@@ -1142,55 +1222,6 @@
             }
         }
 
-        private boolean isAddTimestampAllowed(ApplicationStartInfo startInfo, int key,
-                long timestampNs) {
-            int startupState = startInfo.getStartupState();
-
-            // If startup state is error then don't accept any further timestamps.
-            if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) {
-                if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps.");
-                return false;
-            }
-
-            Map<Integer, Long> timestamps = startInfo.getStartupTimestamps();
-
-            if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN) {
-                switch (key) {
-                    case ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN:
-                        // Allowed, continue to confirm it's not already added.
-                        break;
-                    case ApplicationStartInfo.START_TIMESTAMP_INITIAL_RENDERTHREAD_FRAME:
-                        Long firstFrameTimeNs = timestamps
-                                .get(ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME);
-                        if (firstFrameTimeNs == null) {
-                            // This should never happen. State can't be first frame drawn if first
-                            // frame timestamp was not provided.
-                            return false;
-                        }
-
-                        if (timestampNs > firstFrameTimeNs) {
-                            // Initial renderthread frame has to occur before first frame.
-                            return false;
-                        }
-
-                        // Allowed, continue to confirm it's not already added.
-                        break;
-                    case ApplicationStartInfo.START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE:
-                        // Allowed, continue to confirm it's not already added.
-                        break;
-                    default:
-                        return false;
-                }
-            }
-
-            if (timestamps.get(key) != null) {
-                // Timestamp should not occur more than once for a given start.
-                return false;
-            }
-
-            return true;
-        }
-
         @GuardedBy("mLock")
         void dumpLocked(PrintWriter pw, String prefix, SimpleDateFormat sdf) {
             if (mMonitoringModeEnabled) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 29e0f7a..1ac37ad 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -128,8 +128,10 @@
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.power.stats.BatteryUsageStatsProvider;
 import com.android.server.power.stats.BluetoothPowerStatsProcessor;
+import com.android.server.power.stats.CameraPowerStatsProcessor;
 import com.android.server.power.stats.CpuPowerStatsProcessor;
 import com.android.server.power.stats.FlashlightPowerStatsProcessor;
+import com.android.server.power.stats.GnssPowerStatsProcessor;
 import com.android.server.power.stats.MobileRadioPowerStatsProcessor;
 import com.android.server.power.stats.PhoneCallPowerStatsProcessor;
 import com.android.server.power.stats.PowerStatsAggregator;
@@ -528,8 +530,7 @@
                         AggregatedPowerStatsConfig.STATE_SCREEN,
                         AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
                 .setProcessor(
-                        new AudioPowerStatsProcessor(mPowerProfile,
-                                mPowerStatsUidResolver));
+                        new AudioPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
 
         config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_VIDEO)
                 .trackDeviceStates(
@@ -539,9 +540,7 @@
                         AggregatedPowerStatsConfig.STATE_POWER,
                         AggregatedPowerStatsConfig.STATE_SCREEN,
                         AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
-                .setProcessor(
-                        new VideoPowerStatsProcessor(mPowerProfile,
-                                mPowerStatsUidResolver));
+                .setProcessor(new VideoPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
 
         config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)
                 .trackDeviceStates(
@@ -552,8 +551,29 @@
                         AggregatedPowerStatsConfig.STATE_SCREEN,
                         AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
                 .setProcessor(
-                        new FlashlightPowerStatsProcessor(mPowerProfile,
-                                mPowerStatsUidResolver));
+                        new FlashlightPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CAMERA)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessor(
+                        new CameraPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
+
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_GNSS)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessor(
+                        new GnssPowerStatsProcessor(mPowerProfile, mPowerStatsUidResolver));
         return config;
     }
 
@@ -639,6 +659,12 @@
                 BatteryConsumer.POWER_COMPONENT_FLASHLIGHT,
                 Flags.streamlinedMiscBatteryStats());
 
+        mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_CAMERA,
+                Flags.streamlinedMiscBatteryStats());
+        mBatteryUsageStatsProvider.setPowerStatsExporterEnabled(
+                BatteryConsumer.POWER_COMPONENT_CAMERA,
+                Flags.streamlinedMiscBatteryStats());
+
         mWorker.systemServicesReady();
         mStats.systemServicesReady(mContext);
         mCpuWakeupStats.systemServicesReady();
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index b1b1dba..93bd926 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -212,24 +212,46 @@
     public static final int TOUCH_VIRTUAL = 3;
 
     /**
-     * Diff result: The {@link #state} or {@link #committedState} fields differ.
-     */
-    public static final int DIFF_STATE = 1 << 0;
-
-    /**
      * Diff result: Other fields differ.
      */
-    public static final int DIFF_OTHER = 1 << 1;
+    public static final int DIFF_OTHER = 1 << 0;
+
+    /**
+     * Diff result: The {@link #state} or {@link #committedState} fields differ.
+     */
+    public static final int DIFF_STATE = 1 << 1;
+
+    /**
+     * Diff result: The committed state differs. Note this is slightly different from the state,
+     * which is what most of the device should care about.
+     */
+    public static final int DIFF_COMMITTED_STATE = 1 << 2;
 
     /**
      * Diff result: The color mode fields differ.
      */
-    public static final int DIFF_COLOR_MODE = 1 << 2;
+    public static final int DIFF_COLOR_MODE = 1 << 3;
 
     /**
      * Diff result: The hdr/sdr ratio differs
      */
-    public static final int DIFF_HDR_SDR_RATIO = 1 << 3;
+    public static final int DIFF_HDR_SDR_RATIO = 1 << 4;
+
+    /**
+     * Diff result: The rotation differs
+     */
+    public static final int DIFF_ROTATION = 1 << 5;
+
+    /**
+     * Diff result: The render timings. Note this could be any of {@link #renderFrameRate},
+     * {@link #presentationDeadlineNanos}, or {@link #appVsyncOffsetNanos}.
+     */
+    public static final int DIFF_RENDER_TIMINGS = 1 << 6;
+
+    /**
+     * Diff result: The mode ID differs.
+     */
+    public static final int DIFF_MODE_ID = 1 << 7;
 
     /**
      * Diff result: Catch-all for "everything changed"
@@ -462,21 +484,33 @@
      */
     public int diff(DisplayDeviceInfo other) {
         int diff = 0;
-        if (state != other.state || committedState != other.committedState) {
+        if (state != other.state) {
             diff |= DIFF_STATE;
         }
+        if (committedState != other.committedState) {
+            diff |= DIFF_COMMITTED_STATE;
+        }
         if (colorMode != other.colorMode) {
             diff |= DIFF_COLOR_MODE;
         }
         if (!BrightnessSynchronizer.floatEquals(hdrSdrRatio, other.hdrSdrRatio)) {
             diff |= DIFF_HDR_SDR_RATIO;
         }
+        if (rotation != other.rotation) {
+            diff |= DIFF_ROTATION;
+        }
+        if (renderFrameRate != other.renderFrameRate
+                || presentationDeadlineNanos != other.presentationDeadlineNanos
+                || appVsyncOffsetNanos != other.appVsyncOffsetNanos) {
+            diff |= DIFF_RENDER_TIMINGS;
+        }
+        if (modeId != other.modeId) {
+            diff |= DIFF_MODE_ID;
+        }
         if (!Objects.equals(name, other.name)
                 || !Objects.equals(uniqueId, other.uniqueId)
                 || width != other.width
                 || height != other.height
-                || modeId != other.modeId
-                || renderFrameRate != other.renderFrameRate
                 || defaultModeId != other.defaultModeId
                 || userPreferredModeId != other.userPreferredModeId
                 || !Arrays.equals(supportedModes, other.supportedModes)
@@ -487,12 +521,9 @@
                 || densityDpi != other.densityDpi
                 || xDpi != other.xDpi
                 || yDpi != other.yDpi
-                || appVsyncOffsetNanos != other.appVsyncOffsetNanos
-                || presentationDeadlineNanos != other.presentationDeadlineNanos
                 || flags != other.flags
                 || !Objects.equals(displayCutout, other.displayCutout)
                 || touch != other.touch
-                || rotation != other.rotation
                 || type != other.type
                 || !Objects.equals(address, other.address)
                 || !Objects.equals(deviceProductInfo, other.deviceProductInfo)
diff --git a/services/core/java/com/android/server/display/DisplayDeviceRepository.java b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
index 6164154..086f8a9 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceRepository.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceRepository.java
@@ -21,6 +21,7 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayAddress;
+import android.view.Surface;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.display.DisplayManagerService.SyncRoot;
@@ -179,6 +180,20 @@
             if (diff == DisplayDeviceInfo.DIFF_STATE) {
                 Slog.i(TAG, "Display device changed state: \"" + info.name
                         + "\", " + Display.stateToString(info.state));
+            } else if (diff == DisplayDeviceInfo.DIFF_ROTATION) {
+                Slog.i(TAG, "Display device rotated: \"" + info.name
+                        + "\", " + Surface.rotationToString(info.rotation));
+            } else if (diff
+                    == (DisplayDeviceInfo.DIFF_MODE_ID | DisplayDeviceInfo.DIFF_RENDER_TIMINGS)) {
+                Slog.i(TAG, "Display device changed render timings: \"" + info.name
+                        + "\", renderFrameRate=" + info.renderFrameRate
+                        + ", presentationDeadlineNanos=" + info.presentationDeadlineNanos
+                        + ", appVsyncOffsetNanos=" + info.appVsyncOffsetNanos);
+            } else if (diff == DisplayDeviceInfo.DIFF_COMMITTED_STATE) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Display device changed committed state: \"" + info.name
+                            + "\", " + Display.stateToString(info.committedState));
+                }
             } else if (diff != DisplayDeviceInfo.DIFF_HDR_SDR_RATIO) {
                 Slog.i(TAG, "Display device changed: " + info);
             }
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 2b5241f..b43b35b 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -102,6 +102,9 @@
 
     private DisplayManagerFlags mDisplayManagerFlags;
 
+    // Indicates if the current auto-brightness should be ramped up or down slowly.
+    private boolean mIsSlowChange;
+
     @VisibleForTesting
     AutomaticBrightnessStrategy(Context context, int displayId, Injector injector,
             DisplayManagerFlags displayManagerFlags) {
@@ -172,6 +175,11 @@
                 isValid = true;
             }
         }
+
+        // A change is slow when the auto-brightness was already applied, and there are no new
+        // auto-brightness adjustments from an external client(e.g. Moving the slider). As such,
+        // it is important to record this value before applying the current auto-brightness.
+        mIsSlowChange = hasAppliedAutoBrightness() && !getAutoBrightnessAdjustmentChanged();
         setAutoBrightnessApplied(isValid);
         return isValid;
     }
@@ -284,8 +292,7 @@
                 .setSdrBrightness(brightness)
                 .setBrightnessReason(brightnessReason)
                 .setDisplayBrightnessStrategyName(getName())
-                .setIsSlowChange(hasAppliedAutoBrightness()
-                        && !getAutoBrightnessAdjustmentChanged())
+                .setIsSlowChange(mIsSlowChange)
                 .setBrightnessEvent(brightnessEvent)
                 .setBrightnessAdjustmentFlag(mAutoBrightnessAdjustmentReasonsFlags)
                 .setShouldUpdateScreenBrightnessSetting(
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
old mode 100755
new mode 100644
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
index 8a3a56c..fd3a92e 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
@@ -212,6 +212,16 @@
         }
     }
 
+    public void setChargingPolicy(int policy) throws RemoteException {
+        IHealth service = mLastService.get();
+        if (service == null) return;
+        try {
+            service.setChargingPolicy(policy);
+        } catch (UnsupportedOperationException | ServiceSpecificException ex) {
+            return;
+        }
+    }
+
     private static void traceBegin(String name) {
         Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
     }
diff --git a/services/core/java/com/android/server/health/OWNERS b/services/core/java/com/android/server/health/OWNERS
index 81522fc..44ab7f7 100644
--- a/services/core/java/com/android/server/health/OWNERS
+++ b/services/core/java/com/android/server/health/OWNERS
@@ -1 +1 @@
-file:platform/hardware/interfaces:/health/aidl/OWNERS
+file:platform/hardware/interfaces:/health/OWNERS
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index d5e85da..3673eb0 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -57,11 +57,8 @@
 import java.util.Objects;
 import java.util.Set;
 
-/**
- * Maintains a connection to a particular {@link MediaRoute2ProviderService}.
- */
-final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
-        implements ServiceConnection {
+/** Maintains a connection to a particular {@link MediaRoute2ProviderService}. */
+final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
     private static final String TAG = "MR2ProviderSvcProxy";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
@@ -69,6 +66,7 @@
     private final int mUserId;
     private final Handler mHandler;
     private final boolean mIsSelfScanOnlyProvider;
+    private final ServiceConnection mServiceConnection = new ServiceConnectionImpl();
 
     // Connection state
     private boolean mRunning;
@@ -303,9 +301,12 @@
             Intent service = new Intent(MediaRoute2ProviderService.SERVICE_INTERFACE);
             service.setComponent(mComponentName);
             try {
-                mBound = mContext.bindServiceAsUser(service, this,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
-                        new UserHandle(mUserId));
+                mBound =
+                        mContext.bindServiceAsUser(
+                                service,
+                                mServiceConnection,
+                                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                                new UserHandle(mUserId));
                 if (!mBound && DEBUG) {
                     Slog.d(TAG, this + ": Bind failed");
                 }
@@ -325,12 +326,11 @@
 
             mBound = false;
             disconnect();
-            mContext.unbindService(this);
+            mContext.unbindService(mServiceConnection);
         }
     }
 
-    @Override
-    public void onServiceConnected(ComponentName name, IBinder service) {
+    private void onServiceConnectedInternal(IBinder service) {
         if (DEBUG) {
             Slog.d(TAG, this + ": Connected");
         }
@@ -354,16 +354,14 @@
         }
     }
 
-    @Override
-    public void onServiceDisconnected(ComponentName name) {
+    private void onServiceDisconnectedInternal() {
         if (DEBUG) {
             Slog.d(TAG, this + ": Service disconnected");
         }
         disconnect();
     }
 
-    @Override
-    public void onBindingDied(ComponentName name) {
+    private void onBindingDiedInternal(ComponentName name) {
         unbind();
         if (Flags.enablePreventionOfKeepAliveRouteProviders()) {
             Slog.w(
@@ -662,6 +660,37 @@
                 pendingTransferCount);
     }
 
+    // All methods in this class are called on the main thread.
+    private final class ServiceConnectionImpl implements ServiceConnection {
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (Flags.enableMr2ServiceNonMainBgThread()) {
+                mHandler.post(() -> onServiceConnectedInternal(service));
+            } else {
+                onServiceConnectedInternal(service);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            if (Flags.enableMr2ServiceNonMainBgThread()) {
+                mHandler.post(() -> onServiceDisconnectedInternal());
+            } else {
+                onServiceDisconnectedInternal();
+            }
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            if (Flags.enableMr2ServiceNonMainBgThread()) {
+                mHandler.post(() -> onBindingDiedInternal(name));
+            } else {
+                onBindingDiedInternal(name);
+            }
+        }
+    }
+
     private final class Connection implements DeathRecipient {
         private final IMediaRoute2ProviderService mService;
         private final ServiceCallbackStub mCallbackStub;
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
old mode 100755
new mode 100644
diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java
index 0afda45..11f2059 100644
--- a/services/core/java/com/android/server/pm/PackageFreezer.java
+++ b/services/core/java/com/android/server/pm/PackageFreezer.java
@@ -62,6 +62,12 @@
 
     PackageFreezer(String packageName, int userId, String killReason,
             PackageManagerService pm, int exitInfoReason, @Nullable InstallRequest request) {
+        this(packageName, userId, killReason, pm, exitInfoReason, request, false);
+    }
+
+    PackageFreezer(String packageName, int userId, String killReason,
+            PackageManagerService pm, int exitInfoReason, @Nullable InstallRequest request,
+            boolean waitAppKilled) {
         mPm = pm;
         mPackageName = packageName;
         mInstallRequest = request;
@@ -77,7 +83,7 @@
             ps = mPm.mSettings.getPackageLPr(mPackageName);
         }
         if (ps != null) {
-            if (Flags.waitApplicationKilled()) {
+            if (waitAppKilled && Flags.waitApplicationKilled()) {
                 mPm.killApplicationSync(ps.getPackageName(), ps.getAppId(), userId, killReason,
                         exitInfoReason);
             } else {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ca84d68..66a93d7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4347,7 +4347,14 @@
 
     public PackageFreezer freezePackage(String packageName, int userId, String killReason,
             int exitInfoReason, InstallRequest request) {
-        return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason, request);
+        return freezePackage(packageName, userId, killReason, exitInfoReason, request,
+                /* waitAppKilled= */ false);
+    }
+
+    private PackageFreezer freezePackage(String packageName, int userId, String killReason,
+            int exitInfoReason, InstallRequest request, boolean waitAppKilled) {
+        return new PackageFreezer(packageName, userId, killReason, this, exitInfoReason, request,
+                waitAppKilled);
     }
 
     public PackageFreezer freezePackageForDelete(String packageName, int userId, int deleteFlags,
@@ -4772,7 +4779,8 @@
                     final boolean succeeded;
                     try (PackageFreezer freezer = freezePackage(packageName, UserHandle.USER_ALL,
                             "clearApplicationUserData",
-                            ApplicationExitInfo.REASON_USER_REQUESTED, null /* request */)) {
+                            ApplicationExitInfo.REASON_USER_REQUESTED, null /* request */,
+                            /* waitAppKilled= */ true)) {
                         try (PackageManagerTracedLock installLock = mInstallLock.acquireLock()) {
                             succeeded = clearApplicationUserDataLIF(snapshotComputer(), packageName,
                                     userId);
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
index 23872d4f..119b659 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
@@ -336,8 +336,13 @@
             final PermissionManagerServiceInternal permissionManagerInternal =
                     LocalServices.getService(PermissionManagerServiceInternal.class);
             for (final int userId : UserManagerService.getInstance().getUserIds()) {
-                packageManagerInternal.forEachPackage(pkg ->
-                        permissionManagerInternal.resetRuntimePermissions(pkg, userId));
+                packageManagerInternal.forEachPackage(pkg -> {
+                    // Filter out packages that don't have app IDs which means they don't have
+                    // permission states either.
+                    if (pkg.getUid() != -1) {
+                        permissionManagerInternal.resetRuntimePermissions(pkg, userId);
+                    }
+                });
             }
         }
 
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 5bae5a4..322ed86 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -298,6 +298,8 @@
     private final MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector;
     private final WifiPowerStatsCollector mWifiPowerStatsCollector;
     private final BluetoothPowerStatsCollector mBluetoothPowerStatsCollector;
+    private final CameraPowerStatsCollector mCameraPowerStatsCollector;
+    private final GnssPowerStatsCollector mGnssPowerStatsCollector;
     private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray();
     private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever =
             new WifiPowerStatsCollector.WifiStatsRetriever() {
@@ -1963,7 +1965,7 @@
 
     private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector,
             MobileRadioPowerStatsCollector.Injector, WifiPowerStatsCollector.Injector,
-            BluetoothPowerStatsCollector.Injector {
+            BluetoothPowerStatsCollector.Injector, EnergyConsumerPowerStatsCollector.Injector {
         private PackageManager mPackageManager;
         private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
         private NetworkStatsManager mNetworkStatsManager;
@@ -5446,7 +5448,10 @@
         final int mappedUid = mapUid(uid);
         if (mGpsNesting == 0) {
             mHistory.recordStateStartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_GPS_ON_FLAG);
+                    HistoryItem.STATE_GPS_ON_FLAG, uid, "gnss");
+            if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
+                mGnssPowerStatsCollector.schedule();
+            }
         }
         mGpsNesting++;
 
@@ -5465,11 +5470,14 @@
         mGpsNesting--;
         if (mGpsNesting == 0) {
             mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE_GPS_ON_FLAG);
+                    HistoryItem.STATE_GPS_ON_FLAG, uid, "gnss");
             mHistory.recordGpsSignalQualityEvent(elapsedRealtimeMs, uptimeMs,
                     GPS_SIGNAL_QUALITY_NONE);
             stopAllGpsSignalQualityTimersLocked(-1, elapsedRealtimeMs);
             mGpsSignalQualityBin = -1;
+            if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
+                mGnssPowerStatsCollector.schedule();
+            }
         }
 
         mFrameworkStatsLogger.gpsScanStateChanged(mapIsolatedUid(uid), workChain, /* on */ false);
@@ -6652,13 +6660,17 @@
         uid = mapUid(uid);
         if (mCameraOnNesting++ == 0) {
             mHistory.recordState2StartEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_CAMERA_FLAG);
+                    HistoryItem.STATE2_CAMERA_FLAG, uid, "camera");
             mCameraOnTimer.startRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteCameraTurnedOnLocked(elapsedRealtimeMs);
 
-        scheduleSyncExternalStatsLocked("camera-on", ExternalStatsSync.UPDATE_CAMERA);
+        if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA)) {
+            mCameraPowerStatsCollector.schedule();
+        } else {
+            scheduleSyncExternalStatsLocked("camera-on", ExternalStatsSync.UPDATE_CAMERA);
+        }
     }
 
     @GuardedBy("this")
@@ -6669,13 +6681,17 @@
         uid = mapUid(uid);
         if (--mCameraOnNesting == 0) {
             mHistory.recordState2StopEvent(elapsedRealtimeMs, uptimeMs,
-                    HistoryItem.STATE2_CAMERA_FLAG);
+                    HistoryItem.STATE2_CAMERA_FLAG, uid, "camera");
             mCameraOnTimer.stopRunningLocked(elapsedRealtimeMs);
         }
         getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
                 .noteCameraTurnedOffLocked(elapsedRealtimeMs);
 
-        scheduleSyncExternalStatsLocked("camera-off", ExternalStatsSync.UPDATE_CAMERA);
+        if (mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA)) {
+            mCameraPowerStatsCollector.schedule();
+        } else {
+            scheduleSyncExternalStatsLocked("camera-off", ExternalStatsSync.UPDATE_CAMERA);
+        }
     }
 
     @GuardedBy("this")
@@ -11281,6 +11297,12 @@
                 mPowerStatsCollectorInjector);
         mBluetoothPowerStatsCollector.addConsumer(this::recordPowerStats);
 
+        mCameraPowerStatsCollector = new CameraPowerStatsCollector(mPowerStatsCollectorInjector);
+        mCameraPowerStatsCollector.addConsumer(this::recordPowerStats);
+
+        mGnssPowerStatsCollector = new GnssPowerStatsCollector(mPowerStatsCollectorInjector);
+        mGnssPowerStatsCollector.addConsumer(this::recordPowerStats);
+
         mStartCount++;
         initTimersAndCounters();
         mOnBattery = mOnBatteryInternal = false;
@@ -14703,6 +14725,14 @@
                 mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_BLUETOOTH));
         mBluetoothPowerStatsCollector.schedule();
 
+        mCameraPowerStatsCollector.setEnabled(
+                mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA));
+        mCameraPowerStatsCollector.schedule();
+
+        mGnssPowerStatsCollector.setEnabled(
+                mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS));
+        mGnssPowerStatsCollector.schedule();
+
         mSystemReady = true;
     }
 
@@ -14721,6 +14751,10 @@
                 return mWifiPowerStatsCollector;
             case BatteryConsumer.POWER_COMPONENT_BLUETOOTH:
                 return mBluetoothPowerStatsCollector;
+            case BatteryConsumer.POWER_COMPONENT_CAMERA:
+                return mCameraPowerStatsCollector;
+            case BatteryConsumer.POWER_COMPONENT_GNSS:
+                return mGnssPowerStatsCollector;
         }
         return null;
     }
@@ -16258,6 +16292,8 @@
         mMobileRadioPowerStatsCollector.forceSchedule();
         mWifiPowerStatsCollector.forceSchedule();
         mBluetoothPowerStatsCollector.forceSchedule();
+        mCameraPowerStatsCollector.forceSchedule();
+        mGnssPowerStatsCollector.forceSchedule();
     }
 
     /**
@@ -16278,6 +16314,8 @@
         mMobileRadioPowerStatsCollector.collectAndDump(pw);
         mWifiPowerStatsCollector.collectAndDump(pw);
         mBluetoothPowerStatsCollector.collectAndDump(pw);
+        mCameraPowerStatsCollector.collectAndDump(pw);
+        mGnssPowerStatsCollector.collectAndDump(pw);
     }
 
     private final Runnable mWriteAsyncRunnable = () -> {
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index ba6e4a9..ce0ee39 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -95,8 +95,12 @@
                 }
                 mPowerCalculators.add(new SensorPowerCalculator(
                         mContext.getSystemService(SensorManager.class)));
-                mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile));
-                mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile));
+                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_GNSS)) {
+                    mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile));
+                }
+                if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_CAMERA)) {
+                    mPowerCalculators.add(new CameraPowerCalculator(mPowerProfile));
+                }
                 if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_FLASHLIGHT)) {
                     mPowerCalculators.add(new FlashlightPowerCalculator(mPowerProfile));
                 }
diff --git a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
index 490bd5e..599e63d 100644
--- a/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
+++ b/services/core/java/com/android/server/power/stats/BinaryStatePowerStatsProcessor.java
@@ -51,7 +51,7 @@
     private long mLastUpdateTimestamp;
 
     private PowerStats.Descriptor mDescriptor;
-    private final BinaryStatePowerStatsLayout mStatsLayout = new BinaryStatePowerStatsLayout();
+    private final BinaryStatePowerStatsLayout mStatsLayout;
     private PowerStats mPowerStats;
     private PowerEstimationPlan mPlan;
     private long[] mTmpDeviceStatsArray;
@@ -59,9 +59,17 @@
 
     BinaryStatePowerStatsProcessor(int powerComponentId,
             PowerStatsUidResolver uidResolver, double averagePowerMilliAmp) {
+        this(powerComponentId, uidResolver, averagePowerMilliAmp,
+                new BinaryStatePowerStatsLayout());
+    }
+
+    BinaryStatePowerStatsProcessor(int powerComponentId,
+            PowerStatsUidResolver uidResolver, double averagePowerMilliAmp,
+            BinaryStatePowerStatsLayout statsLayout) {
         mPowerComponentId = powerComponentId;
         mUsageBasedPowerEstimator = new UsageBasedPowerEstimator(averagePowerMilliAmp);
         mUidResolver = uidResolver;
+        mStatsLayout = statsLayout;
     }
 
     protected abstract @BinaryState int getBinaryState(BatteryStats.HistoryItem item);
@@ -107,7 +115,7 @@
                 mInitiatingUid = mUidResolver.mapUid(item.eventTag.uid);
             }
         } else {
-            recordUsageDuration(item.time);
+            recordUsageDuration(mPowerStats, mInitiatingUid, item.time);
             mInitiatingUid = Process.INVALID_UID;
             if (!mEnergyConsumerSupported) {
                 flushPowerStats(stats, item.time);
@@ -117,20 +125,16 @@
         mLastState = state;
     }
 
-    private void recordUsageDuration(long time) {
-        if (mLastState == STATE_OFF) {
-            return;
-        }
-
+    protected void recordUsageDuration(PowerStats powerStats, int uid, long time) {
         long durationMs = time - mLastStateTimestamp;
         mStatsLayout.setUsageDuration(mPowerStats.stats,
                 mStatsLayout.getUsageDuration(mPowerStats.stats) + durationMs);
 
-        if (mInitiatingUid != Process.INVALID_UID) {
-            long[] uidStats = mPowerStats.uidStats.get(mInitiatingUid);
+        if (uid != Process.INVALID_UID) {
+            long[] uidStats = mPowerStats.uidStats.get(uid);
             if (uidStats == null) {
                 uidStats = new long[mDescriptor.uidStatsArrayLength];
-                mPowerStats.uidStats.put(mInitiatingUid, uidStats);
+                mPowerStats.uidStats.put(uid, uidStats);
                 mStatsLayout.setUidUsageDuration(uidStats, durationMs);
             } else {
                 mStatsLayout.setUsageDuration(mPowerStats.stats,
@@ -143,7 +147,11 @@
     void addPowerStats(PowerComponentAggregatedPowerStats stats, PowerStats powerStats,
             long timestampMs) {
         ensureInitialized();
-        recordUsageDuration(timestampMs);
+
+        if (mLastState == STATE_ON) {
+            recordUsageDuration(mPowerStats, mInitiatingUid, timestampMs);
+        }
+
         long consumedEnergy = mStatsLayout.getConsumedEnergy(powerStats.stats, 0);
         if (consumedEnergy != BatteryStats.POWER_DATA_UNAVAILABLE) {
             mEnergyConsumerSupported = true;
@@ -169,14 +177,16 @@
 
     @Override
     void finish(PowerComponentAggregatedPowerStats stats, long timestampMs) {
-        recordUsageDuration(timestampMs);
+        if (mLastState == STATE_ON) {
+            recordUsageDuration(mPowerStats, mInitiatingUid, timestampMs);
+        }
         flushPowerStats(stats, timestampMs);
 
         if (mPlan == null) {
             mPlan = new PowerEstimationPlan(stats.getConfig());
         }
 
-        computeDevicePowerEstimates(stats);
+        computeDevicePowerEstimates(stats, mPlan, mEnergyConsumerSupported);
         combineDevicePowerEstimates(stats);
 
         List<Integer> uids = new ArrayList<>();
@@ -186,9 +196,10 @@
         computeUidPowerEstimates(stats, uids);
     }
 
-    private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats) {
-        for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) {
-            DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i);
+    protected void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats,
+            PowerEstimationPlan plan, boolean energyConsumerSupported) {
+        for (int i = plan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+            DeviceStateEstimation estimation = plan.deviceStateEstimations.get(i);
             if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
                 continue;
             }
@@ -196,7 +207,7 @@
             long duration = mStatsLayout.getUsageDuration(mTmpDeviceStatsArray);
             if (duration > 0) {
                 double power;
-                if (mEnergyConsumerSupported) {
+                if (energyConsumerSupported) {
                     power = uCtoMah(mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, 0));
                 } else {
                     power = mUsageBasedPowerEstimator.calculatePower(duration);
diff --git a/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java
new file mode 100644
index 0000000..8705bd5
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CameraPowerStatsCollector.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+
+public class CameraPowerStatsCollector extends EnergyConsumerPowerStatsCollector {
+
+    CameraPowerStatsCollector(Injector injector) {
+        super(injector, BatteryConsumer.POWER_COMPONENT_CAMERA,
+                BatteryConsumer.powerComponentIdToString(BatteryConsumer.POWER_COMPONENT_CAMERA),
+                EnergyConsumerType.CAMERA, /* energy consumer name */ null,
+                new BinaryStatePowerStatsLayout());
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/CameraPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CameraPowerStatsProcessor.java
new file mode 100644
index 0000000..15c3eb8
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/CameraPowerStatsProcessor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+
+import com.android.internal.os.PowerProfile;
+
+public class CameraPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
+    public CameraPowerStatsProcessor(PowerProfile powerProfile,
+            PowerStatsUidResolver uidResolver) {
+        super(BatteryConsumer.POWER_COMPONENT_CAMERA, uidResolver,
+                powerProfile.getAveragePower(PowerProfile.POWER_CAMERA));
+    }
+
+    @Override
+    protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
+        return (item.states2 & BatteryStats.HistoryItem.STATE2_CAMERA_FLAG) != 0
+                ? STATE_ON
+                : STATE_OFF;
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
new file mode 100644
index 0000000..2021f85
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerPowerStatsCollector.java
@@ -0,0 +1,150 @@
+/*
+ * 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.power.stats;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.Handler;
+import android.os.PersistableBundle;
+import android.util.Slog;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.PowerStats;
+
+import java.util.function.IntSupplier;
+
+public class EnergyConsumerPowerStatsCollector extends PowerStatsCollector {
+    private static final String TAG = "CameraPowerStatsCollector";
+
+    private static final long CAMERA_ACTIVITY_REQUEST_TIMEOUT = 20000;
+
+    private static final long ENERGY_UNSPECIFIED = -1;
+
+    interface Injector {
+        Handler getHandler();
+        Clock getClock();
+        PowerStatsUidResolver getUidResolver();
+        long getPowerStatsCollectionThrottlePeriod(String powerComponentName);
+        ConsumedEnergyRetriever getConsumedEnergyRetriever();
+        IntSupplier getVoltageSupplier();
+    }
+
+    private final Injector mInjector;
+    private final int mPowerComponentId;
+    private final String mPowerComponentName;
+    private final int mEnergyConsumerType;
+    private final String mEnergyConsumerName;
+
+    private final BinaryStatePowerStatsLayout mLayout;
+    private boolean mIsInitialized;
+
+    private PowerStats mPowerStats;
+    private ConsumedEnergyRetriever mConsumedEnergyRetriever;
+    private IntSupplier mVoltageSupplier;
+    private int[] mEnergyConsumerIds = new int[0];
+    private long mLastConsumedEnergyUws = ENERGY_UNSPECIFIED;
+    private int mLastVoltageMv;
+    private long mLastUpdateTimestamp;
+    private boolean mFirstCollection = true;
+
+    EnergyConsumerPowerStatsCollector(Injector injector, int powerComponentId,
+            String powerComponentName, @EnergyConsumerType int energyConsumerType,
+            String energyConsumerName, BinaryStatePowerStatsLayout statsLayout) {
+        super(injector.getHandler(),
+                injector.getPowerStatsCollectionThrottlePeriod(powerComponentName),
+                injector.getUidResolver(), injector.getClock());
+        mInjector = injector;
+        mPowerComponentId = powerComponentId;
+        mPowerComponentName = powerComponentName;
+        mEnergyConsumerType = energyConsumerType;
+        mEnergyConsumerName = energyConsumerName;
+        mLayout = statsLayout;
+    }
+
+    private boolean ensureInitialized() {
+        if (mIsInitialized) {
+            return true;
+        }
+
+        if (!isEnabled()) {
+            return false;
+        }
+
+        mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever();
+        mVoltageSupplier = mInjector.getVoltageSupplier();
+        mEnergyConsumerIds = mConsumedEnergyRetriever.getEnergyConsumerIds(mEnergyConsumerType,
+                mEnergyConsumerName);
+
+        PersistableBundle extras = new PersistableBundle();
+        mLayout.toExtras(extras);
+        PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor(
+                mPowerComponentId, mPowerComponentName, mLayout.getDeviceStatsArrayLength(),
+                null, 0, mLayout.getUidStatsArrayLength(),
+                extras);
+        mPowerStats = new PowerStats(powerStatsDescriptor);
+
+        mIsInitialized = true;
+        return true;
+    }
+
+    @Override
+    protected PowerStats collectStats() {
+        if (!ensureInitialized()) {
+            return null;
+        }
+
+        if (mEnergyConsumerIds.length == 0) {
+            return null;
+        }
+
+        long consumedEnergy = 0;
+        int voltageMv = mVoltageSupplier.getAsInt();
+        if (voltageMv <= 0) {
+            Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv
+                    + " mV) when querying energy consumers");
+        } else {
+            long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds);
+            if (energyUws != null) {
+                for (int i = energyUws.length - 1; i >= 0; i--) {
+                    if (energyUws[i] != ENERGY_UNSPECIFIED) {
+                        consumedEnergy += energyUws[i];
+                    }
+                }
+            }
+        }
+
+        long energyDelta = mLastConsumedEnergyUws != ENERGY_UNSPECIFIED
+                ? consumedEnergy - mLastConsumedEnergyUws : 0;
+        mLastConsumedEnergyUws = consumedEnergy;
+        if (energyDelta < 0) {
+            // Likely, restart of powerstats HAL
+            energyDelta = 0;
+        }
+
+        if (energyDelta == 0 && !mFirstCollection) {
+            return null;
+        }
+
+        int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv;
+        mLastVoltageMv = voltageMv;
+        mLayout.setConsumedEnergy(mPowerStats.stats, 0, uJtoUc(energyDelta, averageVoltage));
+        long timestamp = mClock.elapsedRealtime();
+        mPowerStats.durationMs = timestamp - mLastUpdateTimestamp;
+        mLastUpdateTimestamp = timestamp;
+        mFirstCollection = false;
+        return mPowerStats;
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java
new file mode 100644
index 0000000..168a874
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsCollector.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+
+public class GnssPowerStatsCollector extends EnergyConsumerPowerStatsCollector {
+
+    GnssPowerStatsCollector(Injector injector) {
+        super(injector, BatteryConsumer.POWER_COMPONENT_GNSS,
+                BatteryConsumer.powerComponentIdToString(BatteryConsumer.POWER_COMPONENT_GNSS),
+                EnergyConsumerType.GNSS, /* energy consumer name */ null,
+                new GnssPowerStatsLayout());
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsLayout.java
new file mode 100644
index 0000000..9a1317d
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsLayout.java
@@ -0,0 +1,65 @@
+/*
+ * 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.power.stats;
+
+import android.location.GnssSignalQuality;
+import android.os.PersistableBundle;
+
+class GnssPowerStatsLayout extends BinaryStatePowerStatsLayout {
+    private static final String EXTRA_DEVICE_TIME_SIGNAL_LEVEL_POSITION = "dt-sig";
+    private static final String EXTRA_UID_TIME_SIGNAL_LEVEL_POSITION = "ut-sig";
+
+    private int mDeviceSignalLevelTimePosition;
+    private int mUidSignalLevelTimePosition;
+
+    GnssPowerStatsLayout() {
+        mDeviceSignalLevelTimePosition = addDeviceSection(
+                GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS, "level");
+        mUidSignalLevelTimePosition = addUidSection(
+                GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS, "level");
+    }
+
+    @Override
+    public void fromExtras(PersistableBundle extras) {
+        super.fromExtras(extras);
+        mDeviceSignalLevelTimePosition = extras.getInt(EXTRA_DEVICE_TIME_SIGNAL_LEVEL_POSITION);
+        mUidSignalLevelTimePosition = extras.getInt(EXTRA_UID_TIME_SIGNAL_LEVEL_POSITION);
+    }
+
+    @Override
+    public void toExtras(PersistableBundle extras) {
+        super.toExtras(extras);
+        extras.putInt(EXTRA_DEVICE_TIME_SIGNAL_LEVEL_POSITION, mDeviceSignalLevelTimePosition);
+        extras.putInt(EXTRA_UID_TIME_SIGNAL_LEVEL_POSITION, mUidSignalLevelTimePosition);
+    }
+
+    public void setDeviceSignalLevelTime(long[] stats, int signalLevel, long durationMillis) {
+        stats[mDeviceSignalLevelTimePosition + signalLevel] = durationMillis;
+    }
+
+    public long getDeviceSignalLevelTime(long[] stats, int signalLevel) {
+        return stats[mDeviceSignalLevelTimePosition + signalLevel];
+    }
+
+    public void setUidSignalLevelTime(long[] stats, int signalLevel, long durationMillis) {
+        stats[mUidSignalLevelTimePosition + signalLevel] = durationMillis;
+    }
+
+    public long getUidSignalLevelTime(long[] stats, int signalLevel) {
+        return stats[mUidSignalLevelTimePosition + signalLevel];
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
new file mode 100644
index 0000000..572bde9
--- /dev/null
+++ b/services/core/java/com/android/server/power/stats/GnssPowerStatsProcessor.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import android.location.GnssSignalQuality;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Process;
+
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import java.util.Arrays;
+
+public class GnssPowerStatsProcessor extends BinaryStatePowerStatsProcessor {
+    private int mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
+    private long mGnssSignalLevelTimestamp;
+    private final long[] mGnssSignalDurations =
+            new long[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
+    private static final GnssPowerStatsLayout sStatsLayout = new GnssPowerStatsLayout();
+    private final UsageBasedPowerEstimator[] mSignalLevelEstimators =
+            new UsageBasedPowerEstimator[GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS];
+    private final boolean mUseSignalLevelEstimators;
+    private long[] mTmpDeviceStatsArray;
+
+    public GnssPowerStatsProcessor(PowerProfile powerProfile, PowerStatsUidResolver uidResolver) {
+        super(BatteryConsumer.POWER_COMPONENT_GNSS, uidResolver,
+                powerProfile.getAveragePower(PowerProfile.POWER_GPS_ON),
+                sStatsLayout);
+
+        boolean useSignalLevelEstimators = false;
+        for (int level = 0; level < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; level++) {
+            double power = powerProfile.getAveragePower(
+                    PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, level);
+            if (power != 0) {
+                useSignalLevelEstimators = true;
+            }
+            mSignalLevelEstimators[level] = new UsageBasedPowerEstimator(power);
+        }
+        mUseSignalLevelEstimators = useSignalLevelEstimators;
+    }
+
+    @Override
+    protected @BinaryState int getBinaryState(BatteryStats.HistoryItem item) {
+        if ((item.states & BatteryStats.HistoryItem.STATE_GPS_ON_FLAG) == 0) {
+            mGnssSignalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
+            return STATE_OFF;
+        }
+
+        noteGnssSignalLevel(item);
+        return STATE_ON;
+    }
+
+    private void noteGnssSignalLevel(BatteryStats.HistoryItem item) {
+        int signalLevel = (item.states2 & BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_MASK)
+                >> BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
+        if (signalLevel >= GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS) {
+            signalLevel = GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN;
+        }
+        if (signalLevel == mGnssSignalLevel) {
+            return;
+        }
+
+        if (mGnssSignalLevel != GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN) {
+            mGnssSignalDurations[mGnssSignalLevel] += item.time - mGnssSignalLevelTimestamp;
+        }
+        mGnssSignalLevel = signalLevel;
+        mGnssSignalLevelTimestamp = item.time;
+    }
+
+    @Override
+    protected void recordUsageDuration(PowerStats powerStats, int uid, long time) {
+        super.recordUsageDuration(powerStats, uid, time);
+
+        if (mGnssSignalLevel != GnssSignalQuality.GNSS_SIGNAL_QUALITY_UNKNOWN) {
+            mGnssSignalDurations[mGnssSignalLevel] += time - mGnssSignalLevelTimestamp;
+        } else if (mUseSignalLevelEstimators) {
+            // Default GNSS signal quality to GOOD for the purposes of power attribution
+            mGnssSignalDurations[GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD] +=
+                    time - mGnssSignalLevelTimestamp;
+        }
+
+        for (int level = 0; level < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; level++) {
+            long duration = mGnssSignalDurations[level];
+            sStatsLayout.setDeviceSignalLevelTime(powerStats.stats, level, duration);
+            if (uid != Process.INVALID_UID) {
+                long[] uidStats = powerStats.uidStats.get(uid);
+                if (uidStats == null) {
+                    uidStats = new long[powerStats.descriptor.uidStatsArrayLength];
+                    powerStats.uidStats.put(uid, uidStats);
+                    sStatsLayout.setUidSignalLevelTime(uidStats, level, duration);
+                } else {
+                    sStatsLayout.setUidSignalLevelTime(uidStats, level,
+                            sStatsLayout.getUidSignalLevelTime(uidStats, level) + duration);
+                }
+            }
+        }
+
+        mGnssSignalLevelTimestamp = time;
+        Arrays.fill(mGnssSignalDurations, 0);
+    }
+
+    protected void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats,
+            PowerEstimationPlan plan, boolean energyConsumerSupported) {
+        if (!mUseSignalLevelEstimators || energyConsumerSupported) {
+            super.computeDevicePowerEstimates(stats, plan, energyConsumerSupported);
+            return;
+        }
+
+        if (mTmpDeviceStatsArray == null) {
+            mTmpDeviceStatsArray = new long[stats.getPowerStatsDescriptor().statsArrayLength];
+        }
+
+        for (int i = plan.deviceStateEstimations.size() - 1; i >= 0; i--) {
+            DeviceStateEstimation estimation = plan.deviceStateEstimations.get(i);
+            if (!stats.getDeviceStats(mTmpDeviceStatsArray, estimation.stateValues)) {
+                continue;
+            }
+
+            double power = 0;
+            for (int level = 0; level < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; level++) {
+                long duration = sStatsLayout.getDeviceSignalLevelTime(mTmpDeviceStatsArray, level);
+                power += mSignalLevelEstimators[level].calculatePower(duration);
+            }
+            sStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, power);
+            stats.setDeviceStats(estimation.stateValues, mTmpDeviceStatsArray);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index b82c021..d442c61 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -231,10 +231,14 @@
     }
 
     interface ConsumedEnergyRetriever {
-        int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType);
+        int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType, String name);
 
         @Nullable
         long[] getConsumedEnergyUws(int[] energyConsumerIds);
+
+        default int[] getEnergyConsumerIds(@EnergyConsumerType int energyConsumerType) {
+            return getEnergyConsumerIds(energyConsumerType, null);
+        }
     }
 
     static class ConsumedEnergyRetrieverImpl implements ConsumedEnergyRetriever {
@@ -245,7 +249,7 @@
         }
 
         @Override
-        public int[] getEnergyConsumerIds(int energyConsumerType) {
+        public int[] getEnergyConsumerIds(int energyConsumerType, String name) {
             if (mPowerStatsInternal == null) {
                 return new int[0];
             }
@@ -257,7 +261,8 @@
 
             List<EnergyConsumer> energyConsumers = new ArrayList<>();
             for (EnergyConsumer energyConsumer : energyConsumerInfo) {
-                if (energyConsumer.type == energyConsumerType) {
+                if (energyConsumer.type == energyConsumerType
+                        && (name == null || name.equals(energyConsumer.name))) {
                     energyConsumers.add(energyConsumer);
                 }
             }
diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java
old mode 100755
new mode 100644
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index c9395da..3e177c9 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -1299,9 +1299,11 @@
             // The restore windowing mode must be set after the windowing mode is set since
             // Task#setWindowingMode resets the restore windowing mode to WINDOWING_MODE_INVALID.
             requester.mMultiWindowRestoreWindowingMode = restoreWindowingMode;
+            requester.mMultiWindowRestoreParent =
+                    requester.getParent().mRemoteToken.toWindowContainerToken();
         } else {
             targetWindowingMode = requester.mMultiWindowRestoreWindowingMode;
-            requester.setWindowingMode(targetWindowingMode);
+            requester.restoreWindowingMode();
         }
         if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
             requester.setBounds(null);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d9e14340..d38cd88 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -6548,7 +6548,10 @@
         // Schedule an idle timeout in case the app doesn't do it for us.
         mTaskSupervisor.scheduleIdleTimeout(this);
 
-        mTaskSupervisor.reportResumedActivityLocked(this);
+        mTaskSupervisor.mStoppingActivities.remove(this);
+        if (getDisplayArea().allResumedActivitiesComplete()) {
+            mRootWindowContainer.executeAppTransitionForAllDisplay();
+        }
 
         resumeKeyDispatchingLocked();
         final Task rootTask = getRootTask();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3867d2d..b6e6991 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2064,21 +2064,6 @@
         }
     }
 
-    boolean reportResumedActivityLocked(ActivityRecord r) {
-        // A resumed activity cannot be stopping. remove from list
-        mStoppingActivities.remove(r);
-
-        final Task rootTask = r.getRootTask();
-        if (rootTask.getDisplayArea().allResumedActivitiesComplete()) {
-            mRootWindowContainer.ensureActivitiesVisible();
-            // Make sure activity & window visibility should be identical
-            // for all displays in this stage.
-            mRootWindowContainer.executeAppTransitionForAllDisplay();
-            return true;
-        }
-        return false;
-    }
-
     // Called when WindowManager has finished animating the launchingBehind activity to the back.
     private void handleLaunchTaskBehindCompleteLocked(ActivityRecord r) {
         final Task task = r.getTask();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index f91ef1d..0febec9 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -200,6 +200,7 @@
             }
             infoBuilder.setOnBackInvokedCallback(callbackInfo.getCallback());
             infoBuilder.setAnimationCallback(callbackInfo.isAnimationCallback());
+            infoBuilder.setTouchableRegion(window.getFrame());
             mNavigationMonitor.startMonitor(window, navigationObserver);
 
             ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, "
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index c683d4d..f70d2a5 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -538,13 +538,6 @@
                 || !mWindowManager.isKeyguardSecure(mService.getCurrentUserId());
     }
 
-    /**
-     * @return Whether the dream activity is on top of default display.
-     */
-    boolean isShowingDream() {
-        return getDisplayState(DEFAULT_DISPLAY).mShowingDream;
-    }
-
     private void updateKeyguardSleepToken() {
         for (int displayNdx = mRootWindowContainer.getChildCount() - 1;
              displayNdx >= 0; displayNdx--) {
@@ -631,7 +624,6 @@
          * Note that this can be true even if the keyguard is disabled or not showing.
          */
         private boolean mOccluded;
-        private boolean mShowingDream;
 
         private ActivityRecord mTopOccludesActivity;
         private ActivityRecord mDismissingKeyguardActivity;
@@ -669,7 +661,6 @@
 
             mRequestDismissKeyguard = false;
             mOccluded = false;
-            mShowingDream = false;
 
             mTopOccludesActivity = null;
             mDismissingKeyguardActivity = null;
@@ -697,21 +688,18 @@
 
                 // Only the top activity may control occluded, as we can't occlude the Keyguard
                 // if the top app doesn't want to occlude it.
-                occludedByActivity = mTopOccludesActivity != null
+                mOccluded = mTopOccludesActivity != null
                         || (mDismissingKeyguardActivity != null
                         && task.topRunningActivity() == mDismissingKeyguardActivity
                         && controller.canShowWhileOccluded(
                                 true /* dismissKeyguard */, false /* showWhenLocked */));
                 // FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD only apply for secondary display.
                 if (mDisplayId != DEFAULT_DISPLAY) {
-                    occludedByActivity |= display.canShowWithInsecureKeyguard()
+                    mOccluded |= display.canShowWithInsecureKeyguard()
                             && controller.canDismissKeyguard();
                 }
             }
 
-            mShowingDream = display.getDisplayPolicy().isShowingDreamLw() && (top != null
-                    && top.getActivityType() == ACTIVITY_TYPE_DREAM);
-            mOccluded = mShowingDream || occludedByActivity;
             mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
                     && !mOccluded && !mKeyguardGoingAway
                     && mDismissingKeyguardActivity != null;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 9c7c41c..4770358 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1898,6 +1898,7 @@
             // Don't do recursive work.
             return;
         }
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RWC_ensureActivitiesVisible");
         mTaskSupervisor.beginActivityVisibilityUpdate();
         try {
             // First the front root tasks. In case any are not fullscreen and are in front of home.
@@ -1907,6 +1908,7 @@
             }
         } finally {
             mTaskSupervisor.endActivityVisibilityUpdate();
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 13883fd..c02a437 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -445,6 +445,7 @@
     int mPrevDisplayId = INVALID_DISPLAY;
 
     int mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE;
+    WindowContainerToken mMultiWindowRestoreParent;
 
     /**
      * Last requested orientation reported to DisplayContent. This is different from {@link
@@ -4634,6 +4635,25 @@
         return TASK;
     }
 
+    /**
+     * Restores to the windowing mode saved when task requested to enter fullscreen using
+     * {@link Activity#requestFullscreenMode} API if it is valid. The task is also reparented to
+     * the previous parent if parent has changed.
+     */
+    void restoreWindowingMode() {
+        if (mMultiWindowRestoreWindowingMode == INVALID_WINDOWING_MODE) {
+            return;
+        }
+        if (!getParent().mRemoteToken.toWindowContainerToken()
+                .equals(mMultiWindowRestoreParent)) {
+            // Restore previous parent if parent has changed.
+            final Task parent = fromWindowContainerToken(mMultiWindowRestoreParent);
+            reparent(parent, MAX_VALUE);
+        }
+
+        setWindowingMode(mMultiWindowRestoreWindowingMode);
+    }
+
     @Override
     public void setWindowingMode(int windowingMode) {
         // Calling Task#setWindowingMode() for leaf task since this is a specialization of
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8a6c73a..b814ccd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3502,10 +3502,11 @@
         if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) {
             throw new SecurityException("Requires CONTROL_KEYGUARD permission");
         }
-        if (!dreamHandlesConfirmKeys() && mAtmService.mKeyguardController.isShowingDream()) {
-            mAtmService.mTaskSupervisor.wakeUp("leaveDream");
-        }
         synchronized (mGlobalLock) {
+            if (!dreamHandlesConfirmKeys()
+                    && getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw()) {
+                mAtmService.mTaskSupervisor.wakeUp("leaveDream");
+            }
             mPolicy.dismissKeyguardLw(callback, message);
         }
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceInfoTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceInfoTest.java
new file mode 100644
index 0000000..bacbf89
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceInfoTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import static com.android.server.display.DisplayDeviceInfo.DIFF_COLOR_MODE;
+import static com.android.server.display.DisplayDeviceInfo.DIFF_COMMITTED_STATE;
+import static com.android.server.display.DisplayDeviceInfo.DIFF_HDR_SDR_RATIO;
+import static com.android.server.display.DisplayDeviceInfo.DIFF_MODE_ID;
+import static com.android.server.display.DisplayDeviceInfo.DIFF_RENDER_TIMINGS;
+import static com.android.server.display.DisplayDeviceInfo.DIFF_ROTATION;
+import static com.android.server.display.DisplayDeviceInfo.DIFF_STATE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.Display;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DisplayDeviceInfoTest {
+
+    @Test
+    public void testDiff_noChange() {
+        var oldDdi = createInfo();
+        var newDdi = createInfo();
+
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(0);
+    }
+
+    @Test
+    public void testDiff_state() {
+        var oldDdi = createInfo();
+        var newDdi = createInfo();
+
+        newDdi.state = Display.STATE_VR;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_STATE);
+    }
+
+    @Test
+    public void testDiff_committedState() {
+        var oldDdi = createInfo();
+        var newDdi = createInfo();
+
+        newDdi.committedState = Display.STATE_UNKNOWN;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_COMMITTED_STATE);
+    }
+
+    @Test
+    public void testDiff_colorMode() {
+        var oldDdi = createInfo();
+        var newDdi = createInfo();
+
+        newDdi.colorMode = Display.COLOR_MODE_DISPLAY_P3;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_COLOR_MODE);
+    }
+
+    @Test
+    public void testDiff_hdrSdrRatio() {
+        var oldDdi = createInfo();
+        var newDdi = createInfo();
+
+        /* First change new ratio to non-NaN */
+        newDdi.hdrSdrRatio = 2.3f;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_HDR_SDR_RATIO);
+
+        /* Then change old to be non-NaN and also distinct */
+        oldDdi.hdrSdrRatio = 1.1f;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_HDR_SDR_RATIO);
+
+        /* Now make the new one NaN and the old one non-NaN */
+        newDdi.hdrSdrRatio = Float.NaN;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_HDR_SDR_RATIO);
+    }
+
+    @Test
+    public void testDiff_rotation() {
+        var oldDdi = createInfo();
+        var newDdi = createInfo();
+
+        newDdi.rotation = Surface.ROTATION_270;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_ROTATION);
+    }
+
+    @Test
+    public void testDiff_frameRate() {
+        var oldDdi = createInfo();
+        var newDdi = createInfo();
+
+        newDdi.renderFrameRate = 123.4f;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_RENDER_TIMINGS);
+        newDdi.renderFrameRate = oldDdi.renderFrameRate;
+
+        newDdi.appVsyncOffsetNanos = 31222221;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_RENDER_TIMINGS);
+        newDdi.appVsyncOffsetNanos = oldDdi.appVsyncOffsetNanos;
+
+        newDdi.presentationDeadlineNanos = 23000000;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_RENDER_TIMINGS);
+    }
+
+    @Test
+    public void testDiff_modeId() {
+        var oldDdi = createInfo();
+        var newDdi = createInfo();
+
+        newDdi.modeId = 9;
+        assertThat(oldDdi.diff(newDdi)).isEqualTo(DIFF_MODE_ID);
+    }
+
+    private static DisplayDeviceInfo createInfo() {
+        var ddi = new DisplayDeviceInfo();
+        ddi.name = "TestDisplayDeviceInfo";
+        ddi.uniqueId = "test:51651561321";
+        ddi.width = 671;
+        ddi.height = 483;
+        ddi.modeId = 2;
+        ddi.renderFrameRate = 68.9f;
+        ddi.supportedModes = new Display.Mode[] {
+                new Display.Mode.Builder().setRefreshRate(68.9f).setResolution(671, 483).build(),
+        };
+        ddi.appVsyncOffsetNanos = 6233332;
+        ddi.presentationDeadlineNanos = 11500000;
+        ddi.rotation = Surface.ROTATION_90;
+        ddi.state = Display.STATE_ON;
+        ddi.committedState = Display.STATE_DOZE_SUSPEND;
+        return ddi;
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index 8a33f34..1d04baa 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -570,6 +570,86 @@
         assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
     }
 
+    @Test
+    public void
+            updateBrightness_constructsDisplayBrightnessState_withNoAdjustmentFlag_isSlowChange() {
+        BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID);
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(
+                mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                mAutomaticBrightnessController);
+        float brightness = 0.4f;
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+        when(mAutomaticBrightnessController.getAutomaticScreenBrightness(brightnessEvent))
+                .thenReturn(brightness);
+
+        // Set the state such that auto-brightness was already applied
+        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(true);
+
+        // Update the auto-brightess validity state to change the isSlowChange flag
+        mAutomaticBrightnessStrategy.isAutoBrightnessValid();
+
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mock(DisplayManagerInternal.DisplayPowerRequest.class);
+
+        DisplayBrightnessState expectedDisplayBrightnessState = new DisplayBrightnessState.Builder()
+                .setBrightness(brightness)
+                .setSdrBrightness(brightness)
+                .setBrightnessReason(brightnessReason)
+                .setDisplayBrightnessStrategyName(mAutomaticBrightnessStrategy.getName())
+                .setIsSlowChange(true)
+                .setBrightnessEvent(brightnessEvent)
+                .setBrightnessAdjustmentFlag(0)
+                .setShouldUpdateScreenBrightnessSetting(true)
+                .setIsUserInitiatedChange(true)
+                .build();
+        DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
+                .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
+                        /* userSetBrightnessChanged= */ true));
+        assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
+    }
+
+
+    @Test
+    public void updateBrightness_autoBrightnessNotApplied_noAdjustments_isNotSlowChange() {
+        BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID);
+        mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(
+                mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags);
+        mAutomaticBrightnessStrategy.setAutomaticBrightnessController(
+                mAutomaticBrightnessController);
+        float brightness = 0.4f;
+        BrightnessReason brightnessReason = new BrightnessReason();
+        brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC);
+        when(mAutomaticBrightnessController.getAutomaticScreenBrightness(brightnessEvent))
+                .thenReturn(brightness);
+
+        // Set the state such that auto-brightness was not already applied
+        mAutomaticBrightnessStrategy.setAutoBrightnessApplied(false);
+
+        // Update the auto-brightess validity state to change the isSlowChange flag
+        mAutomaticBrightnessStrategy.isAutoBrightnessValid();
+
+        DisplayManagerInternal.DisplayPowerRequest displayPowerRequest =
+                mock(DisplayManagerInternal.DisplayPowerRequest.class);
+
+        DisplayBrightnessState expectedDisplayBrightnessState = new DisplayBrightnessState.Builder()
+                .setBrightness(brightness)
+                .setSdrBrightness(brightness)
+                .setBrightnessReason(brightnessReason)
+                .setDisplayBrightnessStrategyName(mAutomaticBrightnessStrategy.getName())
+                .setIsSlowChange(false)
+                .setBrightnessEvent(brightnessEvent)
+                .setBrightnessAdjustmentFlag(0)
+                .setShouldUpdateScreenBrightnessSetting(true)
+                .setIsUserInitiatedChange(true)
+                .build();
+        DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
+                .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
+                        /* userSetBrightnessChanged= */ true));
+        assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
+    }
+
     private void setPendingAutoBrightnessAdjustment(float pendingAutoBrightnessAdjustment) {
         Settings.System.putFloat(mContext.getContentResolver(),
                 Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingAutoBrightnessAdjustment);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index e88e28b..ee96c2a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -74,6 +74,15 @@
     private static final String TAG = ApplicationStartInfoTest.class.getSimpleName();
     private static final ComponentName COMPONENT = new ComponentName("com.android.test", ".Foo");
 
+    private static final int APP_1_UID = 10123;
+    private static final int APP_1_PID_1 = 12345;
+    private static final int APP_1_PID_2 = 12346;
+    private static final int APP_1_DEFINING_UID = 23456;
+    private static final int APP_1_UID_USER_2 = 1010123;
+    private static final int APP_1_PID_USER_2 = 12347;
+    private static final String APP_1_PROCESS_NAME = "com.android.test.stub1:process";
+    private static final String APP_1_PACKAGE_NAME = "com.android.test.stub1";
+
     @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
     @Mock private AppOpsService mAppOpsService;
     @Mock private PackageManagerInternal mPackageManagerInt;
@@ -111,6 +120,12 @@
         // Remove stale instance of PackageManagerInternal if there is any
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+
+        mAppStartInfoTracker.clearProcessStartInfo(true);
+        mAppStartInfoTracker.mAppStartInfoLoaded.set(true);
+        mAppStartInfoTracker.mAppStartInfoHistoryListSize =
+                mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE;
+        doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean());
     }
 
     @After
@@ -120,26 +135,12 @@
 
     @Test
     public void testApplicationStartInfo() throws Exception {
-        mAppStartInfoTracker.clearProcessStartInfo(true);
-        mAppStartInfoTracker.mAppStartInfoLoaded.set(true);
-        mAppStartInfoTracker.mAppStartInfoHistoryListSize =
-                mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE;
         mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
                 AppStartInfoTracker.APP_START_STORE_DIR);
         assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
         mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
                 AppStartInfoTracker.APP_START_INFO_FILE);
 
-        doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean());
-
-        final int app1Uid = 10123;
-        final int app1Pid1 = 12345;
-        final int app1Pid2 = 12346;
-        final int app1DefiningUid = 23456;
-        final int app1UidUser2 = 1010123;
-        final int app1PidUser2 = 12347;
-        final String app1ProcessName = "com.android.test.stub1:process";
-        final String app1PackageName = "com.android.test.stub1";
         final long appStartTimestampIntentStarted = 1000000;
         final long appStartTimestampActivityLaunchFinished = 2000000;
         final long appStartTimestampFirstFrameDrawn = 2500000;
@@ -149,23 +150,23 @@
         final long appStartTimestampRContentProvider = 6000000;
 
         ProcessRecord app = makeProcessRecord(
-                app1Pid1,                    // pid
-                app1Uid,                     // uid
-                app1Uid,                     // packageUid
+                APP_1_PID_1,                 // pid
+                APP_1_UID,                   // uid
+                APP_1_UID,                   // packageUid
                 null,                        // definingUid
-                app1ProcessName,             // processName
-                app1PackageName);            // packageName
+                APP_1_PROCESS_NAME,          // processName
+                APP_1_PACKAGE_NAME);         // packageName
 
         ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
 
         // Case 1: Activity start intent failed
         mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
                 appStartTimestampIntentStarted);
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
         verifyInProgressRecordsSize(1);
         assertEquals(list.size(), 0);
 
-        verifyInProgApplicationStartInfo(
+        verifyInProgressApplicationStartInfo(
                 0,                                                    // index
                 0,                                                    // pid
                 0,                                                    // uid
@@ -179,7 +180,7 @@
 
         mAppStartInfoTracker.onIntentFailed(appStartTimestampIntentStarted);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
         verifyInProgressRecordsSize(0);
         assertEquals(list.size(), 0);
 
@@ -189,24 +190,24 @@
         mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
                 appStartTimestampIntentStarted);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
         verifyInProgressRecordsSize(1);
         assertEquals(list.size(), 0);
 
         mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
                 ApplicationStartInfo.START_TYPE_COLD, app);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
         verifyInProgressRecordsSize(1);
         assertEquals(list.size(), 1);
 
-        verifyInProgApplicationStartInfo(
+        verifyInProgressApplicationStartInfo(
                 0,                                                    // index
-                app1Pid1,                                             // pid
-                app1Uid,                                              // uid
-                app1Uid,                                              // packageUid
+                APP_1_PID_1,                                          // pid
+                APP_1_UID,                                            // uid
+                APP_1_UID,                                            // packageUid
                 null,                                                 // definingUid
-                app1ProcessName,                                      // processName
+                APP_1_PROCESS_NAME,                                   // processName
                 ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
                 ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
                 ApplicationStartInfo.START_TYPE_COLD,                 // state type
@@ -214,17 +215,17 @@
 
         mAppStartInfoTracker.onActivityLaunchCancelled(appStartTimestampIntentStarted);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
         verifyInProgressRecordsSize(0);
         assertEquals(list.size(), 1);
 
         verifyApplicationStartInfo(
                 list.get(0),                                          // info
-                app1Pid1,                                             // pid
-                app1Uid,                                              // uid
-                app1Uid,                                              // packageUid
+                APP_1_PID_1,                                          // pid
+                APP_1_UID,                                            // uid
+                APP_1_UID,                                            // packageUid
                 null,                                                 // definingUid
-                app1ProcessName,                                      // processName
+                APP_1_PROCESS_NAME,                                   // processName
                 ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
                 ApplicationStartInfo.STARTUP_STATE_ERROR,             // startup state
                 ApplicationStartInfo.START_TYPE_COLD,                 // state type
@@ -236,24 +237,24 @@
         mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
                 appStartTimestampIntentStarted);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
         verifyInProgressRecordsSize(1);
         assertEquals(list.size(), 0);
 
         mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
                 ApplicationStartInfo.START_TYPE_COLD, app);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
         verifyInProgressRecordsSize(1);
         assertEquals(list.size(), 1);
 
-        verifyInProgApplicationStartInfo(
+        verifyInProgressApplicationStartInfo(
                 0,                                                    // index
-                app1Pid1,                                             // pid
-                app1Uid,                                              // uid
-                app1Uid,                                              // packageUid
+                APP_1_PID_1,                                          // pid
+                APP_1_UID,                                            // uid
+                APP_1_UID,                                            // packageUid
                 null,                                                 // definingUid
-                app1ProcessName,                                      // processName
+                APP_1_PROCESS_NAME,                                   // processName
                 ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
                 ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
                 ApplicationStartInfo.START_TYPE_COLD,                 // state type
@@ -261,11 +262,11 @@
 
         verifyApplicationStartInfo(
                 list.get(0),                                          // info
-                app1Pid1,                                             // pid
-                app1Uid,                                              // uid
-                app1Uid,                                              // packageUid
+                APP_1_PID_1,                                          // pid
+                APP_1_UID,                                            // uid
+                APP_1_UID,                                            // packageUid
                 null,                                                 // definingUid
-                app1ProcessName,                                      // processName
+                APP_1_PROCESS_NAME,                                   // processName
                 ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
                 ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
                 ApplicationStartInfo.START_TYPE_COLD,                 // state type
@@ -273,20 +274,20 @@
 
         mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT,
                 appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD);
-        mAppStartInfoTracker.addTimestampToStart(app1PackageName, app1Uid,
+        mAppStartInfoTracker.addTimestampToStart(APP_1_PACKAGE_NAME, APP_1_UID,
                 appStartTimestampFirstFrameDrawn, ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
         verifyInProgressRecordsSize(1);
         assertEquals(list.size(), 1);
 
-        verifyInProgApplicationStartInfo(
+        verifyInProgressApplicationStartInfo(
                 0,                                                    // index
-                app1Pid1,                                             // pid
-                app1Uid,                                              // uid
-                app1Uid,                                              // packageUid
+                APP_1_PID_1,                                          // pid
+                APP_1_UID,                                            // uid
+                APP_1_UID,                                            // packageUid
                 null,                                                 // definingUid
-                app1ProcessName,                                      // processName
+                APP_1_PROCESS_NAME,                                   // processName
                 ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
                 ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
                 ApplicationStartInfo.START_TYPE_COLD,                 // state type
@@ -295,17 +296,17 @@
         mAppStartInfoTracker.onReportFullyDrawn(appStartTimestampIntentStarted,
                 appStartTimestampReportFullyDrawn);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_1, 0, list);
         verifyInProgressRecordsSize(0);
         assertEquals(list.size(), 1);
 
         verifyApplicationStartInfo(
                 list.get(0),                                          // info
-                app1Pid1,                                             // pid
-                app1Uid,                                              // uid
-                app1Uid,                                              // packageUid
+                APP_1_PID_1,                                          // pid
+                APP_1_UID,                                            // uid
+                APP_1_UID,                                            // packageUid
                 null,                                                 // definingUid
-                app1ProcessName,                                      // processName
+                APP_1_PROCESS_NAME,                                   // processName
                 ApplicationStartInfo.START_REASON_START_ACTIVITY,     // reason
                 ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
                 ApplicationStartInfo.START_TYPE_COLD,                 // state type
@@ -316,26 +317,26 @@
         // Case 4: Create an other app1 record with different pid started for a service
         sleep(1);
         app = makeProcessRecord(
-                app1Pid2,                    // pid
-                app1Uid,                     // uid
-                app1Uid,                     // packageUid
-                app1DefiningUid,             // definingUid
-                app1ProcessName,             // processName
-                app1PackageName);            // packageName
+                APP_1_PID_2,                 // pid
+                APP_1_UID,                   // uid
+                APP_1_UID,                   // packageUid
+                APP_1_DEFINING_UID,          // definingUid
+                APP_1_PROCESS_NAME,          // processName
+                APP_1_PACKAGE_NAME);         // packageName
         ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
 
         mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, 0, 0, list);
         assertEquals(list.size(), 2);
 
         verifyApplicationStartInfo(
                 list.get(0),                                          // info
-                app1Pid2,                                             // pid
-                app1Uid,                                              // uid
-                app1Uid,                                              // packageUid
-                app1DefiningUid,                                      // definingUid
-                app1ProcessName,                                      // processName
+                APP_1_PID_2,                                          // pid
+                APP_1_UID,                                            // uid
+                APP_1_UID,                                            // packageUid
+                APP_1_DEFINING_UID,                                   // definingUid
+                APP_1_PROCESS_NAME,                                   // processName
                 ApplicationStartInfo.START_REASON_SERVICE,            // reason
                 ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
                 ApplicationStartInfo.START_TYPE_COLD,                 // state type
@@ -344,39 +345,41 @@
         // Case 5: Create an instance of app1 with a different user started for a broadcast
         sleep(1);
         app = makeProcessRecord(
-                app1PidUser2,                    // pid
-                app1UidUser2,                    // uid
-                app1UidUser2,                    // packageUid
+                APP_1_PID_USER_2,                // pid
+                APP_1_UID_USER_2,                // uid
+                APP_1_UID_USER_2,                // packageUid
                 null,                            // definingUid
-                app1ProcessName,                 // processName
-                app1PackageName);                // packageName
+                APP_1_PROCESS_NAME,              // processName
+                APP_1_PACKAGE_NAME);             // packageName
 
         mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
                 buildIntent(COMPONENT), false /* isAlarm */);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID_USER_2, APP_1_PID_USER_2, 0,
+                list);
         assertEquals(list.size(), 1);
 
         verifyApplicationStartInfo(
                 list.get(0),                                          // info
-                app1PidUser2,                                         // pid
-                app1UidUser2,                                         // uid
-                app1UidUser2,                                         // packageUid
+                APP_1_PID_USER_2,                                     // pid
+                APP_1_UID_USER_2,                                     // uid
+                APP_1_UID_USER_2,                                     // packageUid
                 null,                                                 // definingUid
-                app1ProcessName,                                      // processName
+                APP_1_PROCESS_NAME,                                   // processName
                 ApplicationStartInfo.START_REASON_BROADCAST,          // reason
                 ApplicationStartInfo.STARTUP_STATE_STARTED,           // startup state
                 ApplicationStartInfo.START_TYPE_COLD,                 // state type
                 ApplicationStartInfo.LAUNCH_MODE_STANDARD);           // launch mode
 
         // Case 6: User 2 gets removed
-        mAppStartInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false);
+        mAppStartInfoTracker.onPackageRemoved(APP_1_PACKAGE_NAME, APP_1_UID_USER_2, false);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID_USER_2, APP_1_PID_USER_2, 0,
+                list);
         assertEquals(list.size(), 0);
 
         list.clear();
-        mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1PidUser2, 0, list);
+        mAppStartInfoTracker.getStartInfo(APP_1_PACKAGE_NAME, APP_1_UID, APP_1_PID_USER_2, 0, list);
         assertEquals(list.size(), 2);
 
 
@@ -416,7 +419,7 @@
 
         // Case 8: Save and load again
         ArrayList<ApplicationStartInfo> original = new ArrayList<ApplicationStartInfo>();
-        mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, original);
+        mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, original);
         assertTrue(original.size() > 0);
 
         mAppStartInfoTracker.persistProcessStartInfo();
@@ -424,12 +427,12 @@
 
         mAppStartInfoTracker.clearProcessStartInfo(false);
         list.clear();
-        mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+        mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
         assertEquals(0, list.size());
 
         mAppStartInfoTracker.loadExistingProcessStartInfo();
         list.clear();
-        mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+        mAppStartInfoTracker.getStartInfo(null, APP_1_UID, 0, 0, list);
         assertEquals(original.size(), list.size());
 
         for (int i = list.size() - 1; i >= 0; i--) {
@@ -437,6 +440,48 @@
         }
     }
 
+    /**
+     * Test to make sure that in progress records stay within their size limits and discard the
+     * correct records.
+     */
+    @SuppressWarnings("GuardedBy")
+    @Test
+    public void testInProgressRecordsLimit() throws Exception {
+        ProcessRecord app = makeProcessRecord(
+                APP_1_PID_1,                 // pid
+                APP_1_UID,                   // uid
+                APP_1_UID,                   // packageUid
+                null,                        // definingUid
+                APP_1_PROCESS_NAME,          // processName
+                APP_1_PACKAGE_NAME);         // packageName
+
+        // Mock performing 2 x MAX_IN_PROGRESS_RECORDS successful starts and ensure that the list
+        // never exceeds the expected size of MAX_IN_PROGRESS_RECORDS.
+        for (int i = 0; i < AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS * 2; i++) {
+            Long startTime = Long.valueOf(i);
+            mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT), startTime);
+            verifyInProgressRecordsSize(
+                    Math.min(i + 1, AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS));
+
+            mAppStartInfoTracker.onActivityLaunched(startTime, COMPONENT,
+                    ApplicationStartInfo.START_TYPE_COLD, app);
+            verifyInProgressRecordsSize(
+                    Math.min(i + 1, AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS));
+
+            mAppStartInfoTracker.onActivityLaunchFinished(startTime, COMPONENT,
+                    startTime + 100, ApplicationStartInfo.LAUNCH_MODE_STANDARD);
+            verifyInProgressRecordsSize(
+                    Math.min(i + 1, AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS));
+
+            // Make sure that the record added in this iteration is still present.
+            assertTrue(mAppStartInfoTracker.mInProgressRecords.containsKey(startTime));
+        }
+
+        // Confirm that after 2 x MAX_IN_PROGRESS_RECORDS starts only MAX_IN_PROGRESS_RECORDS are
+        // present.
+        verifyInProgressRecordsSize(AppStartInfoTracker.MAX_IN_PROGRESS_RECORDS);
+    }
+
     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
         try {
             Field field = clazz.getDeclaredField(fieldName);
@@ -484,16 +529,16 @@
 
     private void verifyInProgressRecordsSize(int expectedSize) {
         synchronized (mAppStartInfoTracker.mLock) {
-            assertEquals(mAppStartInfoTracker.mInProgRecords.size(), expectedSize);
+            assertEquals(mAppStartInfoTracker.mInProgressRecords.size(), expectedSize);
         }
     }
 
-    private void verifyInProgApplicationStartInfo(int index,
+    private void verifyInProgressApplicationStartInfo(int index,
             Integer pid, Integer uid, Integer packageUid,
             Integer definingUid, String processName,
             Integer reason, Integer startupState, Integer startType, Integer launchMode) {
         synchronized (mAppStartInfoTracker.mLock) {
-            verifyApplicationStartInfo(mAppStartInfoTracker.mInProgRecords.valueAt(index),
+            verifyApplicationStartInfo(mAppStartInfoTracker.mInProgressRecords.valueAt(index),
                     pid, uid, packageUid, definingUid, processName, reason, startupState,
                     startType, launchMode);
         }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index 976cc18..a3f0770 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -91,7 +91,7 @@
         final Parcel parcel = Parcel.obtain();
         parcel.writeParcelable(outBatteryUsageStats, 0);
 
-        assertThat(parcel.dataSize()).isLessThan(10000);
+        assertThat(parcel.dataSize()).isLessThan(12000);
 
         parcel.setDataPosition(0);
 
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java
new file mode 100644
index 0000000..36deb08
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerStatsTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class CameraPowerStatsTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
+    @Rule(order = 1)
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_CAMERA, 100.0)
+            .initMeasuredEnergyStatsLocked();
+
+    private static final double PRECISION = 0.00001;
+    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+    private static final int VOLTAGE_MV = 3500;
+    private static final int ENERGY_CONSUMER_ID = 777;
+
+    private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
+    @Mock
+    private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+
+    EnergyConsumerPowerStatsCollector.Injector mInjector =
+            new EnergyConsumerPowerStatsCollector.Injector() {
+                @Override
+                public Handler getHandler() {
+                    return mStatsRule.getHandler();
+                }
+
+                @Override
+                public Clock getClock() {
+                    return mStatsRule.getMockClock();
+                }
+
+                @Override
+                public PowerStatsUidResolver getUidResolver() {
+                    return mUidResolver;
+                }
+
+                @Override
+                public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+                    return 0;
+                }
+
+                @Override
+                public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+                    return mConsumedEnergyRetriever;
+                }
+
+                @Override
+                public IntSupplier getVoltageSupplier() {
+                    return () -> VOLTAGE_MV;
+                }
+            };
+
+    private MonotonicClock mMonotonicClock;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+    }
+
+    @Test
+    public void energyConsumerModel() {
+        when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.CAMERA, null))
+                .thenReturn(new int[]{ENERGY_CONSUMER_ID});
+        CameraPowerStatsProcessor processor = new CameraPowerStatsProcessor(
+                mStatsRule.getPowerProfile(), mUidResolver);
+
+        PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+        CameraPowerStatsCollector collector = new CameraPowerStatsCollector(mInjector);
+        collector.addConsumer(
+                powerStats -> {
+                    processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
+                });
+        collector.setEnabled(true);
+
+        // Establish a baseline
+        when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+                .thenReturn(new long[]{uCtoUj(10000)});
+        collector.collectAndDeliverStats();
+
+        processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
+
+        // Turn the screen off after 2.5 seconds
+        stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+        processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
+
+        when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+                .thenReturn(new long[]{uCtoUj(2_170_000)});
+        collector.collectAndDeliverStats();
+
+        processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
+
+        mStatsRule.setTime(11_000, 11_000);
+        when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+                .thenReturn(new long[]{uCtoUj(3_610_000)});
+        collector.collectAndDeliverStats();
+
+        processor.finish(stats, 11_000);
+
+        PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+        BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+        statsLayout.fromExtras(descriptor.extras);
+
+        // Total estimated power = 3,600,000 uC = 1.0 mAh
+        // of which 3,000,000 is distributed:
+        //     Screen-on  - 2500/6000 * 2160000 = 900000 uC = 0.25 mAh
+        //     Screen-off - 3500/6000 * 2160000 = 1260000 uC = 0.35 mAh
+        // and 600,000 was fully with screen off:
+        //     Screen-off - 1440000 uC = 0.4 mAh
+        long[] deviceStats = new long[descriptor.statsArrayLength];
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of(0.25);
+
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of(0.35 + 0.4);
+
+        // UID1 =
+        //     2,160,000 uC = 0.6 mAh
+        //     split between three different states
+        //          fg screen-on: 2500/6000
+        //          bg screen-off: 2500/6000
+        //          fgs screen-off: 1000/6000
+        double expectedPower1 = 0.6;
+        long[] uidStats = new long[descriptor.uidStatsArrayLength];
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000);
+
+        // UID2 =
+        //     1440000 mA-ms = 0.4 mAh
+        //     all in the same state
+        double expectedPower2 = 0.4;
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower2);
+
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0);
+    }
+
+    private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
+            int uid) {
+        mStatsRule.setTime(timestamp, timestamp);
+        BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
+        historyItem.time = mMonotonicClock.monotonicTime();
+        historyItem.states2 = stateOn ? BatteryStats.HistoryItem.STATE2_CAMERA_FLAG : 0;
+        if (stateOn) {
+            historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+                    | BatteryStats.HistoryItem.EVENT_FLAG_START;
+        } else {
+            historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+                    | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+        }
+        historyItem.eventTag = historyItem.localEventTag;
+        historyItem.eventTag.uid = uid;
+        historyItem.eventTag.string = "camera";
+        return historyItem;
+    }
+
+    private int[] states(int... states) {
+        return states;
+    }
+
+    private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+            BinaryStatePowerStatsProcessor processor) {
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CAMERA)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessor(processor);
+
+        AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
+        PowerComponentAggregatedPowerStats powerComponentStats =
+                aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_CAMERA);
+        processor.start(powerComponentStats, 0);
+
+        powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+        powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+        powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+        powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+        return powerComponentStats;
+    }
+
+    private static long uCtoUj(long uc) {
+        return (long) (uc * (double) VOLTAGE_MV / 1000);
+    }
+}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
new file mode 100644
index 0000000..8a391c6
--- /dev/null
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerStatsTest.java
@@ -0,0 +1,375 @@
+/*
+ * 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.power.stats;
+
+import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE;
+
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE;
+import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.hardware.power.stats.EnergyConsumerType;
+import android.location.GnssSignalQuality;
+import android.os.BatteryConsumer;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import com.android.internal.os.Clock;
+import com.android.internal.os.MonotonicClock;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.PowerStats;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.IntSupplier;
+
+public class GnssPowerStatsTest {
+    @Rule(order = 0)
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
+    @Rule(order = 1)
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
+            .setAveragePower(PowerProfile.POWER_GPS_ON, 100.0)
+            .setAveragePower(PowerProfile.POWER_GPS_SIGNAL_QUALITY_BASED, new double[]{1000, 100})
+            .initMeasuredEnergyStatsLocked();
+
+    private static final double PRECISION = 0.00001;
+    private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
+    private static final int VOLTAGE_MV = 3500;
+    private static final int ENERGY_CONSUMER_ID = 777;
+
+    private final PowerStatsUidResolver mUidResolver = new PowerStatsUidResolver();
+    @Mock
+    private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever;
+
+    EnergyConsumerPowerStatsCollector.Injector mInjector =
+            new EnergyConsumerPowerStatsCollector.Injector() {
+                @Override
+                public Handler getHandler() {
+                    return mStatsRule.getHandler();
+                }
+
+                @Override
+                public Clock getClock() {
+                    return mStatsRule.getMockClock();
+                }
+
+                @Override
+                public PowerStatsUidResolver getUidResolver() {
+                    return mUidResolver;
+                }
+
+                @Override
+                public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) {
+                    return 0;
+                }
+
+                @Override
+                public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() {
+                    return mConsumedEnergyRetriever;
+                }
+
+                @Override
+                public IntSupplier getVoltageSupplier() {
+                    return () -> VOLTAGE_MV;
+                }
+            };
+
+    private MonotonicClock mMonotonicClock;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mMonotonicClock = new MonotonicClock(0, mStatsRule.getMockClock());
+    }
+
+    @Test
+    public void powerProfileModel() {
+        // ODPM unsupported
+        when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.GNSS, null))
+                .thenReturn(new int[0]);
+        GnssPowerStatsProcessor processor = new GnssPowerStatsProcessor(
+                mStatsRule.getPowerProfile(), mUidResolver);
+
+        PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+        GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
+        collector.addConsumer(
+                powerStats -> {
+                    processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
+                });
+        collector.setEnabled(true);
+
+        // Establish a baseline
+        collector.collectAndDeliverStats();
+
+        processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
+
+        // Turn the screen off after 2.5 seconds
+        stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+        processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
+
+        collector.collectAndDeliverStats();
+
+        processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
+        processor.noteStateChange(stats, buildHistoryItem(7000,
+                GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+        processor.noteStateChange(stats, buildHistoryItem(8000,
+                GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+        mStatsRule.setTime(11_000, 11_000);
+        collector.collectAndDeliverStats();
+
+        processor.finish(stats, 11_000);
+
+        PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+        BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+        statsLayout.fromExtras(descriptor.extras);
+
+        // scr-on, GNSS-good: 2500 * 100 = 250000 mA-ms = 0.06944 mAh
+        // scr-off GNSS=good: 4500 * 100 = 0.12500 mAh
+        // scr-off GNSS=poor: 3000 * 1000 = 0.83333 mAh
+        // scr-off GNSS-on: 0.12500 + 0.83333 = 0.95833 mAh
+        long[] deviceStats = new long[descriptor.statsArrayLength];
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of(0.06944);
+
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of(0.12500 + 0.83333);
+
+        // UID1 =
+        //   scr-on FG: 2500 -> 0.06944 mAh
+        //   scr-off BG: 2500/7500 * 0.95833 = 0.31944 mAh
+        //   scr-off FGS: 1000/7500 * 0.95833 = 0.12777 mAh
+        long[] uidStats = new long[descriptor.uidStatsArrayLength];
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0.06944);
+
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0.31944);
+
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0.12777);
+
+        // UID2 =
+        //   scr-off cached: 4000/7500 * 0.95833 = 0.51111 mAh
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0.51111);
+
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0);
+    }
+
+    @Test
+    public void energyConsumerModel() {
+        when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.GNSS, null))
+                .thenReturn(new int[]{ENERGY_CONSUMER_ID});
+        GnssPowerStatsProcessor processor = new GnssPowerStatsProcessor(
+                mStatsRule.getPowerProfile(), mUidResolver);
+
+        PowerComponentAggregatedPowerStats stats = createAggregatedPowerStats(processor);
+
+        GnssPowerStatsCollector collector = new GnssPowerStatsCollector(mInjector);
+        collector.addConsumer(
+                powerStats -> {
+                    processor.addPowerStats(stats, powerStats, mMonotonicClock.monotonicTime());
+                });
+        collector.setEnabled(true);
+
+        // Establish a baseline
+        when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+                .thenReturn(new long[]{uCtoUj(10000)});
+        collector.collectAndDeliverStats();
+
+        processor.noteStateChange(stats, buildHistoryItem(0, true, APP_UID1));
+
+        // Turn the screen off after 2.5 seconds
+        stats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500);
+        stats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, 5000);
+
+        processor.noteStateChange(stats, buildHistoryItem(6000, false, APP_UID1));
+
+        when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+                .thenReturn(new long[]{uCtoUj(2_170_000)});
+        collector.collectAndDeliverStats();
+
+        processor.noteStateChange(stats, buildHistoryItem(7000, true, APP_UID2));
+        processor.noteStateChange(stats, buildHistoryItem(7000,
+                GnssSignalQuality.GNSS_SIGNAL_QUALITY_GOOD));
+        processor.noteStateChange(stats, buildHistoryItem(8000,
+                GnssSignalQuality.GNSS_SIGNAL_QUALITY_POOR));
+        mStatsRule.setTime(11_000, 11_000);
+        when(mConsumedEnergyRetriever.getConsumedEnergyUws(new int[]{ENERGY_CONSUMER_ID}))
+                .thenReturn(new long[]{uCtoUj(3_610_000)});
+        collector.collectAndDeliverStats();
+
+        processor.finish(stats, 11_000);
+
+        PowerStats.Descriptor descriptor = stats.getPowerStatsDescriptor();
+        BinaryStatePowerStatsLayout statsLayout = new BinaryStatePowerStatsLayout();
+        statsLayout.fromExtras(descriptor.extras);
+
+        // Total estimated power = 3,600,000 uC = 1.0 mAh
+        // of which 3,000,000 is distributed:
+        //     Screen-on  - 2500/6000 * 2160000 = 900000 uC = 0.25 mAh
+        //     Screen-off - 3500/6000 * 2160000 = 1260000 uC = 0.35 mAh
+        // and 600,000 was fully with screen off:
+        //     Screen-off - 1440000 uC = 0.4 mAh
+        long[] deviceStats = new long[descriptor.statsArrayLength];
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of(0.25);
+
+        stats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER));
+        assertThat(statsLayout.getDevicePowerEstimate(deviceStats))
+                .isWithin(PRECISION).of(0.35 + 0.4);
+
+        // UID1 =
+        //     2,160,000 uC = 0.6 mAh
+        //     split between three different states
+        //          fg screen-on: 2500/6000
+        //          bg screen-off: 2500/6000
+        //          fgs screen-off: 1000/6000
+        double expectedPower1 = 0.6;
+        long[] uidStats = new long[descriptor.uidStatsArrayLength];
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower1 * 2500 / 6000);
+
+        stats.getUidStats(uidStats, APP_UID1,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower1 * 1000 / 6000);
+
+        // UID2 =
+        //     1440000 mA-ms = 0.4 mAh
+        //     all in the same state
+        double expectedPower2 = 0.4;
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(expectedPower2);
+
+        stats.getUidStats(uidStats, APP_UID2,
+                states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED));
+        assertThat(statsLayout.getUidPowerEstimate(uidStats))
+                .isWithin(PRECISION).of(0);
+    }
+
+    private BatteryStats.HistoryItem buildHistoryItem(int timestamp, boolean stateOn,
+            int uid) {
+        mStatsRule.setTime(timestamp, timestamp);
+        BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
+        historyItem.time = mMonotonicClock.monotonicTime();
+        historyItem.states = stateOn ? BatteryStats.HistoryItem.STATE_GPS_ON_FLAG : 0;
+        if (stateOn) {
+            historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+                    | BatteryStats.HistoryItem.EVENT_FLAG_START;
+        } else {
+            historyItem.eventCode = BatteryStats.HistoryItem.EVENT_STATE_CHANGE
+                    | BatteryStats.HistoryItem.EVENT_FLAG_FINISH;
+        }
+        historyItem.eventTag = historyItem.localEventTag;
+        historyItem.eventTag.uid = uid;
+        historyItem.eventTag.string = "gnss";
+        return historyItem;
+    }
+
+    private BatteryStats.HistoryItem buildHistoryItem(int timestamp, int signalLevel) {
+        mStatsRule.setTime(timestamp, timestamp);
+        BatteryStats.HistoryItem historyItem = new BatteryStats.HistoryItem();
+        historyItem.time = mMonotonicClock.monotonicTime();
+        historyItem.states = BatteryStats.HistoryItem.STATE_GPS_ON_FLAG;
+        historyItem.states2 =
+                signalLevel << BatteryStats.HistoryItem.STATE2_GPS_SIGNAL_QUALITY_SHIFT;
+        return historyItem;
+    }
+
+    private int[] states(int... states) {
+        return states;
+    }
+
+    private static PowerComponentAggregatedPowerStats createAggregatedPowerStats(
+            BinaryStatePowerStatsProcessor processor) {
+        AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
+        config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_GNSS)
+                .trackDeviceStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN)
+                .trackUidStates(
+                        AggregatedPowerStatsConfig.STATE_POWER,
+                        AggregatedPowerStatsConfig.STATE_SCREEN,
+                        AggregatedPowerStatsConfig.STATE_PROCESS_STATE)
+                .setProcessor(processor);
+
+        AggregatedPowerStats aggregatedPowerStats = new AggregatedPowerStats(config);
+        PowerComponentAggregatedPowerStats powerComponentStats =
+                aggregatedPowerStats.getPowerComponentStats(BatteryConsumer.POWER_COMPONENT_GNSS);
+        processor.start(powerComponentStats, 0);
+
+        powerComponentStats.setState(STATE_POWER, POWER_STATE_OTHER, 0);
+        powerComponentStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0);
+        powerComponentStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
+        powerComponentStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
+
+        return powerComponentStats;
+    }
+
+    private static long uCtoUj(long uc) {
+        return (long) (uc * (double) VOLTAGE_MV / 1000);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageTest.java
old mode 100755
new mode 100644
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
old mode 100755
new mode 100644
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
old mode 100755
new mode 100644
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
old mode 100755
new mode 100644
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
old mode 100755
new mode 100644
diff --git a/telephony/common/com/google/android/mms/package.html b/telephony/common/com/google/android/mms/package.html
old mode 100755
new mode 100644
diff --git a/telephony/common/com/google/android/mms/pdu/PduParser.java b/telephony/common/com/google/android/mms/pdu/PduParser.java
old mode 100755
new mode 100644
diff --git a/telephony/common/com/google/android/mms/pdu/PduPersister.java b/telephony/common/com/google/android/mms/pdu/PduPersister.java
old mode 100755
new mode 100644
diff --git a/telephony/common/com/google/android/mms/pdu/package.html b/telephony/common/com/google/android/mms/pdu/package.html
old mode 100755
new mode 100644
diff --git a/telephony/common/com/google/android/mms/util/package.html b/telephony/common/com/google/android/mms/util/package.html
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/SubscriptionInfo.aidl b/telephony/java/android/telephony/SubscriptionInfo.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/ims/ImsCallSession.java b/telephony/java/android/telephony/ims/ImsCallSession.java
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/DownloadRequest.aidl b/telephony/java/android/telephony/mbms/DownloadRequest.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/FileInfo.aidl b/telephony/java/android/telephony/mbms/FileInfo.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/FileServiceInfo.aidl b/telephony/java/android/telephony/mbms/FileServiceInfo.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl b/telephony/java/android/telephony/mbms/IDownloadProgressListener.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/IDownloadStatusListener.aidl b/telephony/java/android/telephony/mbms/IDownloadStatusListener.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/IGroupCallCallback.aidl b/telephony/java/android/telephony/mbms/IGroupCallCallback.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsDownloadSessionCallback.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsGroupCallSessionCallback.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl b/telephony/java/android/telephony/mbms/IMbmsStreamingSessionCallback.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl b/telephony/java/android/telephony/mbms/IStreamingServiceCallback.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/ServiceInfo.aidl b/telephony/java/android/telephony/mbms/ServiceInfo.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl b/telephony/java/android/telephony/mbms/StreamingServiceInfo.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/UriPathPair.aidl b/telephony/java/android/telephony/mbms/UriPathPair.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsDownloadService.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsGroupCallService.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl b/telephony/java/android/telephony/mbms/vendor/IMbmsStreamingService.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/com/android/internal/telephony/IOns.aidl b/telephony/java/com/android/internal/telephony/IOns.aidl
old mode 100755
new mode 100644
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index a9ebd5c..2158f3d 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -252,4 +252,10 @@
 
     /** The key to specify the emergency service category */
     public static final String EXTRA_EMERGENCY_SERVICE_CATEGORY = "emergency_service_category";
+
+    /** The key to specify the alternate emergency URNs */
+    public static final String EXTRA_EMERGENCY_URNS = "emergency_urns";
+
+    /** The key to specify whether or not to use emergency routing */
+    public static final String EXTRA_USE_EMERGENCY_ROUTING = "use_emergency_routing";
 }
diff --git a/test-mock/api/system-current.txt b/test-mock/api/system-current.txt
index f350957..7d891c8 100644
--- a/test-mock/api/system-current.txt
+++ b/test-mock/api/system-current.txt
@@ -28,7 +28,7 @@
     method @Deprecated public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method @Deprecated public void revokeRuntimePermission(String, String, android.os.UserHandle);
     method @Deprecated public boolean setDefaultBrowserPackageNameAsUser(String, int);
-    method public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String);
+    method @Deprecated public String[] setPackagesSuspended(String[], boolean, android.os.PersistableBundle, android.os.PersistableBundle, String);
     method @Deprecated public void setUpdateAvailable(String, boolean);
     method @Deprecated public boolean updateIntentVerificationStatusAsUser(String, int, int);
     method @Deprecated public void updatePermissionFlags(String, String, int, int, android.os.UserHandle);
diff --git a/tests/AccessoryDisplay/sink/res/drawable-hdpi/ic_app.png b/tests/AccessoryDisplay/sink/res/drawable-hdpi/ic_app.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png b/tests/AccessoryDisplay/source/res/drawable-hdpi/ic_app.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/tests/AttestationVerificationTest/AndroidManifest.xml b/tests/AttestationVerificationTest/AndroidManifest.xml
old mode 100755
new mode 100644
diff --git a/tests/DozeTest/res/drawable-hdpi/ic_app.png b/tests/DozeTest/res/drawable-hdpi/ic_app.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
index a23f211..379b45c 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt
@@ -24,7 +24,6 @@
 import android.tools.flicker.legacy.LegacyFlickerTest
 import android.tools.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.traces.parsers.toFlickerComponent
-import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -175,12 +174,6 @@
         }
     }
 
-    @FlakyTest(bugId = 342596801)
-    @Test
-    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
-        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-    }
-
     @Ignore("Not applicable to this CUJ.")
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() {}
 
diff --git a/tests/Internal/TEST_MAPPING b/tests/Internal/TEST_MAPPING
new file mode 100644
index 0000000..20af028
--- /dev/null
+++ b/tests/Internal/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "InternalTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/OdmApps/app/AndroidManifest.xml b/tests/OdmApps/app/AndroidManifest.xml
old mode 100755
new mode 100644
diff --git a/tests/OdmApps/priv-app/AndroidManifest.xml b/tests/OdmApps/priv-app/AndroidManifest.xml
old mode 100755
new mode 100644
diff --git a/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png b/tests/RemoteDisplayProvider/res/drawable-hdpi/ic_app.png
old mode 100755
new mode 100644
Binary files differ
diff --git a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
index 56dbde0..fff1dd1 100644
--- a/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
+++ b/tests/inputmethod/ConcurrentMultiSessionImeTest/src/com/android/server/inputmethod/multisessiontest/ConcurrentMultiUserTest.java
@@ -39,11 +39,13 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(BedsteadJUnit4.class)
+@Ignore("b/345557347")
 public final class ConcurrentMultiUserTest {
 
     @ClassRule
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
old mode 100755
new mode 100644
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
index 81ac897..8f67fa8 100755
--- a/tools/localedata/extract_icu_data.py
+++ b/tools/localedata/extract_icu_data.py
@@ -22,6 +22,8 @@
 import os.path
 import sys
 
+import xml.etree.ElementTree as ElementTree
+
 
 def get_locale_parts(locale):
     """Split a locale into three parts, for langauge, script, and region."""
@@ -40,42 +42,43 @@
 
 def read_likely_subtags(input_file_name):
     """Read and parse ICU's likelySubtags.txt."""
-    with open(input_file_name) as input_file:
-        likely_script_dict = {
-            # Android's additions for pseudo-locales. These internal codes make
-            # sure that the pseudo-locales would not match other English or
-            # Arabic locales. (We can't use private-use ISO 15924 codes, since
-            # they may be used by apps for other purposes.)
-            "en_XA": "~~~A",
-            "ar_XB": "~~~B",
-            # Removed data from later versions of ICU
-            "ji": "Hebr", # Old code for Yiddish, still used in Java and Android
-        }
-        representative_locales = {
-            # Android's additions
-            "en_Latn_GB", # representative for en_Latn_001
-            "es_Latn_MX", # representative for es_Latn_419
-            "es_Latn_US", # representative for es_Latn_419 (not the best idea,
-                          # but Android has been shipping with it for quite a
-                          # while. Fortunately, MX < US, so if both exist, MX
-                          # would be chosen.)
-        }
-        for line in input_file:
-            line = line.strip(u' \n\uFEFF')
-            if line.startswith('//'):
-                continue
-            if '{' in line and '}' in line:
-                from_locale = line[:line.index('{')]
-                to_locale = line[line.index('"')+1:line.rindex('"')]
-                from_lang, from_scr, from_region = get_locale_parts(from_locale)
-                _, to_scr, to_region = get_locale_parts(to_locale)
-                if from_lang == 'und':
-                    continue  # not very useful for our purposes
-                if from_region is None and to_region not in ['001', 'ZZ']:
-                    representative_locales.add(to_locale)
-                if from_scr is None:
-                    likely_script_dict[from_locale] = to_scr
-        return likely_script_dict, frozenset(representative_locales)
+    likely_script_dict = {
+        # Android's additions for pseudo-locales. These internal codes make
+        # sure that the pseudo-locales would not match other English or
+        # Arabic locales. (We can't use private-use ISO 15924 codes, since
+        # they may be used by apps for other purposes.)
+        "en_XA": "~~~A",
+        "ar_XB": "~~~B",
+        # Removed data from later versions of ICU
+        "ji": "Hebr", # Old code for Yiddish, still used in Java and Android
+    }
+    representative_locales = {
+        # Android's additions
+        "en_Latn_GB", # representative for en_Latn_001
+        "es_Latn_MX", # representative for es_Latn_419
+        "es_Latn_US", # representative for es_Latn_419 (not the best idea,
+        # but Android has been shipping with it for quite a
+        # while. Fortunately, MX < US, so if both exist, MX
+        # would be chosen.)
+    }
+    xml_tree = ElementTree.parse(input_file_name)
+    likely_subtags = xml_tree.find('likelySubtags')
+    for child in likely_subtags:
+        from_locale = child.get('from')
+        to_locale = child.get('to')
+        # print(f'from: {from_locale} to: {to_locale}')
+        from_lang, from_scr, from_region = get_locale_parts(from_locale)
+        _, to_scr, to_region = get_locale_parts(to_locale)
+        if to_locale == "FAIL":
+            continue # "FAIL" cases are not useful here.
+        if from_lang == 'und':
+            continue  # not very useful for our purposes
+        if from_region is None and to_region not in ['001', 'ZZ']:
+            representative_locales.add(to_locale)
+        if from_scr is None:
+            likely_script_dict[from_locale] = to_scr
+
+    return likely_script_dict, frozenset(representative_locales)
 
 
 # From packLanguageOrRegion() in ResourceTypes.cpp
@@ -86,7 +89,7 @@
     elif len(inp) == 2:
         return ord(inp[0]), ord(inp[1])
     else:
-        assert len(inp) == 3
+        assert len(inp) == 3, f'Expects a 3-character string, but "{inp}" '
         base = ord(base)
         first = ord(inp[0]) - base
         second = ord(inp[1]) - base
@@ -161,9 +164,10 @@
     print('});')
 
 
-def read_and_dump_likely_data(icu_data_dir):
+def read_and_dump_likely_data(cldr_source_dir):
     """Read and dump the likely-script data."""
-    likely_subtags_txt = os.path.join(icu_data_dir, 'misc', 'likelySubtags.txt')
+    likely_subtags_txt = os.path.join(cldr_source_dir,
+                                      'common', 'supplemental', 'likelySubtags.xml')
     likely_script_dict, representative_locales = read_likely_subtags(
         likely_subtags_txt)
 
@@ -280,10 +284,11 @@
     icu_data_dir = os.path.join(
         source_root,
         'external', 'icu', 'icu4c', 'source', 'data')
+    cldr_source_dir = os.path.join(source_root, 'external', 'cldr')
 
     print('// Auto-generated by %s' % sys.argv[0])
     print()
-    likely_script_dict = read_and_dump_likely_data(icu_data_dir)
+    likely_script_dict = read_and_dump_likely_data(cldr_source_dir)
     read_and_dump_parent_data(icu_data_dir, likely_script_dict)
 
 
diff --git a/wifi/java/src/android/net/wifi/SoftApConfToXmlMigrationUtil.java b/wifi/java/src/android/net/wifi/SoftApConfToXmlMigrationUtil.java
old mode 100755
new mode 100644
diff --git a/wifi/java/src/android/net/wifi/WifiMigration.java b/wifi/java/src/android/net/wifi/WifiMigration.java
old mode 100755
new mode 100644
diff --git a/wifi/java/src/android/net/wifi/WifiNetworkScoreCache.java b/wifi/java/src/android/net/wifi/WifiNetworkScoreCache.java
old mode 100755
new mode 100644