Merge "Skip restore of apps that have been launched or restored." into main
diff --git a/INPUT_OWNERS b/INPUT_OWNERS
index e02ba77..44b2f38 100644
--- a/INPUT_OWNERS
+++ b/INPUT_OWNERS
@@ -5,3 +5,5 @@
 prabirmsp@google.com
 svv@google.com
 vdevmurari@google.com
+
+per-file Virtual*=file:/services/companion/java/com/android/server/companion/virtual/OWNERS
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index f6a7d2a..da8277c 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -271,7 +271,7 @@
             final IBinder token = new Binder(
                     "android.hardware.input.VirtualDpad:" + config.getInputDeviceName());
             mVirtualDevice.createVirtualDpad(config, token);
-            return new VirtualDpad(mVirtualDevice, token);
+            return new VirtualDpad(config, mVirtualDevice, token);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -283,7 +283,7 @@
             final IBinder token = new Binder(
                     "android.hardware.input.VirtualKeyboard:" + config.getInputDeviceName());
             mVirtualDevice.createVirtualKeyboard(config, token);
-            return new VirtualKeyboard(mVirtualDevice, token);
+            return new VirtualKeyboard(config, mVirtualDevice, token);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -295,7 +295,7 @@
             final IBinder token = new Binder(
                     "android.hardware.input.VirtualMouse:" + config.getInputDeviceName());
             mVirtualDevice.createVirtualMouse(config, token);
-            return new VirtualMouse(mVirtualDevice, token);
+            return new VirtualMouse(config, mVirtualDevice, token);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -308,7 +308,7 @@
             final IBinder token = new Binder(
                     "android.hardware.input.VirtualTouchscreen:" + config.getInputDeviceName());
             mVirtualDevice.createVirtualTouchscreen(config, token);
-            return new VirtualTouchscreen(mVirtualDevice, token);
+            return new VirtualTouchscreen(config, mVirtualDevice, token);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -322,7 +322,7 @@
                     "android.hardware.input.VirtualNavigationTouchpad:"
                             + config.getInputDeviceName());
             mVirtualDevice.createVirtualNavigationTouchpad(config, token);
-            return new VirtualNavigationTouchpad(mVirtualDevice, token);
+            return new VirtualNavigationTouchpad(config, mVirtualDevice, token);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 884d463..f532c4c 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -62,6 +62,8 @@
             "mediaSharedWithParent";
     private static final String ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT =
             "credentialShareableWithParent";
+    private static final String ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE =
+            "authAlwaysRequiredToDisableQuietMode";
     private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent";
     private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible";
 
@@ -80,6 +82,7 @@
             INDEX_DELETE_APP_WITH_PARENT,
             INDEX_ALWAYS_VISIBLE,
             INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE,
+            INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
@@ -97,6 +100,7 @@
     private static final int INDEX_DELETE_APP_WITH_PARENT = 10;
     private static final int INDEX_ALWAYS_VISIBLE = 11;
     private static final int INDEX_HIDE_IN_SETTINGS_IN_QUIET_MODE = 12;
+    private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -329,6 +333,8 @@
             setShowInSettings(orig.getShowInSettings());
             setHideInSettingsInQuietMode(orig.getHideInSettingsInQuietMode());
             setUseParentsContacts(orig.getUseParentsContacts());
+            setAuthAlwaysRequiredToDisableQuietMode(
+                    orig.isAuthAlwaysRequiredToDisableQuietMode());
         }
         if (hasQueryOrManagePermission) {
             // Add items that require QUERY_USERS or stronger.
@@ -611,6 +617,31 @@
     }
     private boolean mCredentialShareableWithParent;
 
+    /**
+     * Returns whether the profile always requires user authentication to disable from quiet mode.
+     *
+     * <p> Settings this field to true will ensure that the credential confirmation activity is
+     * always shown whenever the user requests to disable quiet mode. The behavior of credential
+     * checks is not guaranteed when the property is false and may vary depending on user types.
+     * @hide
+     */
+    public boolean isAuthAlwaysRequiredToDisableQuietMode() {
+        if (isPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE)) {
+            return mAuthAlwaysRequiredToDisableQuietMode;
+        }
+        if (mDefaultProperties != null) {
+            return mDefaultProperties.mAuthAlwaysRequiredToDisableQuietMode;
+        }
+        throw new SecurityException(
+                "You don't have permission to query authAlwaysRequiredToDisableQuietMode");
+    }
+    /** @hide */
+    public void setAuthAlwaysRequiredToDisableQuietMode(boolean val) {
+        this.mAuthAlwaysRequiredToDisableQuietMode = val;
+        setPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE);
+    }
+    private boolean mAuthAlwaysRequiredToDisableQuietMode;
+
     /*
      Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during
      OTA update between user-parent
@@ -693,6 +724,8 @@
                 + getCrossProfileIntentResolutionStrategy()
                 + ", mMediaSharedWithParent=" + isMediaSharedWithParent()
                 + ", mCredentialShareableWithParent=" + isCredentialShareableWithParent()
+                + ", mAuthAlwaysRequiredToDisableQuietMode="
+                + isAuthAlwaysRequiredToDisableQuietMode()
                 + ", mDeleteAppWithParent=" + getDeleteAppWithParent()
                 + ", mAlwaysVisible=" + getAlwaysVisible()
                 + "}";
@@ -720,6 +753,8 @@
         pw.println(prefix + "    mMediaSharedWithParent=" + isMediaSharedWithParent());
         pw.println(prefix + "    mCredentialShareableWithParent="
                 + isCredentialShareableWithParent());
+        pw.println(prefix + "    mAuthAlwaysRequiredToDisableQuietMode="
+                + isAuthAlwaysRequiredToDisableQuietMode());
         pw.println(prefix + "    mDeleteAppWithParent=" + getDeleteAppWithParent());
         pw.println(prefix + "    mAlwaysVisible=" + getAlwaysVisible());
     }
@@ -788,6 +823,9 @@
                 case ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT:
                     setCredentialShareableWithParent(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE:
+                    setAuthAlwaysRequiredToDisableQuietMode(parser.getAttributeBoolean(i));
+                    break;
                 case ATTR_DELETE_APP_WITH_PARENT:
                     setDeleteAppWithParent(parser.getAttributeBoolean(i));
                     break;
@@ -853,6 +891,10 @@
             serializer.attributeBoolean(null, ATTR_CREDENTIAL_SHAREABLE_WITH_PARENT,
                     mCredentialShareableWithParent);
         }
+        if (isPresent(INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE)) {
+            serializer.attributeBoolean(null, ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE,
+                    mAuthAlwaysRequiredToDisableQuietMode);
+        }
         if (isPresent(INDEX_DELETE_APP_WITH_PARENT)) {
             serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT,
                     mDeleteAppWithParent);
@@ -878,6 +920,7 @@
         dest.writeInt(mCrossProfileIntentResolutionStrategy);
         dest.writeBoolean(mMediaSharedWithParent);
         dest.writeBoolean(mCredentialShareableWithParent);
+        dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode);
         dest.writeBoolean(mDeleteAppWithParent);
         dest.writeBoolean(mAlwaysVisible);
     }
@@ -901,6 +944,7 @@
         mCrossProfileIntentResolutionStrategy = source.readInt();
         mMediaSharedWithParent = source.readBoolean();
         mCredentialShareableWithParent = source.readBoolean();
+        mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean();
         mDeleteAppWithParent = source.readBoolean();
         mAlwaysVisible = source.readBoolean();
     }
@@ -941,6 +985,7 @@
                 CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT;
         private boolean mMediaSharedWithParent = false;
         private boolean mCredentialShareableWithParent = false;
+        private boolean mAuthAlwaysRequiredToDisableQuietMode = false;
         private boolean mDeleteAppWithParent = false;
         private boolean mAlwaysVisible = false;
 
@@ -1010,6 +1055,14 @@
             return this;
         }
 
+        /** Sets the value for {@link #mAuthAlwaysRequiredToDisableQuietMode} */
+        public Builder setAuthAlwaysRequiredToDisableQuietMode(
+                boolean authAlwaysRequiredToDisableQuietMode) {
+            mAuthAlwaysRequiredToDisableQuietMode =
+                    authAlwaysRequiredToDisableQuietMode;
+            return this;
+        }
+
         /** Sets the value for {@link #mDeleteAppWithParent}*/
         public Builder setDeleteAppWithParent(boolean deleteAppWithParent) {
             mDeleteAppWithParent = deleteAppWithParent;
@@ -1036,6 +1089,7 @@
                     mCrossProfileIntentResolutionStrategy,
                     mMediaSharedWithParent,
                     mCredentialShareableWithParent,
+                    mAuthAlwaysRequiredToDisableQuietMode,
                     mDeleteAppWithParent,
                     mAlwaysVisible);
         }
@@ -1053,6 +1107,7 @@
             @CrossProfileIntentResolutionStrategy int crossProfileIntentResolutionStrategy,
             boolean mediaSharedWithParent,
             boolean credentialShareableWithParent,
+            boolean authAlwaysRequiredToDisableQuietMode,
             boolean deleteAppWithParent,
             boolean alwaysVisible) {
         mDefaultProperties = null;
@@ -1067,6 +1122,8 @@
         setCrossProfileIntentResolutionStrategy(crossProfileIntentResolutionStrategy);
         setMediaSharedWithParent(mediaSharedWithParent);
         setCredentialShareableWithParent(credentialShareableWithParent);
+        setAuthAlwaysRequiredToDisableQuietMode(
+                authAlwaysRequiredToDisableQuietMode);
         setDeleteAppWithParent(deleteAppWithParent);
         setAlwaysVisible(alwaysVisible);
     }
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index acdc0fa..d33eadc 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -27,7 +27,6 @@
  * <p>
  * This class is not thread-safe.
  * </p>
- * Note that this class is unrelated to {@link SQLiteRawStatement}.
  */
 public final class SQLiteStatement extends SQLiteProgram {
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java
index 8133472..7f2d8a0 100644
--- a/core/java/android/hardware/input/VirtualDpad.java
+++ b/core/java/android/hardware/input/VirtualDpad.java
@@ -52,8 +52,8 @@
                                     KeyEvent.KEYCODE_DPAD_CENTER)));
 
     /** @hide */
-    public VirtualDpad(IVirtualDevice virtualDevice, IBinder token) {
-        super(virtualDevice, token);
+    public VirtualDpad(VirtualDpadConfig config, IVirtualDevice virtualDevice, IBinder token) {
+        super(config, virtualDevice, token);
     }
 
     /**
diff --git a/core/java/android/hardware/input/VirtualInputDevice.java b/core/java/android/hardware/input/VirtualInputDevice.java
index 772ba8e..931e1ff 100644
--- a/core/java/android/hardware/input/VirtualInputDevice.java
+++ b/core/java/android/hardware/input/VirtualInputDevice.java
@@ -42,9 +42,12 @@
      */
     protected final IBinder mToken;
 
+    protected final VirtualInputDeviceConfig mConfig;
+
     /** @hide */
-    VirtualInputDevice(
+    VirtualInputDevice(VirtualInputDeviceConfig config,
             IVirtualDevice virtualDevice, IBinder token) {
+        mConfig = config;
         mVirtualDevice = virtualDevice;
         mToken = token;
     }
@@ -70,4 +73,9 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    @Override
+    public String toString() {
+        return mConfig.toString();
+    }
 }
diff --git a/core/java/android/hardware/input/VirtualInputDeviceConfig.java b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
index d3dacc9..a8caa58 100644
--- a/core/java/android/hardware/input/VirtualInputDeviceConfig.java
+++ b/core/java/android/hardware/input/VirtualInputDeviceConfig.java
@@ -91,6 +91,22 @@
         dest.writeString8(mInputDeviceName);
     }
 
+    @Override
+    public String toString() {
+        return getClass().getName() + "( "
+                + " name=" + mInputDeviceName
+                + " vendorId=" + mVendorId
+                + " productId=" + mProductId
+                + " associatedDisplayId=" + mAssociatedDisplayId
+                + additionalFieldsToString() + ")";
+    }
+
+    /** @hide */
+    @NonNull
+    String additionalFieldsToString() {
+        return "";
+    }
+
     /**
      * A builder for {@link VirtualInputDeviceConfig}
      *
diff --git a/core/java/android/hardware/input/VirtualKeyEvent.java b/core/java/android/hardware/input/VirtualKeyEvent.java
index dc47f08..c0102bf 100644
--- a/core/java/android/hardware/input/VirtualKeyEvent.java
+++ b/core/java/android/hardware/input/VirtualKeyEvent.java
@@ -172,6 +172,7 @@
             KeyEvent.KEYCODE_BREAK,
             KeyEvent.KEYCODE_BACK,
             KeyEvent.KEYCODE_FORWARD,
+            KeyEvent.KEYCODE_LANGUAGE_SWITCH,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface SupportedKeycode {
@@ -205,6 +206,14 @@
         return 0;
     }
 
+    @Override
+    public String toString() {
+        return "VirtualKeyEvent("
+                + " action=" + KeyEvent.actionToString(mAction)
+                + " keyCode=" + KeyEvent.keyCodeToString(mKeyCode)
+                + " eventTime(ns)=" + mEventTimeNanos;
+    }
+
     /**
      * Returns the key code associated with this event.
      */
diff --git a/core/java/android/hardware/input/VirtualKeyboard.java b/core/java/android/hardware/input/VirtualKeyboard.java
index e569dbf..c90f893 100644
--- a/core/java/android/hardware/input/VirtualKeyboard.java
+++ b/core/java/android/hardware/input/VirtualKeyboard.java
@@ -39,8 +39,9 @@
     private final int mUnsupportedKeyCode = KeyEvent.KEYCODE_DPAD_CENTER;
 
     /** @hide */
-    public VirtualKeyboard(IVirtualDevice virtualDevice, IBinder token) {
-        super(virtualDevice, token);
+    public VirtualKeyboard(VirtualKeyboardConfig config,
+            IVirtualDevice virtualDevice, IBinder token) {
+        super(config, virtualDevice, token);
     }
 
     /**
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java
index 6d03065..96a1a36 100644
--- a/core/java/android/hardware/input/VirtualKeyboardConfig.java
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java
@@ -99,6 +99,12 @@
         dest.writeString8(mLayoutType);
     }
 
+    @Override
+    @NonNull
+    String additionalFieldsToString() {
+        return " languageTag=" + mLanguageTag + " layoutType=" + mLayoutType;
+    }
+
     /**
      * Builder for creating a {@link VirtualKeyboardConfig}.
      */
diff --git a/core/java/android/hardware/input/VirtualMouse.java b/core/java/android/hardware/input/VirtualMouse.java
index 7eba2b8..51f3f69 100644
--- a/core/java/android/hardware/input/VirtualMouse.java
+++ b/core/java/android/hardware/input/VirtualMouse.java
@@ -38,8 +38,8 @@
 public class VirtualMouse extends VirtualInputDevice {
 
     /** @hide */
-    public VirtualMouse(IVirtualDevice virtualDevice, IBinder token) {
-        super(virtualDevice, token);
+    public VirtualMouse(VirtualMouseConfig config, IVirtualDevice virtualDevice, IBinder token) {
+        super(config, virtualDevice, token);
     }
 
     /**
diff --git a/core/java/android/hardware/input/VirtualMouseButtonEvent.java b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
index dfdd3b4..fc42b15 100644
--- a/core/java/android/hardware/input/VirtualMouseButtonEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseButtonEvent.java
@@ -110,6 +110,14 @@
         return 0;
     }
 
+    @Override
+    public String toString() {
+        return "VirtualMouseButtonEvent("
+                + " action=" + MotionEvent.actionToString(mAction)
+                + " button=" + MotionEvent.buttonStateToString(mButtonCode)
+                + " eventTime(ns)=" + mEventTimeNanos;
+    }
+
     /**
      * Returns the button code associated with this event.
      */
diff --git a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
index e6ad118..2a42cfc 100644
--- a/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseRelativeEvent.java
@@ -61,6 +61,14 @@
         return 0;
     }
 
+    @Override
+    public String toString() {
+        return "VirtualMouseRelativeEvent("
+                + " x=" + mRelativeX
+                + " y=" + mRelativeY
+                + " eventTime(ns)=" + mEventTimeNanos;
+    }
+
     /**
      * Returns the relative x-axis movement, in pixels.
      */
diff --git a/core/java/android/hardware/input/VirtualMouseScrollEvent.java b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
index 4d0a157..c89c188 100644
--- a/core/java/android/hardware/input/VirtualMouseScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualMouseScrollEvent.java
@@ -65,6 +65,14 @@
         return 0;
     }
 
+    @Override
+    public String toString() {
+        return "VirtualMouseScrollEvent("
+                + " x=" + mXAxisMovement
+                + " y=" + mYAxisMovement
+                + " eventTime(ns)=" + mEventTimeNanos;
+    }
+
     /**
      * Returns the x-axis scroll movement, normalized from -1.0 to 1.0, inclusive. Positive values
      * indicate scrolling upward; negative values, downward.
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpad.java b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
index 2854034..61d72e2 100644
--- a/core/java/android/hardware/input/VirtualNavigationTouchpad.java
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpad.java
@@ -40,8 +40,9 @@
 public class VirtualNavigationTouchpad extends VirtualInputDevice {
 
     /** @hide */
-    public VirtualNavigationTouchpad(IVirtualDevice virtualDevice, IBinder token) {
-        super(virtualDevice, token);
+    public VirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config,
+            IVirtualDevice virtualDevice, IBinder token) {
+        super(config, virtualDevice, token);
     }
 
     /**
diff --git a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
index 8935efa..75f7b3e 100644
--- a/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
+++ b/core/java/android/hardware/input/VirtualNavigationTouchpadConfig.java
@@ -70,6 +70,12 @@
         dest.writeInt(mWidth);
     }
 
+    @Override
+    @NonNull
+    String additionalFieldsToString() {
+        return " width=" + mWidth + " height=" + mHeight;
+    }
+
     @NonNull
     public static final Creator<VirtualNavigationTouchpadConfig> CREATOR =
             new Creator<VirtualNavigationTouchpadConfig>() {
diff --git a/core/java/android/hardware/input/VirtualTouchEvent.java b/core/java/android/hardware/input/VirtualTouchEvent.java
index 2695a79..7936dfe 100644
--- a/core/java/android/hardware/input/VirtualTouchEvent.java
+++ b/core/java/android/hardware/input/VirtualTouchEvent.java
@@ -138,6 +138,19 @@
         return 0;
     }
 
+    @Override
+    public String toString() {
+        return "VirtualTouchEvent("
+                + " pointerId=" + mPointerId
+                + " toolType=" + MotionEvent.toolTypeToString(mToolType)
+                + " action=" + MotionEvent.actionToString(mAction)
+                + " x=" + mX
+                + " y=" + mY
+                + " pressure=" + mPressure
+                + " majorAxisSize=" + mMajorAxisSize
+                + " eventTime(ns)=" + mEventTimeNanos;
+    }
+
     /**
      * Returns the pointer id associated with this event.
      */
diff --git a/core/java/android/hardware/input/VirtualTouchscreen.java b/core/java/android/hardware/input/VirtualTouchscreen.java
index 0d07753..4ac439e 100644
--- a/core/java/android/hardware/input/VirtualTouchscreen.java
+++ b/core/java/android/hardware/input/VirtualTouchscreen.java
@@ -34,8 +34,9 @@
 @SystemApi
 public class VirtualTouchscreen extends VirtualInputDevice {
     /** @hide */
-    public VirtualTouchscreen(IVirtualDevice virtualDevice, IBinder token) {
-        super(virtualDevice, token);
+    public VirtualTouchscreen(VirtualTouchscreenConfig config,
+            IVirtualDevice virtualDevice, IBinder token) {
+        super(config, virtualDevice, token);
     }
 
     /**
diff --git a/core/java/android/hardware/input/VirtualTouchscreenConfig.java b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
index aac341cc..6308459 100644
--- a/core/java/android/hardware/input/VirtualTouchscreenConfig.java
+++ b/core/java/android/hardware/input/VirtualTouchscreenConfig.java
@@ -69,6 +69,12 @@
         dest.writeInt(mHeight);
     }
 
+    @Override
+    @NonNull
+    String additionalFieldsToString() {
+        return " width=" + mWidth + " height=" + mHeight;
+    }
+
     @NonNull
     public static final Creator<VirtualTouchscreenConfig> CREATOR =
             new Creator<VirtualTouchscreenConfig>() {
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index 0a6be20..eda80c8 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -85,14 +85,14 @@
     /**
      * Interface data activity status is changed.
      *
-     * @param transportType The transport type of the data activity change.
+     * @param label label of the data activity change.
      * @param active  True if the interface is actively transmitting data, false if it is idle.
      * @param tsNanos Elapsed realtime in nanos when the state of the network interface changed.
      * @param uid Uid of this event. It represents the uid that was responsible for waking the
      *            radio. For those events that are reported by system itself, not from specific uid,
      *            use -1 for the events which means no uid.
      */
-    void interfaceClassDataActivityChanged(int transportType, boolean active, long tsNanos, int uid);
+    void interfaceClassDataActivityChanged(int label, boolean active, long tsNanos, int uid);
 
     /**
      * Information about available DNS servers has been received.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index daec1721..13572fb 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -832,10 +832,16 @@
     /**
      * Returns true if the current process is a 64-bit runtime.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static final boolean is64Bit() {
         return VMRuntime.getRuntime().is64Bit();
     }
 
+    /** @hide */
+    public static final boolean is64Bit$ravenwood() {
+        return "amd64".equals(System.getProperty("os.arch"));
+    }
+
     private static SomeArgs sIdentity$ravenwood;
 
     /** @hide */
@@ -906,6 +912,7 @@
      * {@link #myUid()} in that a particular user will have multiple
      * distinct apps running under it each with their own uid.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static UserHandle myUserHandle() {
         return UserHandle.of(UserHandle.getUserId(myUid()));
     }
@@ -914,6 +921,7 @@
      * Returns whether the given uid belongs to a system core component or not.
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isCoreUid(int uid) {
         return UserHandle.isCore(uid);
     }
@@ -924,6 +932,7 @@
      * @return Whether the uid corresponds to an application sandbox running in
      *     a specific user.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static boolean isApplicationUid(int uid) {
         return UserHandle.isApp(uid);
     }
@@ -931,6 +940,7 @@
     /**
      * Returns whether the current process is in an isolated sandbox.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isIsolated() {
         return isIsolated(myUid());
     }
@@ -942,6 +952,7 @@
     @Deprecated
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
             publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isIsolated(int uid) {
         return isIsolatedUid(uid);
     }
@@ -949,6 +960,7 @@
     /**
      * Returns whether the process with the given {@code uid} is an isolated sandbox.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isIsolatedUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
@@ -962,6 +974,7 @@
      */
     @SystemApi(client = MODULE_LIBRARIES)
     @TestApi
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isSdkSandboxUid(int uid) {
         uid = UserHandle.getAppId(uid);
         return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
@@ -975,6 +988,7 @@
      */
     @SystemApi(client = MODULE_LIBRARIES)
     @TestApi
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final int getAppUidForSdkSandboxUid(int uid) {
         return uid - (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
     }
@@ -987,6 +1001,7 @@
      */
     @SystemApi(client = MODULE_LIBRARIES)
     @TestApi
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final int toSdkSandboxUid(int uid) {
         return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
     }
@@ -994,6 +1009,7 @@
     /**
      * Returns whether the current process is a sdk sandbox process.
      */
+    @android.ravenwood.annotation.RavenwoodKeep
     public static final boolean isSdkSandbox() {
         return isSdkSandboxUid(myUid());
     }
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index cac7f3b..0644ef1 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -36,6 +36,7 @@
 /**
  * Representation of a user on the device.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class UserHandle implements Parcelable {
     // NOTE: keep logic in sync with system/core/libcutils/multiuser.c
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index d9c6bee..2419a4c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -204,6 +204,8 @@
      * the user in locked state so that a direct boot aware DPC could reset the password.
      * Should not be used together with
      * {@link #QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED} or an exception will be thrown.
+     * This flag is currently only allowed for {@link #isManagedProfile() managed profiles};
+     * usage on other profiles may result in an Exception.
      * @hide
      */
     public static final int QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL = 0x2;
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index 33058d8..2a33caa 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -26,6 +26,7 @@
 import com.android.internal.util.ArtBinaryXmlSerializer;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.BinaryXmlPullParser;
 import com.android.modules.utils.BinaryXmlSerializer;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
@@ -38,6 +39,7 @@
 import org.xml.sax.XMLReader;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.BufferedInputStream;
@@ -115,6 +117,7 @@
     /**
      * Returns a new pull parser with namespace support.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static XmlPullParser newPullParser() {
         try {
             XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
@@ -126,6 +129,12 @@
         }
     }
 
+    /** @hide */
+    public static XmlPullParser newPullParser$ravenwood() {
+        // TODO: remove once we're linking against libcore
+        return new BinaryXmlPullParser();
+    }
+
     /**
      * Creates a new {@link TypedXmlPullParser} which is optimized for use
      * inside the system, typically by supporting only a basic set of features.
@@ -136,10 +145,17 @@
      * @hide
      */
     @SuppressWarnings("AndroidFrameworkEfficientXml")
+    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlPullParser newFastPullParser() {
         return XmlUtils.makeTyped(newPullParser());
     }
 
+    /** @hide */
+    public static TypedXmlPullParser newFastPullParser$ravenwood() {
+        // TODO: remove once we're linking against libcore
+        return new BinaryXmlPullParser();
+    }
+
     /**
      * Creates a new {@link XmlPullParser} that reads XML documents using a
      * custom binary wire protocol which benchmarking has shown to be 8.5x
@@ -148,10 +164,17 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlPullParser newBinaryPullParser() {
         return new ArtBinaryXmlPullParser();
     }
 
+    /** @hide */
+    public static TypedXmlPullParser newBinaryPullParser$ravenwood() {
+        // TODO: remove once we're linking against libcore
+        return new BinaryXmlPullParser();
+    }
+
     /**
      * Creates a new {@link XmlPullParser} which is optimized for use inside the
      * system, typically by supporting only a basic set of features.
@@ -166,6 +189,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlPullParser resolvePullParser(@NonNull InputStream in)
             throws IOException {
         final byte[] magic = new byte[4];
@@ -198,13 +222,33 @@
         return xml;
     }
 
+    /** @hide */
+    public static @NonNull TypedXmlPullParser resolvePullParser$ravenwood(@NonNull InputStream in)
+            throws IOException {
+        // TODO: remove once we're linking against libcore
+        final TypedXmlPullParser xml = new BinaryXmlPullParser();
+        try {
+            xml.setInput(in, StandardCharsets.UTF_8.name());
+        } catch (XmlPullParserException e) {
+            throw new IOException(e);
+        }
+        return xml;
+    }
+
     /**
      * Creates a new xml serializer.
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static XmlSerializer newSerializer() {
         return XmlObjectFactory.newXmlSerializer();
     }
 
+    /** @hide */
+    public static XmlSerializer newSerializer$ravenwood() {
+        // TODO: remove once we're linking against libcore
+        return new BinaryXmlSerializer();
+    }
+
     /**
      * Creates a new {@link XmlSerializer} which is optimized for use inside the
      * system, typically by supporting only a basic set of features.
@@ -215,10 +259,17 @@
      * @hide
      */
     @SuppressWarnings("AndroidFrameworkEfficientXml")
+    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlSerializer newFastSerializer() {
         return XmlUtils.makeTyped(new FastXmlSerializer());
     }
 
+    /** @hide */
+    public static @NonNull TypedXmlSerializer newFastSerializer$ravenwood() {
+        // TODO: remove once we're linking against libcore
+        return new BinaryXmlSerializer();
+    }
+
     /**
      * Creates a new {@link XmlSerializer} that writes XML documents using a
      * custom binary wire protocol which benchmarking has shown to be 4.4x
@@ -227,10 +278,17 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlSerializer newBinarySerializer() {
         return new ArtBinaryXmlSerializer();
     }
 
+    /** @hide */
+    public static @NonNull TypedXmlSerializer newBinarySerializer$ravenwood() {
+        // TODO: remove once we're linking against libcore
+        return new BinaryXmlSerializer();
+    }
+
     /**
      * Creates a new {@link XmlSerializer} which is optimized for use inside the
      * system, typically by supporting only a basic set of features.
@@ -245,6 +303,7 @@
      *
      * @hide
      */
+    @android.ravenwood.annotation.RavenwoodReplace
     public static @NonNull TypedXmlSerializer resolveSerializer(@NonNull OutputStream out)
             throws IOException {
         final TypedXmlSerializer xml;
@@ -257,6 +316,15 @@
         return xml;
     }
 
+    /** @hide */
+    public static @NonNull TypedXmlSerializer resolveSerializer$ravenwood(@NonNull OutputStream out)
+            throws IOException {
+        // TODO: remove once we're linking against libcore
+        final TypedXmlSerializer xml = new BinaryXmlSerializer();
+        xml.setOutput(out, StandardCharsets.UTF_8.name());
+        return xml;
+    }
+
     /**
      * Copy the first XML document into the second document.
      * <p>
diff --git a/core/java/com/android/internal/util/ArtFastDataInput.java b/core/java/com/android/internal/util/ArtFastDataInput.java
index 3e8916c..768ea82 100644
--- a/core/java/com/android/internal/util/ArtFastDataInput.java
+++ b/core/java/com/android/internal/util/ArtFastDataInput.java
@@ -21,6 +21,8 @@
 
 import com.android.modules.utils.FastDataInput;
 
+import dalvik.system.VMRuntime;
+
 import java.io.DataInput;
 import java.io.IOException;
 import java.io.InputStream;
@@ -35,13 +37,14 @@
  */
 public class ArtFastDataInput extends FastDataInput {
     private static AtomicReference<ArtFastDataInput> sInCache = new AtomicReference<>();
+    private static VMRuntime sRuntime = VMRuntime.getRuntime();
 
     private final long mBufferPtr;
 
     public ArtFastDataInput(@NonNull InputStream in, int bufferSize) {
         super(in, bufferSize);
 
-        mBufferPtr = mRuntime.addressOf(mBuffer);
+        mBufferPtr = sRuntime.addressOf(mBuffer);
     }
 
     /**
@@ -66,6 +69,7 @@
      * Release a {@link ArtFastDataInput} to potentially be recycled. You must not
      * interact with the object after releasing it.
      */
+    @Override
     public void release() {
         super.release();
 
@@ -76,6 +80,11 @@
     }
 
     @Override
+    public byte[] newByteArray(int bufferSize) {
+        return (byte[]) sRuntime.newNonMovableArray(byte.class, bufferSize);
+    }
+
+    @Override
     public String readUTF() throws IOException {
         // Attempt to read directly from buffer space if there's enough room,
         // otherwise fall back to chunking into place
@@ -86,9 +95,9 @@
             mBufferPos += len;
             return res;
         } else {
-            final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
+            final byte[] tmp = (byte[]) sRuntime.newNonMovableArray(byte.class, len + 1);
             readFully(tmp, 0, len);
-            return CharsetUtils.fromModifiedUtf8Bytes(mRuntime.addressOf(tmp), 0, len);
+            return CharsetUtils.fromModifiedUtf8Bytes(sRuntime.addressOf(tmp), 0, len);
         }
     }
 }
diff --git a/core/java/com/android/internal/util/ArtFastDataOutput.java b/core/java/com/android/internal/util/ArtFastDataOutput.java
index ac595b6..360ddb8 100644
--- a/core/java/com/android/internal/util/ArtFastDataOutput.java
+++ b/core/java/com/android/internal/util/ArtFastDataOutput.java
@@ -21,6 +21,8 @@
 
 import com.android.modules.utils.FastDataOutput;
 
+import dalvik.system.VMRuntime;
+
 import java.io.DataOutput;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -35,13 +37,14 @@
  */
 public class ArtFastDataOutput extends FastDataOutput {
     private static AtomicReference<ArtFastDataOutput> sOutCache = new AtomicReference<>();
+    private static VMRuntime sRuntime = VMRuntime.getRuntime();
 
     private final long mBufferPtr;
 
     public ArtFastDataOutput(@NonNull OutputStream out, int bufferSize) {
         super(out, bufferSize);
 
-        mBufferPtr = mRuntime.addressOf(mBuffer);
+        mBufferPtr = sRuntime.addressOf(mBuffer);
     }
 
     /**
@@ -73,6 +76,11 @@
     }
 
     @Override
+    public byte[] newByteArray(int bufferSize) {
+        return (byte[]) sRuntime.newNonMovableArray(byte.class, bufferSize);
+    }
+
+    @Override
     public void writeUTF(String s) throws IOException {
         // Attempt to write directly to buffer space if there's enough room,
         // otherwise fall back to chunking into place
@@ -94,8 +102,8 @@
             // Negative value indicates buffer was too small and we need to
             // allocate a temporary buffer for encoding
             len = -len;
-            final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
-            CharsetUtils.toModifiedUtf8Bytes(s, mRuntime.addressOf(tmp), 0, tmp.length);
+            final byte[] tmp = (byte[]) sRuntime.newNonMovableArray(byte.class, len + 1);
+            CharsetUtils.toModifiedUtf8Bytes(s, sRuntime.addressOf(tmp), 0, tmp.length);
             writeShort(len);
             write(tmp, 0, len);
         }
diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java
index 139b88b..61e017d 100644
--- a/core/java/com/android/server/net/BaseNetworkObserver.java
+++ b/core/java/com/android/server/net/BaseNetworkObserver.java
@@ -64,7 +64,7 @@
     }
 
     @Override
-    public void interfaceClassDataActivityChanged(int transportType, boolean active, long tsNanos,
+    public void interfaceClassDataActivityChanged(int label, boolean active, long tsNanos,
             int uid) {
         // default no-op
     }
diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl
index 51b720d..f9347ee 100644
--- a/data/keyboards/Generic.kl
+++ b/data/keyboards/Generic.kl
@@ -324,7 +324,7 @@
 # key 365 "KEY_EPG"
 key 366   DVR
 # key 367 "KEY_MHP"
-# key 368 "KEY_LANGUAGE"
+key 368   LANGUAGE_SWITCH
 # key 369 "KEY_TITLE"
 key 370   CAPTIONS
 # key 371 "KEY_ANGLE"
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index feb914f..757e9f8 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -630,7 +630,6 @@
 
     const DemuxFilterMediaEvent &mediaEvent = event.get<DemuxFilterEvent::Tag::media>();
     ScopedLocalRef<jobject> audioDescriptor(env);
-    gAudioPresentationFields.init(env);
     ScopedLocalRef presentationsJObj(env, JAudioPresentationInfo::asJobject(
         env, gAudioPresentationFields));
     switch (mediaEvent.extraMetaData.getTag()) {
@@ -3731,6 +3730,7 @@
     gFields.linearBlockInitID = env->GetMethodID(linearBlockClazz, "<init>", "()V");
     gFields.linearBlockSetInternalStateID =
             env->GetMethodID(linearBlockClazz, "setInternalStateLocked", "(JZ)V");
+    gAudioPresentationFields.init(env);
 }
 
 static void android_media_tv_Tuner_native_setup(JNIEnv *env, jobject thiz) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index fa8c1fb..0ffcc45 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -594,6 +594,16 @@
                 || cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO);
     }
 
+    /**
+     * Check if the Bluetooth device is an active LE Audio device
+     *
+     * @param cachedDevice the CachedBluetoothDevice
+     * @return if the Bluetooth device is an active LE Audio device
+     */
+    public static boolean isActiveLeAudioDevice(CachedBluetoothDevice cachedDevice) {
+        return cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO);
+    }
+
     private static boolean isDeviceConnected(CachedBluetoothDevice cachedDevice) {
         if (cachedDevice == null) {
             return false;
diff --git a/packages/SystemUI/compose/scene/Android.bp b/packages/SystemUI/compose/scene/Android.bp
index 050d1d5..3424085 100644
--- a/packages/SystemUI/compose/scene/Android.bp
+++ b/packages/SystemUI/compose/scene/Android.bp
@@ -21,12 +21,19 @@
     default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
 }
 
+filegroup {
+    name: "PlatformComposeSceneTransitionLayout-srcs",
+    srcs: [
+        "src/**/*.kt",
+    ],
+}
+
 android_library {
     name: "PlatformComposeSceneTransitionLayout",
     manifest: "AndroidManifest.xml",
 
     srcs: [
-        "src/**/*.kt",
+        ":PlatformComposeSceneTransitionLayout-srcs",
     ],
 
     static_libs: [
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 6153e19..3b999e30 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -17,18 +17,14 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.composed
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.isSpecified
@@ -39,6 +35,7 @@
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.layout.intermediateLayout
+import androidx.compose.ui.node.ModifierNodeElement
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.unit.Constraints
 import androidx.compose.ui.unit.IntSize
@@ -46,6 +43,7 @@
 import com.android.compose.animation.scene.transformation.PropertyTransformation
 import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.ui.util.lerp
+import kotlinx.coroutines.launch
 
 /** An element on screen, that can be composed in one or more scenes. */
 internal class Element(val key: ElementKey) {
@@ -92,13 +90,20 @@
     }
 
     /** The target values of this element in a given scene. */
-    class TargetValues {
+    class TargetValues(val scene: SceneKey) {
         val lastValues = Values()
 
         var targetSize by mutableStateOf(SizeUnspecified)
         var targetOffset by mutableStateOf(Offset.Unspecified)
 
         val sharedValues = SnapshotStateMap<ValueKey, SharedValue<*>>()
+
+        /**
+         * The attached [ElementNode] a Modifier.element() for a given element and scene. During
+         * composition, this set could have 0 to 2 elements. After composition and after all
+         * modifier nodes have been attached/detached, this set should contain exactly 1 element.
+         */
+        val nodes = mutableSetOf<ElementNode>()
     }
 
     /** A shared value of this element. */
@@ -125,50 +130,31 @@
     layoutImpl: SceneTransitionLayoutImpl,
     scene: Scene,
     key: ElementKey,
-): Modifier = composed {
-    val sceneValues = remember(scene, key) { Element.TargetValues() }
-    val element =
-        // Get the element associated to [key] if it was already composed in another scene,
-        // otherwise create it and add it to our Map<ElementKey, Element>. This is done inside a
-        // withoutReadObservation() because there is no need to recompose when that map is mutated.
-        Snapshot.withoutReadObservation {
-            val element =
-                layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
-            val previousValues = element.sceneValues[scene.key]
-            if (previousValues == null) {
-                element.sceneValues[scene.key] = sceneValues
-            } else if (previousValues != sceneValues) {
-                error("$key was composed multiple times in $scene")
-            }
+): Modifier {
+    val element: Element
+    val sceneValues: Element.TargetValues
 
-            element
-        }
-
-    DisposableEffect(scene, sceneValues, element) {
-        onDispose {
-            element.sceneValues.remove(scene.key)
-
-            // This was the last scene this element was in, so remove it from the map.
-            if (element.sceneValues.isEmpty()) {
-                layoutImpl.elements.remove(element.key)
-            }
-        }
+    // Get the element associated to [key] if it was already composed in another scene,
+    // otherwise create it and add it to our Map<ElementKey, Element>. This is done inside a
+    // withoutReadObservation() because there is no need to recompose when that map is mutated.
+    Snapshot.withoutReadObservation {
+        element = layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
+        sceneValues =
+            element.sceneValues[scene.key]
+                ?: Element.TargetValues(scene.key).also { element.sceneValues[scene.key] = it }
     }
 
-    val drawScale by
-        remember(layoutImpl, element, scene, sceneValues) {
-            derivedStateOf { getDrawScale(layoutImpl, element, scene, sceneValues) }
-        }
-
-    drawWithContent {
+    return this.then(ElementModifier(layoutImpl, element, sceneValues))
+        .drawWithContent {
             if (shouldDrawElement(layoutImpl, scene, element)) {
+                val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
                 if (drawScale == Scale.Default) {
-                    this@drawWithContent.drawContent()
+                    drawContent()
                 } else {
                     scale(
                         drawScale.scaleX,
                         drawScale.scaleY,
-                        if (drawScale.pivot.isUnspecified) center else drawScale.pivot
+                        if (drawScale.pivot.isUnspecified) center else drawScale.pivot,
                     ) {
                         this@drawWithContent.drawContent()
                     }
@@ -186,6 +172,84 @@
         .testTag(key.testTag)
 }
 
+/**
+ * An element associated to [ElementNode]. Note that this element does not support updates as its
+ * arguments should always be the same.
+ */
+private data class ElementModifier(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val element: Element,
+    private val sceneValues: Element.TargetValues,
+) : ModifierNodeElement<ElementNode>() {
+    override fun create(): ElementNode = ElementNode(layoutImpl, element, sceneValues)
+
+    override fun update(node: ElementNode) {
+        node.update(layoutImpl, element, sceneValues)
+    }
+}
+
+internal class ElementNode(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: Element,
+    sceneValues: Element.TargetValues,
+) : Modifier.Node() {
+    private var layoutImpl: SceneTransitionLayoutImpl = layoutImpl
+    private var element: Element = element
+    private var sceneValues: Element.TargetValues = sceneValues
+
+    override fun onAttach() {
+        super.onAttach()
+        addNodeToSceneValues()
+    }
+
+    private fun addNodeToSceneValues() {
+        sceneValues.nodes.add(this)
+
+        coroutineScope.launch {
+            // At this point all [CodeLocationNode] have been attached or detached, which means that
+            // [sceneValues.codeLocations] should have exactly 1 element, otherwise this means that
+            // this element was composed multiple times in the same scene.
+            val nCodeLocations = sceneValues.nodes.size
+            if (nCodeLocations != 1 || !sceneValues.nodes.contains(this@ElementNode)) {
+                error("${element.key} was composed $nCodeLocations times in ${sceneValues.scene}")
+            }
+        }
+    }
+
+    override fun onDetach() {
+        super.onDetach()
+        removeNodeFromSceneValues()
+    }
+
+    private fun removeNodeFromSceneValues() {
+        sceneValues.nodes.remove(this)
+
+        // If element is not composed from this scene anymore, remove the scene values. This works
+        // because [onAttach] is called before [onDetach], so if an element is moved from the UI
+        // tree we will first add the new code location then remove the old one.
+        if (sceneValues.nodes.isEmpty()) {
+            element.sceneValues.remove(sceneValues.scene)
+        }
+
+        // If the element is not composed in any scene, remove it from the elements map.
+        if (element.sceneValues.isEmpty()) {
+            layoutImpl.elements.remove(element.key)
+        }
+    }
+
+    fun update(
+        layoutImpl: SceneTransitionLayoutImpl,
+        element: Element,
+        sceneValues: Element.TargetValues,
+    ) {
+        removeNodeFromSceneValues()
+        this.layoutImpl = layoutImpl
+        this.element = element
+        this.sceneValues = sceneValues
+        addNodeToSceneValues()
+    }
+}
+
 private fun shouldDrawElement(
     layoutImpl: SceneTransitionLayoutImpl,
     scene: Scene,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index bc015ee..5b752eb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -43,7 +43,10 @@
     name: String,
     identity: Any = Object(),
 ) : Key(name, identity) {
-    @VisibleForTesting val testTag: String = "scene:$name"
+    @VisibleForTesting
+    // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
+    // access internal members.
+    val testTag: String = "scene:$name"
 
     /** The unique [ElementKey] identifying this scene's root element. */
     val rootElementKey = ElementKey(name, identity)
@@ -64,7 +67,10 @@
      */
     val isBackground: Boolean = false,
 ) : Key(name, identity), ElementMatcher {
-    @VisibleForTesting val testTag: String = "element:$name"
+    @VisibleForTesting
+    // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
+    // access internal members.
+    val testTag: String = "element:$name"
 
     override fun matches(key: ElementKey, scene: SceneKey): Boolean {
         return key == this
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 1a79522..857a596 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -76,6 +76,8 @@
     private val layoutImpl: SceneTransitionLayoutImpl,
     private val scene: Scene,
 ) : SceneScope {
+    override val layoutState: SceneTransitionLayoutState = layoutImpl.state
+
     override fun Modifier.element(key: ElementKey): Modifier {
         return element(layoutImpl, scene, key)
     }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 838cb3b..c51287a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -17,7 +17,6 @@
 package com.android.compose.animation.scene
 
 import android.util.Log
-import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.Spring
 import androidx.compose.animation.core.spring
@@ -37,8 +36,7 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.launch
 
-@VisibleForTesting
-class SceneGestureHandler(
+internal class SceneGestureHandler(
     internal val layoutImpl: SceneTransitionLayoutImpl,
     internal val orientation: Orientation,
     private val coroutineScope: CoroutineScope,
@@ -63,12 +61,10 @@
     internal val currentScene: Scene
         get() = layoutImpl.scene(transitionState.currentScene)
 
-    @VisibleForTesting
-    val isDrivingTransition
+    internal val isDrivingTransition
         get() = transitionState == swipeTransition
 
-    @VisibleForTesting
-    var isAnimatingOffset
+    internal var isAnimatingOffset
         get() = swipeTransition.isAnimatingOffset
         private set(value) {
             swipeTransition.isAnimatingOffset = value
@@ -81,7 +77,7 @@
      * The velocity threshold at which the intent of the user is to swipe up or down. It is the same
      * as SwipeableV2Defaults.VelocityThreshold.
      */
-    @VisibleForTesting val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() }
+    internal val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() }
 
     /**
      * The positional threshold at which the intent of the user is to swipe to the next scene. It is
@@ -533,8 +529,7 @@
     }
 }
 
-@VisibleForTesting
-class SceneNestedScrollHandler(
+internal class SceneNestedScrollHandler(
     private val gestureHandler: SceneGestureHandler,
     private val startBehavior: NestedScrollBehavior,
     private val endBehavior: NestedScrollBehavior,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 9c31445..30d13df 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -57,30 +57,17 @@
     @FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
     scenes: SceneTransitionLayoutScope.() -> Unit,
 ) {
-    val density = LocalDensity.current
-    val coroutineScope = rememberCoroutineScope()
-    val layoutImpl = remember {
-        SceneTransitionLayoutImpl(
-            onChangeScene = onChangeScene,
-            builder = scenes,
-            transitions = transitions,
-            state = state,
-            density = density,
-            edgeDetector = edgeDetector,
-            transitionInterceptionThreshold = transitionInterceptionThreshold,
-            coroutineScope = coroutineScope,
-        )
-    }
-
-    layoutImpl.onChangeScene = onChangeScene
-    layoutImpl.transitions = transitions
-    layoutImpl.density = density
-    layoutImpl.edgeDetector = edgeDetector
-    layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
-
-    layoutImpl.setScenes(scenes)
-    layoutImpl.setCurrentScene(currentScene)
-    layoutImpl.Content(modifier)
+    SceneTransitionLayoutForTesting(
+        currentScene,
+        onChangeScene,
+        transitions,
+        state,
+        edgeDetector,
+        transitionInterceptionThreshold,
+        modifier,
+        onLayoutImpl = null,
+        scenes,
+    )
 }
 
 interface SceneTransitionLayoutScope {
@@ -108,6 +95,9 @@
 
 @ElementDsl
 interface SceneScope {
+    /** The state of the [SceneTransitionLayout] in which this scene is contained. */
+    val layoutState: SceneTransitionLayoutState
+
     /**
      * Tag an element identified by [key].
      *
@@ -228,3 +218,47 @@
     Left(Orientation.Horizontal),
     Right(Orientation.Horizontal),
 }
+
+/**
+ * An internal version of [SceneTransitionLayout] to be used for tests.
+ *
+ * Important: You should use this only in tests and if you need to access the underlying
+ * [SceneTransitionLayoutImpl]. In other cases, you should use [SceneTransitionLayout].
+ */
+@Composable
+internal fun SceneTransitionLayoutForTesting(
+    currentScene: SceneKey,
+    onChangeScene: (SceneKey) -> Unit,
+    transitions: SceneTransitions,
+    state: SceneTransitionLayoutState,
+    edgeDetector: EdgeDetector,
+    transitionInterceptionThreshold: Float,
+    modifier: Modifier,
+    onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)?,
+    scenes: SceneTransitionLayoutScope.() -> Unit,
+) {
+    val density = LocalDensity.current
+    val coroutineScope = rememberCoroutineScope()
+    val layoutImpl = remember {
+        SceneTransitionLayoutImpl(
+                onChangeScene = onChangeScene,
+                builder = scenes,
+                transitions = transitions,
+                state = state,
+                density = density,
+                edgeDetector = edgeDetector,
+                transitionInterceptionThreshold = transitionInterceptionThreshold,
+                coroutineScope = coroutineScope,
+            )
+            .also { onLayoutImpl?.invoke(it) }
+    }
+
+    layoutImpl.onChangeScene = onChangeScene
+    layoutImpl.transitions = transitions
+    layoutImpl.density = density
+    layoutImpl.edgeDetector = edgeDetector
+
+    layoutImpl.setScenes(scenes)
+    layoutImpl.setCurrentScene(currentScene)
+    layoutImpl.Content(modifier)
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 94f2737..60f385a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -17,7 +17,6 @@
 package com.android.compose.animation.scene
 
 import androidx.activity.compose.BackHandler
-import androidx.annotation.VisibleForTesting
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
@@ -42,8 +41,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.Channel
 
-@VisibleForTesting
-class SceneTransitionLayoutImpl(
+internal class SceneTransitionLayoutImpl(
     onChangeScene: (SceneKey) -> Unit,
     builder: SceneTransitionLayoutScope.() -> Unit,
     transitions: SceneTransitions,
@@ -260,8 +258,7 @@
 
     internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene)
 
-    @VisibleForTesting
-    fun setScenesTargetSizeForTest(size: IntSize) {
+    internal fun setScenesTargetSizeForTest(size: IntSize) {
         scenes.values.forEach { it.targetSize = size }
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index b9f83c5..64c9775 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -30,6 +30,22 @@
      */
     var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene))
         internal set
+
+    /**
+     * Whether we are transitioning, optionally restricting the check to the transition between
+     * [from] and [to].
+     */
+    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
+        val transition = transitionState as? TransitionState.Transition ?: return false
+
+        // TODO(b/310915136): Remove this check.
+        if (transition.fromScene == transition.toScene) {
+            return false
+        }
+
+        return (from == null || transition.fromScene == from) &&
+            (to == null || transition.toScene == to)
+    }
 }
 
 sealed interface TransitionState {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 72a2d61..2172ed3 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -16,7 +16,6 @@
 
 package com.android.compose.animation.scene
 
-import androidx.annotation.VisibleForTesting
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.snap
 import androidx.compose.ui.geometry.Offset
@@ -38,12 +37,11 @@
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
 class SceneTransitions(
-    @get:VisibleForTesting val transitionSpecs: List<TransitionSpec>,
+    internal val transitionSpecs: List<TransitionSpec>,
 ) {
     private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
 
-    @VisibleForTesting
-    fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+    internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
         return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
     }
 
diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp
index 6de7550..13df35b 100644
--- a/packages/SystemUI/compose/scene/tests/Android.bp
+++ b/packages/SystemUI/compose/scene/tests/Android.bp
@@ -30,10 +30,13 @@
 
     srcs: [
         "src/**/*.kt",
+
+        // TODO(b/240432457): Depend on PlatformComposeSceneTransitionLayout
+        // directly once Kotlin tests can access internal declarations.
+        ":PlatformComposeSceneTransitionLayout-srcs",
     ],
 
     static_libs: [
-        "PlatformComposeSceneTransitionLayout",
         "PlatformComposeSceneTransitionLayoutTestsUtils",
 
         "androidx.test.runner",
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 6401bb3..cc7a0b8 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -18,9 +18,15 @@
 
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.intermediateLayout
@@ -29,6 +35,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -209,4 +216,215 @@
             }
         }
     }
+
+    @Test
+    fun elementIsReusedInSameSceneAndBetweenScenes() {
+        var currentScene by mutableStateOf(TestScenes.SceneA)
+        var sceneCState by mutableStateOf(0)
+        var sceneDState by mutableStateOf(0)
+        val key = TestElements.Foo
+        var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
+
+        rule.setContent {
+            SceneTransitionLayoutForTesting(
+                currentScene = currentScene,
+                onChangeScene = { currentScene = it },
+                transitions = remember { transitions {} },
+                state = remember { SceneTransitionLayoutState(currentScene) },
+                edgeDetector = DefaultEdgeDetector,
+                modifier = Modifier,
+                transitionInterceptionThreshold = 0f,
+                onLayoutImpl = { nullableLayoutImpl = it },
+            ) {
+                scene(TestScenes.SceneA) { /* Nothing */}
+                scene(TestScenes.SceneB) { Box(Modifier.element(key)) }
+                scene(TestScenes.SceneC) {
+                    when (sceneCState) {
+                        0 -> Row(Modifier.element(key)) {}
+                        1 -> Column(Modifier.element(key)) {}
+                        else -> {
+                            /* Nothing */
+                        }
+                    }
+                }
+                scene(TestScenes.SceneD) {
+                    // We should be able to extract the modifier before assigning it to different
+                    // nodes.
+                    val childModifier = Modifier.element(key)
+                    when (sceneDState) {
+                        0 -> Row(childModifier) {}
+                        1 -> Column(childModifier) {}
+                        else -> {
+                            /* Nothing */
+                        }
+                    }
+                }
+            }
+        }
+
+        assertThat(nullableLayoutImpl).isNotNull()
+        val layoutImpl = nullableLayoutImpl!!
+
+        // Scene A: no elements in the elements map.
+        rule.waitForIdle()
+        assertThat(layoutImpl.elements).isEmpty()
+
+        // Scene B: element is in the map.
+        currentScene = TestScenes.SceneB
+        rule.waitForIdle()
+
+        assertThat(layoutImpl.elements.keys).containsExactly(key)
+        val element = layoutImpl.elements.getValue(key)
+        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneB)
+
+        // Scene C, state 0: the same element is reused.
+        currentScene = TestScenes.SceneC
+        sceneCState = 0
+        rule.waitForIdle()
+
+        assertThat(layoutImpl.elements.keys).containsExactly(key)
+        assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
+        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneC)
+
+        // Scene C, state 1: the same element is reused.
+        sceneCState = 1
+        rule.waitForIdle()
+
+        assertThat(layoutImpl.elements.keys).containsExactly(key)
+        assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
+        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneC)
+
+        // Scene D, state 0: the same element is reused.
+        currentScene = TestScenes.SceneD
+        sceneDState = 0
+        rule.waitForIdle()
+
+        assertThat(layoutImpl.elements.keys).containsExactly(key)
+        assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
+        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneD)
+
+        // Scene D, state 1: the same element is reused.
+        sceneDState = 1
+        rule.waitForIdle()
+
+        assertThat(layoutImpl.elements.keys).containsExactly(key)
+        assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
+        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneD)
+
+        // Scene D, state 2: the element is removed from the map.
+        sceneDState = 2
+        rule.waitForIdle()
+
+        assertThat(element.sceneValues).isEmpty()
+        assertThat(layoutImpl.elements).isEmpty()
+    }
+
+    @Test
+    fun throwsExceptionWhenElementIsComposedMultipleTimes() {
+        val key = TestElements.Foo
+
+        assertThrows(IllegalStateException::class.java) {
+            rule.setContent {
+                TestSceneScope {
+                    Column {
+                        Box(Modifier.element(key))
+                        Box(Modifier.element(key))
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun throwsExceptionWhenElementIsComposedMultipleTimes_childModifier() {
+        val key = TestElements.Foo
+
+        assertThrows(IllegalStateException::class.java) {
+            rule.setContent {
+                TestSceneScope {
+                    Column {
+                        val childModifier = Modifier.element(key)
+                        Box(childModifier)
+                        Box(childModifier)
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun throwsExceptionWhenElementIsComposedMultipleTimes_childModifier_laterDuplication() {
+        val key = TestElements.Foo
+
+        assertThrows(IllegalStateException::class.java) {
+            var nElements by mutableStateOf(1)
+            rule.setContent {
+                TestSceneScope {
+                    Column {
+                        val childModifier = Modifier.element(key)
+                        repeat(nElements) { Box(childModifier) }
+                    }
+                }
+            }
+
+            nElements = 2
+            rule.waitForIdle()
+        }
+    }
+
+    @Test
+    fun throwsExceptionWhenElementIsComposedMultipleTimes_updatedNode() {
+        assertThrows(IllegalStateException::class.java) {
+            var key by mutableStateOf(TestElements.Foo)
+            rule.setContent {
+                TestSceneScope {
+                    Column {
+                        Box(Modifier.element(key))
+                        Box(Modifier.element(TestElements.Bar))
+                    }
+                }
+            }
+
+            key = TestElements.Bar
+            rule.waitForIdle()
+        }
+    }
+
+    @Test
+    fun elementModifierSupportsUpdates() {
+        var key by mutableStateOf(TestElements.Foo)
+        var nullableLayoutImpl: SceneTransitionLayoutImpl? = null
+
+        rule.setContent {
+            SceneTransitionLayoutForTesting(
+                currentScene = TestScenes.SceneA,
+                onChangeScene = {},
+                transitions = remember { transitions {} },
+                state = remember { SceneTransitionLayoutState(TestScenes.SceneA) },
+                edgeDetector = DefaultEdgeDetector,
+                modifier = Modifier,
+                transitionInterceptionThreshold = 0f,
+                onLayoutImpl = { nullableLayoutImpl = it },
+            ) {
+                scene(TestScenes.SceneA) { Box(Modifier.element(key)) }
+            }
+        }
+
+        assertThat(nullableLayoutImpl).isNotNull()
+        val layoutImpl = nullableLayoutImpl!!
+
+        // There is only Foo in the elements map.
+        assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
+        val fooElement = layoutImpl.elements.getValue(TestElements.Foo)
+        assertThat(fooElement.sceneValues.keys).containsExactly(TestScenes.SceneA)
+
+        key = TestElements.Bar
+
+        // There is only Bar in the elements map and foo scene values was cleaned up.
+        rule.waitForIdle()
+        assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Bar)
+        val barElement = layoutImpl.elements.getValue(TestElements.Bar)
+        assertThat(barElement.sceneValues.keys).containsExactly(TestScenes.SceneA)
+        assertThat(fooElement.sceneValues).isEmpty()
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
new file mode 100644
index 0000000..94c51ca
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SceneTransitionLayoutStateTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun isTransitioningTo_idle() {
+        val state = SceneTransitionLayoutState(TestScenes.SceneA)
+
+        assertThat(state.isTransitioning()).isFalse()
+        assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
+        assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
+        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
+            .isFalse()
+    }
+
+    @Test
+    fun isTransitioningTo_fromSceneEqualToToScene() {
+        val state = SceneTransitionLayoutState(TestScenes.SceneA)
+        state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneA)
+
+        assertThat(state.isTransitioning()).isFalse()
+        assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
+        assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
+        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
+            .isFalse()
+    }
+
+    @Test
+    fun isTransitioningTo_transition() {
+        val state = SceneTransitionLayoutState(TestScenes.SceneA)
+        state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneB)
+
+        assertThat(state.isTransitioning()).isTrue()
+        assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
+        assertThat(state.isTransitioning(from = TestScenes.SceneB)).isFalse()
+        assertThat(state.isTransitioning(to = TestScenes.SceneB)).isTrue()
+        assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse()
+        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
+    }
+
+    private fun transition(from: SceneKey, to: SceneKey): TransitionState.Transition {
+        return object : TransitionState.Transition {
+            override val currentScene: SceneKey = from
+            override val fromScene: SceneKey = from
+            override val toScene: SceneKey = to
+            override val progress: Float = 0f
+            override val isInitiatedByUserInput: Boolean = false
+            override val isUserInputOngoing: Boolean = false
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
index b4c393e..b83705a 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
@@ -26,6 +26,7 @@
     val SceneA = SceneKey("SceneA")
     val SceneB = SceneKey("SceneB")
     val SceneC = SceneKey("SceneC")
+    val SceneD = SceneKey("SceneD")
 }
 
 /** Element keys that can be reused by tests. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 4175937..b064391 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -83,6 +83,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter;
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
 import com.android.systemui.log.SessionTracker;
@@ -170,6 +171,7 @@
     @NonNull private final SelectedUserInteractor mSelectedUserInteractor;
     @NonNull private final FpsUnlockTracker mFpsUnlockTracker;
     private final boolean mIgnoreRefreshRate;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
@@ -283,8 +285,8 @@
                         mPrimaryBouncerInteractor,
                         mAlternateBouncerInteractor,
                         mUdfpsKeyguardAccessibilityDelegate,
-                        mUdfpsKeyguardViewModels,
-                            mSelectedUserInteractor
+                        mKeyguardTransitionInteractor,
+                        mSelectedUserInteractor
                     )));
         }
 
@@ -649,7 +651,8 @@
             @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate,
             @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider,
             @NonNull SelectedUserInteractor selectedUserInteractor,
-            @NonNull FpsUnlockTracker fpsUnlockTracker) {
+            @NonNull FpsUnlockTracker fpsUnlockTracker,
+            @NonNull KeyguardTransitionInteractor keyguardTransitionInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -695,6 +698,7 @@
         mSelectedUserInteractor = selectedUserInteractor;
         mFpsUnlockTracker = fpsUnlockTracker;
         mFpsUnlockTracker.startTracking();
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
 
         mTouchProcessor = singlePointerTouchProcessor;
         mSessionTracker = sessionTracker;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 934f9f9..8f31a2d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -51,8 +51,8 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -63,7 +63,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import javax.inject.Provider
 
 private const val TAG = "UdfpsControllerOverlay"
 
@@ -102,7 +101,7 @@
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
     private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
-    private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>,
+    private val transitionInteractor: KeyguardTransitionInteractor,
     private val selectedUserInteractor: SelectedUserInteractor,
 ) {
     /** The view, when [isShowing], or null. */
@@ -264,11 +263,11 @@
                         dialogManager,
                         controller,
                         activityLaunchAnimator,
-                        featureFlags,
                         primaryBouncerInteractor,
                         alternateBouncerInteractor,
                         udfpsKeyguardAccessibilityDelegate,
                         selectedUserInteractor,
+                        transitionInteractor,
                     )
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index d7df0e5..2c4ed58 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.biometrics
 
-import android.animation.ValueAnimator
 import android.content.res.Configuration
 import android.util.MathUtils
 import android.view.View
@@ -27,17 +26,17 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI
@@ -48,10 +47,14 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import java.io.PrintWriter
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.launch
 
 /** Class that coordinates non-HBM animations during keyguard authentication. */
+@ExperimentalCoroutinesApi
 open class UdfpsKeyguardViewControllerLegacy(
     private val view: UdfpsKeyguardViewLegacy,
     statusBarStateController: StatusBarStateController,
@@ -65,11 +68,11 @@
     systemUIDialogManager: SystemUIDialogManager,
     private val udfpsController: UdfpsController,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
-    featureFlags: FeatureFlags,
     primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
     private val selectedUserInteractor: SelectedUserInteractor,
+    private val transitionInteractor: KeyguardTransitionInteractor,
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
         view,
@@ -91,44 +94,10 @@
     private var launchTransitionFadingAway = false
     private var isLaunchingActivity = false
     private var activityLaunchProgress = 0f
-    private val unlockedScreenOffDozeAnimator =
-        ValueAnimator.ofFloat(0f, 1f).apply {
-            duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong()
-            interpolator = Interpolators.ALPHA_IN
-            addUpdateListener { animation ->
-                view.onDozeAmountChanged(
-                    animation.animatedFraction,
-                    animation.animatedValue as Float,
-                    UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF
-                )
-            }
-        }
     private var inputBouncerExpansion = 0f
 
     private val stateListener: StatusBarStateController.StateListener =
         object : StatusBarStateController.StateListener {
-            override fun onDozeAmountChanged(linear: Float, eased: Float) {
-                if (lastDozeAmount < linear) {
-                    showUdfpsBouncer(false)
-                }
-                unlockedScreenOffDozeAnimator.cancel()
-                val animatingFromUnlockedScreenOff =
-                    unlockedScreenOffAnimationController.isAnimationPlaying()
-                if (animatingFromUnlockedScreenOff && linear != 0f) {
-                    // we manually animate the fade in of the UDFPS icon since the unlocked
-                    // screen off animation prevents the doze amounts to be incrementally eased in
-                    unlockedScreenOffDozeAnimator.start()
-                } else {
-                    view.onDozeAmountChanged(
-                        linear,
-                        eased,
-                        UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
-                    )
-                }
-                lastDozeAmount = linear
-                updatePauseAuth()
-            }
-
             override fun onStateChanged(statusBarState: Int) {
                 this@UdfpsKeyguardViewControllerLegacy.statusBarState = statusBarState
                 updateAlpha()
@@ -222,11 +191,39 @@
             repeatOnLifecycle(Lifecycle.State.CREATED) {
                 listenForBouncerExpansion(this)
                 listenForAlternateBouncerVisibility(this)
+                listenForGoneToAodTransition(this)
+                listenForLockscreenAodTransitions(this)
             }
         }
     }
 
     @VisibleForTesting
+    suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job {
+        return scope.launch {
+            transitionInteractor.goneToAodTransition.collect { transitionStep ->
+                view.onDozeAmountChanged(
+                    transitionStep.value,
+                    transitionStep.value,
+                    ANIMATION_UNLOCKED_SCREEN_OFF,
+                )
+            }
+        }
+    }
+
+    @VisibleForTesting
+    suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job {
+        return scope.launch {
+            transitionInteractor.dozeAmountTransition.collect { transitionStep ->
+                  view.onDozeAmountChanged(
+                      transitionStep.value,
+                      transitionStep.value,
+                      UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN,
+                  )
+              }
+        }
+    }
+
+    @VisibleForTesting
     override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
         return scope.launch {
             primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
index 95e3a76..f4ed8ce 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java
@@ -73,9 +73,6 @@
     // AOD anti-burn-in offsets
     private final int mMaxBurnInOffsetX;
     private final int mMaxBurnInOffsetY;
-    private float mBurnInOffsetX;
-    private float mBurnInOffsetY;
-    private float mBurnInProgress;
     private float mInterpolatedDarkAmount;
     private int mAnimationType = ANIMATION_NONE;
     private boolean mFullyInflated;
@@ -138,20 +135,22 @@
         // AoD-burn in location, else we need to translate the icon from LS => AoD.
         final float darkAmountForAnimation = mAnimationType == ANIMATION_UNLOCKED_SCREEN_OFF
                 ? 1f : mInterpolatedDarkAmount;
-        mBurnInOffsetX = MathUtils.lerp(0f,
+        final float burnInOffsetX = MathUtils.lerp(0f,
             getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
                 - mMaxBurnInOffsetX, darkAmountForAnimation);
-        mBurnInOffsetY = MathUtils.lerp(0f,
+        final float burnInOffsetY = MathUtils.lerp(0f,
             getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
                 - mMaxBurnInOffsetY, darkAmountForAnimation);
-        mBurnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), darkAmountForAnimation);
+        final float burnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(),
+                darkAmountForAnimation);
 
         if (mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN && !mPauseAuth) {
-            mLockScreenFp.setTranslationX(mBurnInOffsetX);
-            mLockScreenFp.setTranslationY(mBurnInOffsetY);
+            mLockScreenFp.setTranslationX(burnInOffsetX);
+            mLockScreenFp.setTranslationY(burnInOffsetY);
             mBgProtection.setAlpha(1f - mInterpolatedDarkAmount);
             mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount);
         } else if (darkAmountForAnimation == 0f) {
+            // we're on the lockscreen and should use mAlpha (changes based on shade expansion)
             mLockScreenFp.setTranslationX(0);
             mLockScreenFp.setTranslationY(0);
             mBgProtection.setAlpha(mAlpha / 255f);
@@ -162,9 +161,9 @@
         }
         mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount);
 
-        mAodFp.setTranslationX(mBurnInOffsetX);
-        mAodFp.setTranslationY(mBurnInOffsetY);
-        mAodFp.setProgress(mBurnInProgress);
+        mAodFp.setTranslationX(burnInOffsetX);
+        mAodFp.setTranslationY(burnInOffsetY);
+        mAodFp.setProgress(burnInProgress);
         mAodFp.setAlpha(mInterpolatedDarkAmount);
 
         // done animating
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 78f2da5..789a1e4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -279,6 +279,11 @@
     }
 
     @Override
+    public void onNullBinding(ComponentName name) {
+        executeSetBindService(false);
+    }
+
+    @Override
     public void onServiceDisconnected(ComponentName name) {
         if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name);
         handleDeath();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index c5f16aa..5f0d4d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -43,7 +43,7 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -68,7 +68,6 @@
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
-import javax.inject.Provider
 import org.mockito.Mockito.`when` as whenever
 
 private const val REQUEST_ID = 2L
@@ -111,11 +110,10 @@
     @Mock private lateinit var featureFlags: FeatureFlags
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
-    @Mock private lateinit var udfpsUtils: UdfpsUtils
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
     @Mock private lateinit var udfpsKeyguardAccessibilityDelegate:
             UdfpsKeyguardAccessibilityDelegate
-    @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -164,7 +162,7 @@
             alternateBouncerInteractor,
             isDebuggable,
             udfpsKeyguardAccessibilityDelegate,
-            udfpsKeyguardViewModels,
+            keyguardTransitionInteractor,
             mSelectedUserInteractor,
         )
         block()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 675ca63..c8c400d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -86,6 +86,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
@@ -237,6 +238,8 @@
     private ViewRootImpl mViewRootImpl;
     @Mock
     private FpsUnlockTracker mFpsUnlockTracker;
+    @Mock
+    private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
 
     @Before
     public void setUp() {
@@ -329,7 +332,8 @@
                 mUdfpsKeyguardAccessibilityDelegate,
                 mUdfpsKeyguardViewModels,
                 mSelectedUserInteractor,
-                mFpsUnlockTracker
+                mFpsUnlockTracker,
+                mKeyguardTransitionInteractor
         );
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 2c4e136..2ea803c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -31,6 +31,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -71,6 +72,7 @@
     protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor;
     protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate;
     protected @Mock SelectedUserInteractor mSelectedUserInteractor;
+    protected @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor;
 
     protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
@@ -141,11 +143,11 @@
                 mDialogManager,
                 mUdfpsController,
                 mActivityLaunchAnimator,
-                mFeatureFlags,
                 mPrimaryBouncerInteractor,
                 mAlternateBouncerInteractor,
                 mUdfpsKeyguardAccessibilityDelegate,
-                mSelectedUserInteractor);
+                mSelectedUserInteractor,
+                mKeyguardTransitionInteractor);
         return controller;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
index 21928cd..98d8b05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
@@ -66,17 +66,12 @@
     public void testViewControllerQueriesSBStateOnAttached() {
         mController.onViewAttached();
         verify(mStatusBarStateController).getState();
-        verify(mStatusBarStateController).getDozeAmount();
 
-        final float dozeAmount = .88f;
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
-        when(mStatusBarStateController.getDozeAmount()).thenReturn(dozeAmount);
         captureStatusBarStateListeners();
 
         mController.onViewAttached();
         verify(mView, atLeast(1)).setPauseAuth(true);
-        verify(mView).onDozeAmountChanged(dozeAmount, dozeAmount,
-                UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
     }
 
     @Test
@@ -91,19 +86,6 @@
     }
 
     @Test
-    public void testDozeEventsSentToView() {
-        mController.onViewAttached();
-        captureStatusBarStateListeners();
-
-        final float linear = .55f;
-        final float eased = .65f;
-        mStatusBarStateListener.onDozeAmountChanged(linear, eased);
-
-        verify(mView).onDozeAmountChanged(linear, eased,
-                UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
-    }
-
-    @Test
     public void testShouldPauseAuthUnpausedAlpha0() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 97dada2..a49150f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -31,7 +31,12 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
@@ -49,6 +54,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.mock
@@ -65,6 +71,7 @@
     private val testScope = TestScope(testDispatcher)
 
     private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
 
     @Mock private lateinit var bouncerLogger: TableLogBuffer
 
@@ -78,6 +85,7 @@
                 testScope.backgroundScope,
                 bouncerLogger,
             )
+        transitionRepository = FakeKeyguardTransitionRepository()
         super.setUp()
     }
 
@@ -107,6 +115,12 @@
                 mock(SystemClock::class.java),
                 mKeyguardUpdateMonitor,
             )
+        mKeyguardTransitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                )
+                .keyguardTransitionInteractor
         return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
     }
 
@@ -258,4 +272,145 @@
 
             job.cancel()
         }
+
+    @Test
+    fun aodToLockscreen_dozeAmountChanged() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForLockscreenAodTransitions(this)
+
+            // WHEN transitioning from lockscreen to aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = .3f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            runCurrent()
+            // THEN doze amount is updated
+            verify(mView)
+                .onDozeAmountChanged(
+                    eq(.3f),
+                    eq(.3f),
+                    eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)
+                )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                )
+            )
+            runCurrent()
+            // THEN doze amount is updated
+            verify(mView)
+                .onDozeAmountChanged(
+                    eq(1f),
+                    eq(1f),
+                    eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun lockscreenToAod_dozeAmountChanged() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForLockscreenAodTransitions(this)
+
+            // WHEN transitioning from lockscreen to aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = .3f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            runCurrent()
+            // THEN doze amount is updated
+            verify(mView)
+                .onDozeAmountChanged(
+                    eq(.3f),
+                    eq(.3f),
+                    eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)
+                )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                )
+            )
+            runCurrent()
+            // THEN doze amount is updated
+            verify(mView)
+                .onDozeAmountChanged(
+                    eq(1f),
+                    eq(1f),
+                    eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun goneToAod_dozeAmountChanged() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            Mockito.reset(mView)
+
+            val job = mController.listenForGoneToAodTransition(this)
+
+            // WHEN transitioning from lockscreen to aod
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = .3f,
+                    transitionState = TransitionState.RUNNING
+                )
+            )
+            runCurrent()
+            // THEN doze amount is updated
+            verify(mView)
+                .onDozeAmountChanged(
+                    eq(.3f),
+                    eq(.3f),
+                    eq(UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF)
+                )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                )
+            )
+            runCurrent()
+            // THEN doze amount is updated
+            verify(mView)
+                .onDozeAmountChanged(
+                    eq(1f),
+                    eq(1f),
+                    eq(UdfpsKeyguardViewLegacy.ANIMATION_UNLOCKED_SCREEN_OFF)
+                )
+
+            job.cancel()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 3b07913..5245b22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -27,7 +27,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -41,7 +40,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
-import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.AndroidTestingRunner;
 import android.util.SparseArray;
 import android.view.View;
@@ -82,7 +80,6 @@
 import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -107,10 +104,6 @@
             ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
     private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
     private static final String SETTING = QSHost.TILES_SETTING;
-
-    @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
     @Mock
     private PluginManager mPluginManager;
     @Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 6cc52d7..fbd63c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -403,6 +403,31 @@
         verify(falseContext).bindServiceAsUser(any(), any(), eq(flags), any());
     }
 
+    @Test
+    public void testNullBindingCallsUnbind() {
+        Context mockContext = mock(Context.class);
+        // Binding has to succeed
+        when(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true);
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mockContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mExecutor);
+
+        manager.executeSetBindService(true);
+        mExecutor.runAllReady();
+
+        ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+        verify(mockContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any());
+
+        captor.getValue().onNullBinding(mTileServiceComponentName);
+        mExecutor.runAllReady();
+        verify(mockContext).unbindService(captor.getValue());
+    }
+
     private void mockChangeEnabled(long changeId, boolean enabled) {
         doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
                 any(UserHandle.class)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 355ca81..8c896a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -21,7 +21,6 @@
 import android.content.Intent
 import android.content.pm.UserInfo
 import android.os.UserHandle
-import android.platform.test.flag.junit.SetFlagsRule
 import android.service.quicksettings.Tile
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -62,7 +61,6 @@
 import kotlinx.coroutines.test.runCurrent
 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.ArgumentMatchers.anyString
@@ -77,8 +75,6 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 class CurrentTilesInteractorImplTest : SysuiTestCase() {
 
-    @Rule @JvmField val setFlagsRule = SetFlagsRule()
-
     private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
     private val userRepository = FakeUserRepository()
     private val installedTilesPackageRepository = FakeInstalledTilesComponentRepository()
@@ -109,7 +105,7 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        setFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
+        mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
         // TODO(b/299909337): Add test checking the new factory is used when the flag is on
         featureFlags.set(Flags.QS_PIPELINE_NEW_TILES, true)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
index 62ca965..2e63708 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
@@ -1,13 +1,11 @@
 package com.android.systemui.qs.pipeline.shared
 
-import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -15,22 +13,20 @@
 @RunWith(AndroidJUnit4::class)
 class QSPipelineFlagsRepositoryTest : SysuiTestCase() {
 
-    @Rule @JvmField val setFlagsRule = SetFlagsRule()
-
     private val fakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
 
     private val underTest = QSPipelineFlagsRepository(fakeFeatureFlagsClassic)
 
     @Test
     fun pipelineFlagDisabled() {
-        setFlagsRule.disableFlags(Flags.FLAG_QS_NEW_PIPELINE)
+        mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_PIPELINE)
 
         assertThat(underTest.pipelineEnabled).isFalse()
     }
 
     @Test
     fun pipelineFlagEnabled() {
-        setFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
+        mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
 
         assertThat(underTest.pipelineEnabled).isTrue()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 1d8a346..84cd518 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
 import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
 import static com.android.systemui.statusbar.phone.AutoTileManager.DEVICE_CONTROLS;
 
@@ -135,6 +136,8 @@
         MockitoAnnotations.initMocks(this);
         mSecureSettings = new FakeSettings();
 
+        mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
+
         mContext.getOrCreateTestableResources().addOverride(
                 R.array.config_quickSettingsAutoAdd,
                 new String[] {
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index fc4ed1d..b9e34ee 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -33,3 +33,10 @@
     ],
     visibility: ["//visibility:public"],
 }
+
+java_host_for_device {
+    name: "core-xml-for-device",
+    libs: [
+        "core-xml-for-host",
+    ],
+}
diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt
index 692d598..3e54c7a 100644
--- a/ravenwood/framework-minus-apex-ravenwood-policies.txt
+++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt
@@ -103,7 +103,21 @@
 # Containers
 class android.os.BaseBundle stubclass
 class android.os.Bundle stubclass
+class android.os.PersistableBundle stubclass
 
 # Misc
 class android.os.PatternMatcher stubclass
 class android.os.ParcelUuid stubclass
+
+# XML
+class com.android.internal.util.XmlPullParserWrapper stubclass
+class com.android.internal.util.XmlSerializerWrapper stubclass
+class com.android.internal.util.XmlUtils stubclass
+
+class com.android.modules.utils.BinaryXmlPullParser stubclass
+class com.android.modules.utils.BinaryXmlSerializer stubclass
+class com.android.modules.utils.FastDataInput stubclass
+class com.android.modules.utils.FastDataOutput stubclass
+class com.android.modules.utils.ModifiedUtf8 stubclass
+class com.android.modules.utils.TypedXmlPullParser stubclass
+class com.android.modules.utils.TypedXmlSerializer stubclass
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 776a19a..1ac6bf0 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -2,8 +2,11 @@
 
 com.android.internal.util.ArrayUtils
 
+android.util.Xml
+
 android.os.Binder
 android.os.Binder$IdentitySupplier
 android.os.IBinder
 android.os.Process
 android.os.SystemClock
+android.os.UserHandle
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f9fc4d4..36356bd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -111,6 +111,7 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.ParseUtils;
 import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.net.module.util.NetworkCapabilitiesUtils;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
@@ -475,7 +476,12 @@
                 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
         final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class);
         try {
-            nms.registerObserver(mActivityChangeObserver);
+            if (!SdkLevel.isAtLeastV()) {
+                // On V+ devices, ConnectivityService calls BatteryStats API to update
+                // RadioPowerState change. So BatteryStatsService registers the callback only on
+                // pre V devices.
+                nms.registerObserver(mActivityChangeObserver);
+            }
             cm.registerDefaultNetworkCallback(mNetworkCallback);
         } catch (RemoteException e) {
             Slog.e(TAG, "Could not register INetworkManagement event observer " + e);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a0beedb..b99de5c 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -71,7 +71,7 @@
 import com.android.server.display.config.RefreshRateZone;
 import com.android.server.display.config.SdrHdrRatioMap;
 import com.android.server.display.config.SdrHdrRatioPoint;
-import com.android.server.display.config.SensorDetails;
+import com.android.server.display.config.SensorData;
 import com.android.server.display.config.ThermalStatus;
 import com.android.server.display.config.ThermalThrottling;
 import com.android.server.display.config.ThresholdPoint;
@@ -349,6 +349,20 @@
  *      <proxSensor>
  *        <type>android.sensor.proximity</type>
  *        <name>1234 Proximity Sensor</name>
+ *        <refreshRate>
+ *             <minimum>60</minimum>
+ *             <maximum>60</maximum>
+ *         </refreshRate>
+ *         <supportedModes>
+ *             <point>
+ *                 <first>60</first>   // refreshRate
+ *                 <second>60</second> //vsyncRate
+ *             </point>
+ *             <point>
+ *                 <first>120</first>   // refreshRate
+ *                 <second>120</second> //vsyncRate
+ *             </point>
+ *          </supportedModes>
  *      </proxSensor>
  *
  *      <ambientLightHorizonLong>10001</ambientLightHorizonLong>
@@ -581,15 +595,15 @@
     private final Context mContext;
 
     // The details of the ambient light sensor associated with this display.
-    private final SensorData mAmbientLightSensor = new SensorData();
+    private SensorData mAmbientLightSensor;
 
     // The details of the doze brightness sensor associated with this display.
-    private final SensorData mScreenOffBrightnessSensor = new SensorData();
+    private SensorData mScreenOffBrightnessSensor;
 
     // The details of the proximity sensor associated with this display.
     // Is null when no sensor should be used for that display
     @Nullable
-    private SensorData mProximitySensor = new SensorData();
+    private SensorData mProximitySensor;
 
     private final List<RefreshRateLimitation> mRefreshRateLimitations =
             new ArrayList<>(2 /*initialCapacity*/);
@@ -1913,9 +1927,10 @@
                 loadLuxThrottling(config);
                 loadQuirks(config);
                 loadBrightnessRamps(config);
-                loadAmbientLightSensorFromDdc(config);
-                loadScreenOffBrightnessSensorFromDdc(config);
-                loadProxSensorFromDdc(config);
+                mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(config,
+                        mContext.getResources());
+                mScreenOffBrightnessSensor = SensorData.loadScreenOffBrightnessSensorConfig(config);
+                mProximitySensor = SensorData.loadProxSensorConfig(config);
                 loadAmbientHorizonFromDdc(config);
                 loadBrightnessChangeThresholds(config);
                 loadAutoBrightnessConfigValues(config);
@@ -1940,9 +1955,9 @@
         loadBrightnessConstraintsFromConfigXml();
         loadBrightnessMapFromConfigXml();
         loadBrightnessRampsFromConfigXml();
-        loadAmbientLightSensorFromConfigXml();
+        mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources());
+        mProximitySensor = SensorData.loadSensorUnspecifiedConfig();
         loadBrightnessChangeThresholdsFromXml();
-        setProxSensorUnspecified();
         loadAutoBrightnessConfigsFromConfigXml();
         loadAutoBrightnessAvailableFromConfigXml();
         loadRefreshRateSetting(null);
@@ -1966,8 +1981,8 @@
         mBrightnessRampDecreaseMaxIdleMillis = 0;
         mBrightnessRampIncreaseMaxIdleMillis = 0;
         setSimpleMappingStrategyValues();
-        loadAmbientLightSensorFromConfigXml();
-        setProxSensorUnspecified();
+        mAmbientLightSensor = SensorData.loadAmbientLightSensorConfig(mContext.getResources());
+        mProximitySensor = SensorData.loadSensorUnspecifiedConfig();
         loadAutoBrightnessAvailableFromConfigXml();
     }
 
@@ -2919,64 +2934,10 @@
         mBrightnessRampSlowDecrease = mBrightnessRampSlowIncrease;
     }
 
-    private void loadAmbientLightSensorFromConfigXml() {
-        mAmbientLightSensor.name = "";
-        mAmbientLightSensor.type = mContext.getResources().getString(
-                com.android.internal.R.string.config_displayLightSensorType);
-    }
-
     private void loadAutoBrightnessConfigsFromConfigXml() {
         loadAutoBrightnessDisplayBrightnessMapping(null /*AutoBrightnessConfig*/);
     }
 
-    private void loadAmbientLightSensorFromDdc(DisplayConfiguration config) {
-        final SensorDetails sensorDetails = config.getLightSensor();
-        if (sensorDetails != null) {
-            loadSensorData(sensorDetails, mAmbientLightSensor);
-        } else {
-            loadAmbientLightSensorFromConfigXml();
-        }
-    }
-
-    private void setProxSensorUnspecified() {
-        mProximitySensor = new SensorData();
-    }
-
-    private void loadScreenOffBrightnessSensorFromDdc(DisplayConfiguration config) {
-        final SensorDetails sensorDetails = config.getScreenOffBrightnessSensor();
-        if (sensorDetails != null) {
-            loadSensorData(sensorDetails, mScreenOffBrightnessSensor);
-        }
-    }
-
-    private void loadProxSensorFromDdc(DisplayConfiguration config) {
-        SensorDetails sensorDetails = config.getProxSensor();
-        if (sensorDetails != null) {
-            String name = sensorDetails.getName();
-            String type = sensorDetails.getType();
-            if ("".equals(name) && "".equals(type)) {
-                // <proxSensor> with empty values to the config means no sensor should be used
-                mProximitySensor = null;
-            } else {
-                mProximitySensor = new SensorData();
-                loadSensorData(sensorDetails, mProximitySensor);
-            }
-        } else {
-            setProxSensorUnspecified();
-        }
-    }
-
-    private void loadSensorData(@NonNull SensorDetails sensorDetails,
-            @NonNull SensorData sensorData) {
-        sensorData.name = sensorDetails.getName();
-        sensorData.type = sensorDetails.getType();
-        final RefreshRateRange rr = sensorDetails.getRefreshRate();
-        if (rr != null) {
-            sensorData.minRefreshRate = rr.getMinimum().floatValue();
-            sensorData.maxRefreshRate = rr.getMaximum().floatValue();
-        }
-    }
-
     private void loadBrightnessChangeThresholdsFromXml() {
         loadBrightnessChangeThresholds(/* config= */ null);
     }
@@ -3390,37 +3351,6 @@
     }
 
     /**
-     * Uniquely identifies a Sensor, with the combination of Type and Name.
-     */
-    public static class SensorData {
-        public String type;
-        public String name;
-        public float minRefreshRate = 0.0f;
-        public float maxRefreshRate = Float.POSITIVE_INFINITY;
-
-        @Override
-        public String toString() {
-            return "Sensor{"
-                    + "type: " + type
-                    + ", name: " + name
-                    + ", refreshRateRange: [" + minRefreshRate + ", " + maxRefreshRate + "]"
-                    + "} ";
-        }
-
-        /**
-         * @return True if the sensor matches both the specified name and type, or one if only one
-         * is specified (not-empty). Always returns false if both parameters are null or empty.
-         */
-        public boolean matches(String sensorName, String sensorType) {
-            final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
-            final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType);
-            return (isNameSpecified || isTypeSpecified)
-                    && (!isNameSpecified || sensorName.equals(name))
-                    && (!isTypeSpecified || sensorType.equals(type));
-        }
-    }
-
-    /**
      * Container for high brightness mode configuration data.
      */
     static class HighBrightnessModeData {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index eae153c..8046dbf 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -158,7 +158,7 @@
 import com.android.server.SystemService;
 import com.android.server.UiThread;
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
-import com.android.server.display.DisplayDeviceConfig.SensorData;
+import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java
new file mode 100644
index 0000000..3bb35bf
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/SensorData.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Uniquely identifies a Sensor, with the combination of Type and Name.
+ */
+public class SensorData {
+
+    @Nullable
+    public final String type;
+    @Nullable
+    public final String name;
+    public final float minRefreshRate;
+    public final float maxRefreshRate;
+    public final List<SupportedMode> supportedModes;
+
+    @VisibleForTesting
+    public SensorData() {
+        this(/* type= */ null, /* name= */ null);
+    }
+
+    @VisibleForTesting
+    public SensorData(String type, String name) {
+        this(type, name, /* minRefreshRate= */ 0f, /* maxRefreshRate= */ Float.POSITIVE_INFINITY);
+    }
+
+    @VisibleForTesting
+    public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate) {
+        this(type, name, minRefreshRate, maxRefreshRate, /* supportedModes= */ List.of());
+    }
+
+    @VisibleForTesting
+    public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate,
+            List<SupportedMode> supportedModes) {
+        this.type = type;
+        this.name = name;
+        this.minRefreshRate = minRefreshRate;
+        this.maxRefreshRate = maxRefreshRate;
+        this.supportedModes = Collections.unmodifiableList(supportedModes);
+    }
+
+    /**
+     * @return True if the sensor matches both the specified name and type, or one if only one
+     * is specified (not-empty). Always returns false if both parameters are null or empty.
+     */
+    public boolean matches(String sensorName, String sensorType) {
+        final boolean isNameSpecified = !TextUtils.isEmpty(sensorName);
+        final boolean isTypeSpecified = !TextUtils.isEmpty(sensorType);
+        return (isNameSpecified || isTypeSpecified)
+                && (!isNameSpecified || sensorName.equals(name))
+                && (!isTypeSpecified || sensorType.equals(type));
+    }
+
+    @Override
+    public String toString() {
+        return "SensorData{"
+                + "type= " + type
+                + ", name= " + name
+                + ", refreshRateRange: [" + minRefreshRate + ", " + maxRefreshRate + "]"
+                + ", supportedModes=" + supportedModes
+                + '}';
+    }
+
+    /**
+     * Loads ambient light sensor data from DisplayConfiguration and if missing from resources xml
+     */
+    public static SensorData loadAmbientLightSensorConfig(DisplayConfiguration config,
+            Resources resources) {
+        SensorDetails sensorDetails = config.getLightSensor();
+        if (sensorDetails != null) {
+            return loadSensorData(sensorDetails);
+        } else {
+            return loadAmbientLightSensorConfig(resources);
+        }
+    }
+
+    /**
+     * Loads ambient light sensor data from resources xml
+     */
+    public static SensorData loadAmbientLightSensorConfig(Resources resources) {
+        return new SensorData(
+                resources.getString(com.android.internal.R.string.config_displayLightSensorType),
+                /* name= */ "");
+    }
+
+    /**
+     * Loads screen off brightness sensor data from DisplayConfiguration
+     */
+    public static SensorData loadScreenOffBrightnessSensorConfig(DisplayConfiguration config) {
+        SensorDetails sensorDetails = config.getScreenOffBrightnessSensor();
+        if (sensorDetails != null) {
+            return loadSensorData(sensorDetails);
+        } else {
+            return new SensorData();
+        }
+    }
+
+    /**
+     * Loads proximity sensor data from DisplayConfiguration
+     */
+    @Nullable
+    public static SensorData loadProxSensorConfig(DisplayConfiguration config) {
+        SensorDetails sensorDetails = config.getProxSensor();
+        if (sensorDetails != null) {
+            String name = sensorDetails.getName();
+            String type = sensorDetails.getType();
+            if ("".equals(name) && "".equals(type)) {
+                // <proxSensor> with empty values to the config means no sensor should be used.
+                // See also {@link com.android.server.display.utils.SensorUtils}
+                return null;
+            } else {
+                return loadSensorData(sensorDetails);
+            }
+        } else {
+            return new SensorData();
+        }
+    }
+
+    /**
+     * Loads sensor unspecified config, this means system should use default sensor.
+     * See also {@link com.android.server.display.utils.SensorUtils}
+     */
+    @NonNull
+    public static SensorData loadSensorUnspecifiedConfig() {
+        return new SensorData();
+    }
+
+    private static SensorData loadSensorData(@NonNull SensorDetails sensorDetails) {
+        float minRefreshRate = 0f;
+        float maxRefreshRate = Float.POSITIVE_INFINITY;
+        RefreshRateRange rr = sensorDetails.getRefreshRate();
+        if (rr != null) {
+            minRefreshRate = rr.getMinimum().floatValue();
+            maxRefreshRate = rr.getMaximum().floatValue();
+        }
+        ArrayList<SupportedMode> supportedModes = new ArrayList<>();
+        NonNegativeFloatToFloatMap configSupportedModes = sensorDetails.getSupportedModes();
+        if (configSupportedModes != null) {
+            for (NonNegativeFloatToFloatPoint supportedMode : configSupportedModes.getPoint()) {
+                supportedModes.add(new SupportedMode(supportedMode.getFirst().floatValue(),
+                        supportedMode.getSecond().floatValue()));
+            }
+        }
+
+        return new SensorData(sensorDetails.getType(), sensorDetails.getName(), minRefreshRate,
+                maxRefreshRate, supportedModes);
+    }
+
+    public static class SupportedMode {
+        public final float refreshRate;
+        public final float vsyncRate;
+
+        public SupportedMode(float refreshRate, float vsyncRate) {
+            this.refreshRate = refreshRate;
+            this.vsyncRate = vsyncRate;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 56321cd..8b9fe108 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -21,7 +21,7 @@
 import android.hardware.SensorManager;
 import android.text.TextUtils;
 
-import com.android.server.display.DisplayDeviceConfig;
+import com.android.server.display.config.SensorData;
 
 import java.util.List;
 
@@ -36,7 +36,7 @@
      */
     @Nullable
     public static Sensor findSensor(@Nullable SensorManager sensorManager,
-            @Nullable DisplayDeviceConfig.SensorData sensorData, int fallbackType) {
+            @Nullable SensorData sensorData, int fallbackType) {
         if (sensorData == null) {
             return null;
         } else {
diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java
index 392b86e..681d1a0 100644
--- a/services/core/java/com/android/server/net/NetworkManagementService.java
+++ b/services/core/java/com/android/server/net/NetworkManagementService.java
@@ -327,10 +327,10 @@
     /**
      * Notify our observers of a change in the data activity state of the interface
      */
-    private void notifyInterfaceClassActivity(int type, boolean isActive, long tsNanos,
+    private void notifyInterfaceClassActivity(int label, boolean isActive, long tsNanos,
             int uid) {
         invokeForAllObservers(o -> o.interfaceClassDataActivityChanged(
-                type, isActive, tsNanos, uid));
+                label, isActive, tsNanos, uid));
     }
 
     // Sync the state of the given chain with the native daemon.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8c75367..7e51526 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -706,6 +706,7 @@
     private boolean mNotificationEffectsEnabledForAutomotive;
     private DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener;
     protected NotificationAttentionHelper mAttentionHelper;
+    private boolean mFlagRefactorAttentionHelper;
 
     private int mWarnRemoteViewsSizeBytes;
     private int mStripRemoteViewsSizeBytes;
@@ -1189,7 +1190,7 @@
         @Override
         public void onSetDisabled(int status) {
             synchronized (mNotificationLock) {
-                if (Flags.refactorAttentionHelper()) {
+                if (mFlagRefactorAttentionHelper) {
                     mAttentionHelper.updateDisableNotificationEffectsLocked(status);
                 } else {
                     mDisableNotificationEffects =
@@ -1335,7 +1336,7 @@
         public void clearEffects() {
             synchronized (mNotificationLock) {
                 if (DBG) Slog.d(TAG, "clearEffects");
-                if (Flags.refactorAttentionHelper()) {
+                if (mFlagRefactorAttentionHelper) {
                     mAttentionHelper.clearAttentionEffects();
                 } else {
                     clearSoundLocked();
@@ -1564,7 +1565,7 @@
                         int changedFlags = data.getFlags() ^ flags;
                         if ((changedFlags & FLAG_SUPPRESS_NOTIFICATION) != 0) {
                             // Suppress notification flag changed, clear any effects
-                            if (Flags.refactorAttentionHelper()) {
+                            if (mFlagRefactorAttentionHelper) {
                                 mAttentionHelper.clearEffectsLocked(key);
                             } else {
                                 clearEffectsLocked(key);
@@ -1913,7 +1914,7 @@
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
 
-            if (!Flags.refactorAttentionHelper()) {
+            if (!mFlagRefactorAttentionHelper) {
                 if (action.equals(Intent.ACTION_SCREEN_ON)) {
                     // Keep track of screen on/off state, but do not turn off the notification light
                     // until user passes through the lock screen or views the notification.
@@ -2029,7 +2030,7 @@
             ContentResolver resolver = getContext().getContentResolver();
             resolver.registerContentObserver(NOTIFICATION_BADGING_URI,
                     false, this, UserHandle.USER_ALL);
-            if (!Flags.refactorAttentionHelper()) {
+            if (!mFlagRefactorAttentionHelper) {
                 resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
                     false, this, UserHandle.USER_ALL);
             }
@@ -2059,7 +2060,7 @@
 
         public void update(Uri uri) {
             ContentResolver resolver = getContext().getContentResolver();
-            if (!Flags.refactorAttentionHelper()) {
+            if (!mFlagRefactorAttentionHelper) {
                 if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
                     boolean pulseEnabled = Settings.System.getIntForUser(resolver,
                         Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT)
@@ -2560,7 +2561,9 @@
 
         mToastRateLimiter = toastRateLimiter;
 
-        if (Flags.refactorAttentionHelper()) {
+        //Cache aconfig flag value
+        mFlagRefactorAttentionHelper = Flags.refactorAttentionHelper();
+        if (mFlagRefactorAttentionHelper) {
             mAttentionHelper = new NotificationAttentionHelper(getContext(), lightsManager,
                 mAccessibilityManager, mPackageManagerClient, userManager, usageStats,
                 mNotificationManagerPrivate, mZenModeHelper, flagResolver);
@@ -2570,7 +2573,7 @@
         // If this is called within a test, make sure to unregister the intent receivers by
         // calling onDestroy()
         IntentFilter filter = new IntentFilter();
-        if (!Flags.refactorAttentionHelper()) {
+        if (!mFlagRefactorAttentionHelper) {
             filter.addAction(Intent.ACTION_SCREEN_ON);
             filter.addAction(Intent.ACTION_SCREEN_OFF);
             filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
@@ -2898,7 +2901,7 @@
             }
             registerNotificationPreferencesPullers();
             new LockPatternUtils(getContext()).registerStrongAuthTracker(mStrongAuthTracker);
-            if (Flags.refactorAttentionHelper()) {
+            if (mFlagRefactorAttentionHelper) {
                 mAttentionHelper.onSystemReady();
             }
         } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
@@ -6571,7 +6574,7 @@
                     pw.println("  mMaxPackageEnqueueRate=" + mMaxPackageEnqueueRate);
                     pw.println("  hideSilentStatusBar="
                             + mPreferencesHelper.shouldHideSilentStatusIcons());
-                    if (Flags.refactorAttentionHelper()) {
+                    if (mFlagRefactorAttentionHelper) {
                         mAttentionHelper.dump(pw, "    ", filter);
                     }
                 }
@@ -7042,9 +7045,8 @@
             channelId = (new Notification.TvExtender(notification)).getChannelId();
         }
         String shortcutId = n.getShortcutId();
-        final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
-                pkg, notificationUid, channelId, shortcutId,
-                true /* parent ok */, false /* includeDeleted */);
+        final NotificationChannel channel = getNotificationChannelRestoreDeleted(pkg,
+                callingUid, notificationUid, channelId, shortcutId);
         if (channel == null) {
             final String noChannelStr = "No Channel found for "
                     + "pkg=" + pkg
@@ -7162,6 +7164,35 @@
         return true;
     }
 
+    /**
+     * Returns a channel, if exists, and restores deleted conversation channels.
+     */
+    @Nullable
+    private NotificationChannel getNotificationChannelRestoreDeleted(String pkg,
+            int callingUid, int notificationUid, String channelId, String conversationId) {
+        // Restore a deleted conversation channel, if exists. Otherwise use the parent channel.
+        NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
+                pkg, notificationUid, channelId, conversationId,
+                true /* parent ok */, !TextUtils.isEmpty(conversationId) /* includeDeleted */);
+        // Restore deleted conversation channel
+        if (channel != null && channel.isDeleted()) {
+            if (Objects.equals(conversationId, channel.getConversationId())) {
+                boolean needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(
+                        pkg, notificationUid, channel, true /* fromTargetApp */,
+                        mConditionProviders.isPackageOrComponentAllowed(pkg,
+                        UserHandle.getUserId(notificationUid)), callingUid, true);
+                // Update policy file if the conversation channel was restored
+                if (needsPolicyFileChange) {
+                    handleSavePolicyFile();
+                }
+            } else {
+                // Do not restore parent channel
+                channel = null;
+            }
+        }
+        return channel;
+    }
+
     private void onConversationRemovedInternal(String pkg, int uid, Set<String> shortcuts) {
         checkCallerIsSystem();
         Preconditions.checkStringNotEmpty(pkg);
@@ -7844,7 +7875,7 @@
             boolean wasPosted = removeFromNotificationListsLocked(r);
             cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null,
                     SystemClock.elapsedRealtime());
-            if (Flags.refactorAttentionHelper()) {
+            if (mFlagRefactorAttentionHelper) {
                 mAttentionHelper.updateLightsLocked();
             } else {
                 updateLightsLocked();
@@ -7984,7 +8015,7 @@
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
                             mSendDelete, childrenFlagChecker, mReason,
                             mCancellationElapsedTimeMs);
-                    if (Flags.refactorAttentionHelper()) {
+                    if (mFlagRefactorAttentionHelper) {
                         mAttentionHelper.updateLightsLocked();
                     } else {
                         updateLightsLocked();
@@ -8281,7 +8312,7 @@
 
                     int buzzBeepBlinkLoggingCode = 0;
                     if (!r.isHidden()) {
-                        if (Flags.refactorAttentionHelper()) {
+                        if (mFlagRefactorAttentionHelper) {
                             buzzBeepBlinkLoggingCode = mAttentionHelper.buzzBeepBlinkLocked(r,
                                 new NotificationAttentionHelper.Signals(
                                     mUserProfiles.isCurrentProfile(r.getUserId()),
@@ -9268,7 +9299,7 @@
                     || interruptiveChanged;
             if (interceptBefore && !record.isIntercepted()
                     && record.isNewEnoughForAlerting(System.currentTimeMillis())) {
-                if (Flags.refactorAttentionHelper()) {
+                if (mFlagRefactorAttentionHelper) {
                     mAttentionHelper.buzzBeepBlinkLocked(record,
                         new NotificationAttentionHelper.Signals(
                             mUserProfiles.isCurrentProfile(record.getUserId()), mListenerHints));
@@ -9648,7 +9679,7 @@
                 });
             }
 
-            if (Flags.refactorAttentionHelper()) {
+            if (mFlagRefactorAttentionHelper) {
                 mAttentionHelper.clearEffectsLocked(canceledKey);
             } else {
                 // sound
@@ -10012,7 +10043,7 @@
                             cancellationElapsedTimeMs);
                 }
             }
-            if (Flags.refactorAttentionHelper()) {
+            if (mFlagRefactorAttentionHelper) {
                 mAttentionHelper.updateLightsLocked();
             } else {
                 updateLightsLocked();
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 81a570f..4e14c90 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1388,10 +1388,32 @@
 
         final long identity = Binder.clearCallingIdentity();
         try {
+            // QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL is only allowed for managed-profiles
+            if (dontAskCredential) {
+                UserInfo userInfo;
+                synchronized (mUsersLock) {
+                    userInfo = getUserInfo(userId);
+                }
+                if (!userInfo.isManagedProfile()) {
+                    throw new IllegalArgumentException("Invalid flags: " + flags
+                            + ". Can't skip credential check for the user");
+                }
+            }
             if (enableQuietMode) {
                 setQuietModeEnabled(userId, true /* enableQuietMode */, target, callingPackage);
                 return true;
             }
+            if (android.os.Flags.allowPrivateProfile()) {
+                final UserProperties userProperties = getUserPropertiesInternal(userId);
+                if (userProperties != null
+                        && userProperties.isAuthAlwaysRequiredToDisableQuietMode()) {
+                    if (onlyIfCredentialNotRequired) {
+                        return false;
+                    }
+                    showConfirmCredentialToDisableQuietMode(userId, target);
+                    return false;
+                }
+            }
             final boolean hasUnifiedChallenge =
                     mLockPatternUtils.isManagedProfileWithUnifiedChallenge(userId);
             if (hasUnifiedChallenge) {
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 29e0c35..7da76c1 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -193,6 +193,7 @@
                         .setStartWithParent(true)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
+                        .setAuthAlwaysRequiredToDisableQuietMode(false)
                         .setCredentialShareableWithParent(true));
     }
 
@@ -292,7 +293,8 @@
                 .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
                 .setDefaultUserProperties(new UserProperties.Builder()
                         .setStartWithParent(true)
-                        .setCredentialShareableWithParent(false)
+                        .setCredentialShareableWithParent(true)
+                        .setAuthAlwaysRequiredToDisableQuietMode(true)
                         .setMediaSharedWithParent(false)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
                         .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE)
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 215934f..cca4261 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -455,6 +455,20 @@
                 <xs:annotation name="nullable"/>
                 <xs:annotation name="final"/>
             </xs:element>
+            <!-- list of supported modes when sensor is ON. Each point corresponds to one mode.
+            Mode format is : first = refreshRate, second = vsyncRate. E.g. :
+            <supportedModes>
+                <point>
+                    <first>60</first>   // refreshRate
+                    <second>60</second> //vsyncRate
+                </point>
+                ....
+            </supportedModes>
+             -->
+            <xs:element type="nonNegativeFloatToFloatMap" name="supportedModes" minOccurs="0">
+                <xs:annotation name="nullable"/>
+                <xs:annotation name="final"/>
+            </xs:element>
         </xs:sequence>
     </xs:complexType>
 
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index f7e0043..f767291 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -349,9 +349,11 @@
     ctor public SensorDetails();
     method @Nullable public final String getName();
     method @Nullable public final com.android.server.display.config.RefreshRateRange getRefreshRate();
+    method @Nullable public final com.android.server.display.config.NonNegativeFloatToFloatMap getSupportedModes();
     method @Nullable public final String getType();
     method public final void setName(@Nullable String);
     method public final void setRefreshRate(@Nullable com.android.server.display.config.RefreshRateRange);
+    method public final void setSupportedModes(@Nullable com.android.server.display.config.NonNegativeFloatToFloatMap);
     method public final void setType(@Nullable String);
   }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 179a9d5..0bcbeb9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -17,9 +17,12 @@
 package com.android.server.display;
 
 
+import static com.android.server.display.config.SensorData.SupportedMode;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -526,6 +529,26 @@
     }
 
     @Test
+    public void testProximitySensorWithRefreshRatesFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile(
+                getContent(getValidLuxThrottling(), getValidProxSensorWithRefreshRateAndVsyncRate(),
+                        /* includeIdleMode= */ true));
+        assertEquals("test_proximity_sensor",
+                mDisplayDeviceConfig.getProximitySensor().type);
+        assertEquals("Test Proximity Sensor",
+                mDisplayDeviceConfig.getProximitySensor().name);
+        assertEquals(mDisplayDeviceConfig.getProximitySensor().minRefreshRate, 60, SMALL_DELTA);
+        assertEquals(mDisplayDeviceConfig.getProximitySensor().maxRefreshRate, 90, SMALL_DELTA);
+        assertThat(mDisplayDeviceConfig.getProximitySensor().supportedModes).hasSize(2);
+        SupportedMode mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0);
+        assertEquals(mode.refreshRate, 60, SMALL_DELTA);
+        assertEquals(mode.vsyncRate, 65, SMALL_DELTA);
+        mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(1);
+        assertEquals(mode.refreshRate, 120, SMALL_DELTA);
+        assertEquals(mode.vsyncRate, 125, SMALL_DELTA);
+    }
+
+    @Test
     public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException {
         setupDisplayDeviceConfigFromDisplayConfigFile();
 
@@ -821,6 +844,27 @@
                 + "</proxSensor>\n";
     }
 
+    private String getValidProxSensorWithRefreshRateAndVsyncRate() {
+        return "<proxSensor>\n"
+                +   "<type>test_proximity_sensor</type>\n"
+                +   "<name>Test Proximity Sensor</name>\n"
+                +   "<refreshRate>\n"
+                +       "<minimum>60</minimum>\n"
+                +       "<maximum>90</maximum>\n"
+                +   "</refreshRate>\n"
+                +   "<supportedModes>\n"
+                +       "<point>\n"
+                +           "<first>60</first>\n"   // refreshRate
+                +           "<second>65</second>\n" //vsyncRate
+                +       "</point>\n"
+                +       "<point>\n"
+                +           "<first>120</first>\n"   // refreshRate
+                +           "<second>125</second>\n" //vsyncRate
+                +       "</point>\n"
+                +   "</supportedModes>"
+                + "</proxSensor>\n";
+    }
+
     private String getProxSensorWithEmptyValues() {
         return "<proxSensor>\n"
                 +   "<type></type>\n"
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 9684f42..3775ac94 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -123,6 +123,7 @@
 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.display.DisplayManagerService.DeviceStateListener;
 import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.input.InputManagerInternal;
@@ -2317,11 +2318,8 @@
         String testSensorType = "testType";
         Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
 
-        DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData();
-        sensorData.type = testSensorType;
-        sensorData.name = testSensorName;
-        sensorData.minRefreshRate = 10f;
-        sensorData.maxRefreshRate = 100f;
+        SensorData sensorData = new SensorData(testSensorType, testSensorName,
+                /* minRefreshRate= */ 10f, /* maxRefreshRate= */ 100f);
 
         when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(sensorData);
         when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList(
@@ -2352,12 +2350,6 @@
         String testSensorType = "testType";
         Sensor testSensor = TestUtils.createSensor(testSensorType, testSensorName);
 
-        DisplayDeviceConfig.SensorData sensorData = new DisplayDeviceConfig.SensorData();
-        sensorData.type = testSensorType;
-        sensorData.name = testSensorName;
-        sensorData.minRefreshRate = 10f;
-        sensorData.maxRefreshRate = 100f;
-
         when(mMockDisplayDeviceConfig.getProximitySensor()).thenReturn(null);
         when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(Collections.singletonList(
                 testSensor));
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 47521d1..57f392a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -77,6 +77,7 @@
 import com.android.server.display.brightness.clamper.BrightnessClamperController;
 import com.android.server.display.brightness.clamper.HdrClamper;
 import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.feature.flags.Flags;
 import com.android.server.display.layout.Layout;
@@ -1618,23 +1619,13 @@
         when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
         when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
         when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
+                new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
         when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
         when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
         when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData());
+                new SensorData());
         when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_LIGHT;
-                        name = null;
-                    }
-                });
+                new SensorData(Sensor.STRING_TYPE_LIGHT, null));
         when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
                 .thenReturn(new int[0]);
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 37ee23f..9617bd0 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -76,6 +76,7 @@
 import com.android.server.display.RampAnimator.DualRampAnimator;
 import com.android.server.display.brightness.BrightnessEvent;
 import com.android.server.display.color.ColorDisplayService;
+import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.feature.flags.Flags;
 import com.android.server.display.layout.Layout;
@@ -1515,23 +1516,13 @@
         when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId);
         when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock);
         when(displayDeviceConfigMock.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
+                new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
         when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500});
         when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true);
         when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData());
+                new SensorData());
         when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_LIGHT;
-                        name = null;
-                    }
-                });
+                new SensorData(Sensor.STRING_TYPE_LIGHT, null));
         when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux())
                 .thenReturn(new int[0]);
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
index 534a708..ebd6614 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerProximityStateControllerTest.java
@@ -37,6 +37,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.server.display.config.SensorData;
 import com.android.server.testutils.OffsettableClock;
 
 import org.junit.Before;
@@ -74,14 +75,7 @@
         mClock = new OffsettableClock.Stopped();
         mTestLooper = new TestLooper(mClock::now);
         when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        // This is kept null because currently there is no way to define a sensor
-                        // name in TestUtils
-                        name = null;
-                    }
-                });
+                new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
         setUpProxSensor();
         DisplayPowerProximityStateController.Injector injector =
                 new DisplayPowerProximityStateController.Injector() {
@@ -171,13 +165,7 @@
 
     @Test
     public void isProximitySensorAvailableReturnsFalseWhenNotAvailableAndNoDefault() {
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = null;
-                        name = null;
-                    }
-                });
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
         mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
                 mWakelockController, mDisplayDeviceConfig, mTestLooper.getLooper(),
                 mNudgeUpdatePowerState, Display.DEFAULT_DISPLAY,
@@ -188,13 +176,7 @@
     @Test
     public void isProximitySensorAvailableReturnsTrueWhenNotAvailableAndHasDefault()
             throws Exception {
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = null;
-                        name = null;
-                    }
-                });
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
         when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
                 TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
         mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
@@ -207,13 +189,7 @@
     @Test
     public void isProximitySensorAvailableReturnsFalseWhenNotAvailableHasDefaultNonDefaultDisplay()
             throws Exception {
-        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = null;
-                        name = null;
-                    }
-                });
+        when(mDisplayDeviceConfig.getProximitySensor()).thenReturn(new SensorData());
         when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(
                 TestUtils.createSensor(Sensor.TYPE_PROXIMITY, "proximity"));
         mDisplayPowerProximityStateController = new DisplayPowerProximityStateController(
@@ -240,12 +216,7 @@
     public void notifyDisplayDeviceChangedReloadsTheProximitySensor() throws Exception {
         DisplayDeviceConfig updatedDisplayDeviceConfig = mock(DisplayDeviceConfig.class);
         when(updatedDisplayDeviceConfig.getProximitySensor()).thenReturn(
-                new DisplayDeviceConfig.SensorData() {
-                    {
-                        type = Sensor.STRING_TYPE_PROXIMITY;
-                        name = null;
-                    }
-                });
+                new SensorData(Sensor.STRING_TYPE_PROXIMITY, null));
         Sensor newProxSensor = TestUtils.createSensor(
                 Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, 4.0f);
         when(mSensorManager.getSensorList(eq(Sensor.TYPE_ALL)))
diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
index 4494b0c..6e2d954 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java
@@ -28,7 +28,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.annotations.Keep;
-import com.android.server.display.DisplayDeviceConfig.SensorData;
+import com.android.server.display.config.SensorData;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -123,9 +123,7 @@
         when(mSensorManager.getSensorList(Sensor.TYPE_ALL)).thenReturn(allSensors);
         when(mSensorManager.getDefaultSensor(fallbackType)).thenReturn(defaultSensor);
 
-        SensorData sensorData = new SensorData();
-        sensorData.name = sensorName;
-        sensorData.type = sensorType;
+        SensorData sensorData = new SensorData(sensorType, sensorName);
 
         Sensor result = SensorUtils.findSensor(mSensorManager, sensorData, fallbackType);
 
diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
index ef19ba1..e89199d 100644
--- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml
+++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml
@@ -39,6 +39,7 @@
             crossProfileIntentResolutionStrategy='0'
             mediaSharedWithParent='true'
             credentialShareableWithParent='false'
+            authAlwaysRequiredToDisableQuietMode='true'
             showInSettings='23'
             hideInSettingsInQuietMode='true'
             inheritDevicePolicy='450'
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index c684a7b..57b1225 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -67,6 +67,7 @@
                 .setCrossProfileIntentResolutionStrategy(0)
                 .setMediaSharedWithParent(false)
                 .setCredentialShareableWithParent(true)
+                .setAuthAlwaysRequiredToDisableQuietMode(false)
                 .setDeleteAppWithParent(false)
                 .setAlwaysVisible(false)
                 .build();
@@ -80,6 +81,7 @@
         actualProps.setCrossProfileIntentResolutionStrategy(1);
         actualProps.setMediaSharedWithParent(true);
         actualProps.setCredentialShareableWithParent(false);
+        actualProps.setAuthAlwaysRequiredToDisableQuietMode(true);
         actualProps.setDeleteAppWithParent(true);
         actualProps.setAlwaysVisible(true);
 
@@ -123,6 +125,7 @@
                 .setInheritDevicePolicy(1732)
                 .setMediaSharedWithParent(true)
                 .setDeleteAppWithParent(true)
+                .setAuthAlwaysRequiredToDisableQuietMode(false)
                 .setAlwaysVisible(true)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
@@ -131,6 +134,7 @@
         orig.setShowInSettings(1437);
         orig.setInheritDevicePolicy(9456);
         orig.setDeleteAppWithParent(false);
+        orig.setAuthAlwaysRequiredToDisableQuietMode(true);
         orig.setAlwaysVisible(false);
 
         // Test every permission level. (Currently, it's linear so it's easy.)
@@ -182,6 +186,8 @@
                 hasManagePermission);
         assertEqualGetterOrThrows(orig::getUseParentsContacts,
                 copy::getUseParentsContacts, hasManagePermission);
+        assertEqualGetterOrThrows(orig::isAuthAlwaysRequiredToDisableQuietMode,
+                copy::isAuthAlwaysRequiredToDisableQuietMode, hasManagePermission);
 
         // Items requiring hasQueryPermission - put them here using hasQueryPermission.
 
@@ -242,6 +248,8 @@
                 .isEqualTo(actual.isMediaSharedWithParent());
         assertThat(expected.isCredentialShareableWithParent())
                 .isEqualTo(actual.isCredentialShareableWithParent());
+        assertThat(expected.isAuthAlwaysRequiredToDisableQuietMode())
+                .isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode());
         assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent());
         assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible());
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
index 20270a8..48eb5c6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java
@@ -89,6 +89,7 @@
                 .setCrossProfileIntentResolutionStrategy(1)
                 .setMediaSharedWithParent(true)
                 .setCredentialShareableWithParent(false)
+                .setAuthAlwaysRequiredToDisableQuietMode(true)
                 .setShowInSettings(900)
                 .setHideInSettingsInQuietMode(true)
                 .setInheritDevicePolicy(340)
@@ -160,6 +161,8 @@
                 .getCrossProfileIntentResolutionStrategy());
         assertTrue(type.getDefaultUserPropertiesReference().isMediaSharedWithParent());
         assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent());
+        assertTrue(type.getDefaultUserPropertiesReference()
+                .isAuthAlwaysRequiredToDisableQuietMode());
         assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings());
         assertTrue(type.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(340, type.getDefaultUserPropertiesReference()
@@ -306,6 +309,7 @@
                 .setCrossProfileIntentResolutionStrategy(1)
                 .setMediaSharedWithParent(false)
                 .setCredentialShareableWithParent(true)
+                .setAuthAlwaysRequiredToDisableQuietMode(false)
                 .setShowInSettings(20)
                 .setHideInSettingsInQuietMode(false)
                 .setInheritDevicePolicy(21)
@@ -347,6 +351,8 @@
         assertFalse(aospType.getDefaultUserPropertiesReference().isMediaSharedWithParent());
         assertTrue(aospType.getDefaultUserPropertiesReference()
                 .isCredentialShareableWithParent());
+        assertFalse(aospType.getDefaultUserPropertiesReference()
+                .isAuthAlwaysRequiredToDisableQuietMode());
         assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings());
         assertFalse(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(21, aospType.getDefaultUserPropertiesReference()
@@ -394,6 +400,8 @@
         assertTrue(aospType.getDefaultUserPropertiesReference().isMediaSharedWithParent());
         assertFalse(aospType.getDefaultUserPropertiesReference()
                 .isCredentialShareableWithParent());
+        assertTrue(aospType.getDefaultUserPropertiesReference()
+                .isAuthAlwaysRequiredToDisableQuietMode());
         assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings());
         assertTrue(aospType.getDefaultUserPropertiesReference().getHideInSettingsInQuietMode());
         assertEquals(450, aospType.getDefaultUserPropertiesReference()
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 775d42a..2b6d8ed 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -331,6 +331,9 @@
                 .isEqualTo(privateProfileUserProperties.isMediaSharedWithParent());
         assertThat(typeProps.isCredentialShareableWithParent())
                 .isEqualTo(privateProfileUserProperties.isCredentialShareableWithParent());
+        assertThat(typeProps.isAuthAlwaysRequiredToDisableQuietMode())
+                .isEqualTo(privateProfileUserProperties
+                        .isAuthAlwaysRequiredToDisableQuietMode());
         assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent);
 
         // Verify private profile parent
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3803244..7fb8b30 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3239,7 +3239,7 @@
         mService.setPreferencesHelper(mPreferencesHelper);
         when(mPreferencesHelper.getNotificationChannel(
                 anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
-                        new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
+                    new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
 
         Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
         mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
@@ -9927,6 +9927,174 @@
     }
 
     @Test
+    public void testRestoreConversationChannel_deleted() throws Exception {
+        // Create parent channel
+        when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+        final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+                IMPORTANCE_DEFAULT);
+        NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+                NotificationChannel.CREATOR);
+        assertEquals(originalChannel, parentChannel);
+        mBinderService.createNotificationChannels(PKG,
+                new ParceledListSlice(Arrays.asList(parentChannel)));
+
+        //Create deleted conversation channel
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID);
+        final NotificationChannel conversationChannel =
+                mBinderService.getConversationNotificationChannel(
+                PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID);
+        conversationChannel.setDeleted(true);
+
+        //Create notification record
+        Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+                null /* groupKey */, false /* isSummary */);
+        nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
+        nb.setChannelId(originalChannel.getId());
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+        assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        // Verify that the channel was changed to the conversation channel and restored
+        assertThat(mService.getNotificationRecord(nr.getKey()).isConversation()).isTrue();
+        assertThat(mService.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo(
+                conversationChannel);
+        assertThat(mService.getNotificationRecord(nr.getKey()).getChannel().isDeleted()).isFalse();
+        assertThat(mService.getNotificationRecord(nr.getKey()).getChannel().getDeletedTimeMs())
+                .isEqualTo(-1);
+    }
+
+    @Test
+    public void testDoNotRestoreParentChannel_deleted() throws Exception {
+        // Create parent channel and set as deleted
+        when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+        final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+                IMPORTANCE_DEFAULT);
+        NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+                NotificationChannel.CREATOR);
+        assertEquals(originalChannel, parentChannel);
+        mBinderService.createNotificationChannels(PKG,
+                new ParceledListSlice(Arrays.asList(parentChannel)));
+        parentChannel.setDeleted(true);
+
+        //Create notification record
+        Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+                null /* groupKey */, false /* isSummary */);
+        nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
+        nb.setChannelId(originalChannel.getId());
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+        assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+        when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        // Verify that the channel was not restored and the notification was not posted
+        assertThat(mService.mChannelToastsSent).contains(mUid);
+        assertThat(mService.getNotificationRecord(nr.getKey())).isNull();
+        assertThat(parentChannel.isDeleted()).isTrue();
+    }
+
+    @Test
+    public void testEnqueueToConversationChannel_notDeleted_doesNotRestore() throws Exception {
+        TestableNotificationManagerService service = spy(mService);
+        PreferencesHelper preferencesHelper = spy(mService.mPreferencesHelper);
+        service.setPreferencesHelper(preferencesHelper);
+        // Create parent channel
+        when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+        final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+                IMPORTANCE_DEFAULT);
+        NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+                NotificationChannel.CREATOR);
+        assertEquals(originalChannel, parentChannel);
+        mBinderService.createNotificationChannels(PKG,
+                new ParceledListSlice(Arrays.asList(parentChannel)));
+
+        //Create conversation channel
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID);
+        final NotificationChannel conversationChannel =
+                mBinderService.getConversationNotificationChannel(
+                    PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID);
+
+        //Create notification record
+        Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+                null /* groupKey */, false /* isSummary */);
+        nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
+        nb.setChannelId(originalChannel.getId());
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+        assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        // Verify that the channel was changed to the conversation channel and not restored
+        assertThat(service.getNotificationRecord(nr.getKey()).isConversation()).isTrue();
+        assertThat(service.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo(
+                conversationChannel);
+        verify(service, never()).handleSavePolicyFile();
+        verify(preferencesHelper, never()).createNotificationChannel(anyString(),
+                anyInt(), any(), anyBoolean(), anyBoolean(), anyInt(), anyBoolean());
+    }
+
+    @Test
+    public void testEnqueueToParentChannel_notDeleted_doesNotRestore() throws Exception {
+        TestableNotificationManagerService service = spy(mService);
+        PreferencesHelper preferencesHelper = spy(mService.mPreferencesHelper);
+        service.setPreferencesHelper(preferencesHelper);
+        // Create parent channel
+        when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+        final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+                IMPORTANCE_DEFAULT);
+        NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+                NotificationChannel.CREATOR);
+        assertEquals(originalChannel, parentChannel);
+        mBinderService.createNotificationChannels(PKG,
+                new ParceledListSlice(Arrays.asList(parentChannel)));
+
+        //Create deleted conversation channel
+        mBinderService.createConversationNotificationChannelForPackage(
+                PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID);
+        final NotificationChannel conversationChannel =
+                mBinderService.getConversationNotificationChannel(
+                    PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID);
+
+        //Create notification record without a shortcutId
+        Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+                null /* groupKey */, false /* isSummary */);
+        nb.setShortcutId(null);
+        nb.setChannelId(originalChannel.getId());
+        StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+                "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+        NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+        assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+        waitForIdle();
+
+        // Verify that the channel is the parent channel and no channel was restored
+        //assertThat(service.getNotificationRecord(nr.getKey()).isConversation()).isFalse();
+        assertThat(service.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo(
+                parentChannel);
+        verify(service, never()).handleSavePolicyFile();
+        verify(preferencesHelper, never()).createNotificationChannel(anyString(),
+                anyInt(), any(), anyBoolean(), anyBoolean(), anyInt(), anyBoolean());
+    }
+
+    @Test
     public void testGetConversationsForPackage_hasShortcut() throws Exception {
         mService.setPreferencesHelper(mPreferencesHelper);
         ArrayList<ConversationChannelWrapper> convos = new ArrayList<>();
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
index fd4ec8b..5949bca 100644
--- a/tools/hoststubgen/hoststubgen/Android.bp
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -284,6 +284,9 @@
         "hoststubgen-helper-runtime.ravenwood",
         "framework-minus-apex.ravenwood",
     ],
+    static_libs: [
+        "core-xml-for-device",
+    ],
 }
 
 // Defaults for host side test modules.