Merge "Clean up activityWindowInfoFlag (1/n)" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 889b627..9ad8dd3 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12047,6 +12047,7 @@
     field public static final int COLOR_MODE_DEFAULT = 0; // 0x0
     field public static final int COLOR_MODE_HDR = 2; // 0x2
     field public static final int COLOR_MODE_WIDE_COLOR_GAMUT = 1; // 0x1
+    field @FlaggedApi("android.content.res.handle_all_config_changes") public static final int CONFIG_ASSETS_PATHS = -2147483648; // 0x80000000
     field public static final int CONFIG_COLOR_MODE = 16384; // 0x4000
     field public static final int CONFIG_DENSITY = 4096; // 0x1000
     field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
@@ -12060,6 +12061,7 @@
     field public static final int CONFIG_MNC = 2; // 0x2
     field public static final int CONFIG_NAVIGATION = 64; // 0x40
     field public static final int CONFIG_ORIENTATION = 128; // 0x80
+    field @FlaggedApi("android.content.res.handle_all_config_changes") public static final int CONFIG_RESOURCES_UNUSED = 134217728; // 0x8000000
     field public static final int CONFIG_SCREEN_LAYOUT = 256; // 0x100
     field public static final int CONFIG_SCREEN_SIZE = 1024; // 0x400
     field public static final int CONFIG_SMALLEST_SCREEN_SIZE = 2048; // 0x800
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c223ed4..36b1eab 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -30,6 +30,7 @@
 import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
 import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
 import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.content.pm.ActivityInfo.CONFIG_RESOURCES_UNUSED;
 import static android.content.res.Configuration.UI_MODE_TYPE_DESK;
 import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
 import static android.view.Display.DEFAULT_DISPLAY;
@@ -6519,6 +6520,13 @@
             return true;
         }
 
+        if (android.content.res.Flags.handleAllConfigChanges()) {
+            if ((handledConfigChanges & CONFIG_RESOURCES_UNUSED) != 0) {
+                // Report the change if activities claim they do not use resources at all.
+                return true;
+            }
+        }
+
         final int diffWithBucket = SizeConfigurationBuckets.filterDiff(publicDiff, currentConfig,
                 newConfig, sizeBuckets);
         // Compare to the diff which filter the change without crossing size buckets with
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 57ffed4..6952a09 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -941,6 +941,8 @@
             CONFIG_COLOR_MODE,
             CONFIG_FONT_SCALE,
             CONFIG_GRAMMATICAL_GENDER,
+            CONFIG_ASSETS_PATHS,
+            CONFIG_RESOURCES_UNUSED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Config {}
@@ -1060,8 +1062,8 @@
      * can itself handle asset path changes.  Set from the {@link android.R.attr#configChanges}
      * attribute. This is not a core resource configuration, but a higher-level value, so its
      * constant starts at the high bits.
-     * @hide We do not want apps handling this yet, but we do need some kind of bit for diffs.
      */
+    @FlaggedApi(android.content.res.Flags.FLAG_HANDLE_ALL_CONFIG_CHANGES)
     public static final int CONFIG_ASSETS_PATHS = 0x80000000;
     /**
      * Bit in {@link #configChanges} that indicates that the activity
@@ -1088,6 +1090,30 @@
      */
     public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 0x10000000;
 
+    /**
+     * <p>This is probably not the constant you want, the resources compiler supports a less
+     * dangerous version of it, 'allKnown', that only suppresses all currently existing
+     * configuration change restarts depending on your target SDK rather than whatever the latest
+     * SDK supports, allowing the application to work with resources on future Platform versions.
+     *
+     * <p>Bit in {@link #configChanges} that indicates that the activity doesn't use Android
+     * Resources at all and doesn't need to be restarted on any configuration changes. This bit
+     * disables all restarts for configuration dimensions available in the current target SDK as
+     * well as dimensions introduced in future SDKs. Use it only if the activity doesn't need
+     * anything from its resources, and doesn't depend on any libraries that may provide resources
+     * and need to respond to configuration changes. When set,
+     * {@link Activity#onConfigurationChanged(Configuration)} will be called instead of a restart,
+     * and it’s up to the implementation to ensure that no stale resource values remain loaded
+     * anywhere in the code.
+     *
+     * <p>This overrides all other bits, and this is recommended to be used individually.
+     *
+     * <p>This is not a core resource configuration, but a higher-level value, so its constant
+     * starts at the high bits.
+     */
+    @FlaggedApi(android.content.res.Flags.FLAG_HANDLE_ALL_CONFIG_CHANGES)
+    public static final int CONFIG_RESOURCES_UNUSED = 0x8000000;
+
     /** @hide
      * Unfortunately the constants for config changes in native code are
      * different from ActivityInfo. :(  Here are the values we should use for the
@@ -1657,7 +1683,8 @@
      * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
      * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT},
      * {@link #CONFIG_DENSITY}, {@link #CONFIG_LAYOUT_DIRECTION},
-     * {@link #CONFIG_COLOR_MODE}, and {link #CONFIG_GRAMMATICAL_GENDER}.
+     * {@link #CONFIG_COLOR_MODE}, {@link #CONFIG_GRAMMATICAL_GENDER},
+     * {@link #CONFIG_ASSETS_PATHS}, and {@link #CONFIG_RESOURCES_UNUSED}.
      * Set from the {@link android.R.attr#configChanges} attribute.
      */
     public int configChanges;
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index a475cc8..a5f8199 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -56,3 +56,13 @@
     # This flag is read in ResourcesImpl at boot time.
     is_fixed_read_only: true
 }
+
+flag {
+    name: "handle_all_config_changes"
+    is_exported: true
+    namespace: "resource_manager"
+    description: "Feature flag for allowing activities to handle all kinds of configuration changes"
+    bug: "180625460"
+    # This flag is read at boot time.
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 4894fb1..0321e1df 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -395,7 +395,7 @@
      * the display is removed.
      *
      * Public virtual displays without this flag will move their content to main display
-     * stack once they're removed. Private vistual displays will always destroy their
+     * stack once they're removed. Private virtual displays will always destroy their
      * content on removal even without this flag.
      *
      * @see #createVirtualDisplay
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 91caedc..811a834 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -686,8 +686,12 @@
         public RefreshRateRange range;
 
         public RefreshRateLimitation(@RefreshRateLimitType int type, float min, float max) {
+            this(type, new RefreshRateRange(min, max));
+        }
+
+        public RefreshRateLimitation(@RefreshRateLimitType int type, RefreshRateRange range) {
             this.type = type;
-            range = new RefreshRateRange(min, max);
+            this.range = range;
         }
 
         @Override
diff --git a/core/java/android/inputmethodservice/ImsConfigurationTracker.java b/core/java/android/inputmethodservice/ImsConfigurationTracker.java
index 30ef0a2..17a5ffc 100644
--- a/core/java/android/inputmethodservice/ImsConfigurationTracker.java
+++ b/core/java/android/inputmethodservice/ImsConfigurationTracker.java
@@ -16,6 +16,8 @@
 
 package android.inputmethodservice;
 
+import static android.content.pm.ActivityInfo.CONFIG_RESOURCES_UNUSED;
+
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -88,12 +90,16 @@
         if (!mInitialized) {
             return;
         }
+        // Don't restart InputMethodService that claims it does not use resources at all.
+        boolean neverReset = android.content.res.Flags.handleAllConfigChanges()
+                && (mHandledConfigChanges & CONFIG_RESOURCES_UNUSED) != 0;
+
         final int diff = mLastKnownConfig != null
                 ? mLastKnownConfig.diffPublicOnly(newConfig) : CONFIG_CHANGED;
         // If the new config is the same as the config this Service is already running with,
         // then don't bother calling resetStateForNewConfiguration.
         final int unhandledDiff = (diff & ~mHandledConfigChanges);
-        if (unhandledDiff != 0) {
+        if (unhandledDiff != 0 && !neverReset) {
             resetStateForNewConfigurationRunner.run();
         }
         if (diff != 0) {
diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig
index be60c25..04dba46 100644
--- a/core/java/android/tracing/flags.aconfig
+++ b/core/java/android/tracing/flags.aconfig
@@ -46,3 +46,11 @@
     is_fixed_read_only: true
     bug: "323165543"
 }
+
+flag {
+    name: "client_side_proto_logging"
+    namespace: "windowing_tools"
+    description: "Add support for client side protologging"
+    is_fixed_read_only: true
+    bug: "352538294"
+}
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index f732929..1362f7b 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -168,4 +168,11 @@
     namespace: "lse_desktop_experience"
     description: "Whether to enable back navigation treatment in desktop windowing."
     bug: "350421096"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "enable_desktop_windowing_app_handle_education"
+    namespace: "lse_desktop_experience"
+    description: "Enables desktop windowing app handle education"
+    bug: "348208342"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 8fd525c..5397e91 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -208,7 +208,7 @@
 
 flag {
   name: "enforce_shell_thread_model"
-  namespace: "windowing_frentend"
+  namespace: "windowing_frontend"
   description: "Crash the shell process if someone calls in from the wrong thread"
   bug: "351189446"
   is_fixed_read_only: true
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f94c8ab..2e3dbda 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1052,6 +1052,19 @@
         <!-- The font weight adjustment value has changed. Used to reflect the user increasing font
              weight. -->
         <flag name="fontWeightAdjustment" value="0x10000000" />
+        <!-- The assets paths have changed. For example a runtime overlay is installed and enabled.
+             Corresponds to {@link android.content.pm.ActivityInfo#CONFIG_ASSETS_PATHS}. -->
+        <flag name="assetsPaths" value="0x80000000" />
+        <!-- This is probably not the flag you want, the resources compiler supports a less
+             dangerous version of it, 'allKnown', that only suppresses all currently existing
+             configuration change restarts depending on your target SDK rather than whatever the
+             latest SDK supports, allowing the application to work with resources on future Platform
+             versions.
+             Activity doesn't use Android Resources at all and doesn't need to be restarted on any
+             configuration changes. This overrides all other flags, and this is recommended to be
+             used individually. Corresponds to
+             {@link android.content.pm.ActivityInfo#CONFIG_RESOURCES_UNUSED}. -->
+        <flag name="resourcesUnused" value="0x8000000" />
     </attr>
 
     <!-- Indicate that the activity can be launched as the embedded child of another
diff --git a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
index 064439e..6998c32 100644
--- a/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
+++ b/core/tests/coretests/src/android/inputmethodservice/ImsConfigurationTrackerTest.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
+import android.platform.test.annotations.RequiresFlagsEnabled;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -74,4 +75,33 @@
         assertFalse("IME shouldn't restart since it handles configChanges",
                 didReset.get());
     }
+
+    @Test
+    @RequiresFlagsEnabled(android.content.res.Flags.FLAG_HANDLE_ALL_CONFIG_CHANGES)
+    public void testShouldImeRestart_handleResourceUnused() throws Exception {
+        Configuration config = mContext.getResources().getConfiguration();
+        mImsConfigTracker.onInitialize(0 /* handledConfigChanges */);
+        mImsConfigTracker.onBindInput(mContext.getResources());
+        Configuration newConfig = new Configuration(config);
+
+        final AtomicBoolean didReset = new AtomicBoolean();
+        Runnable resetStateRunner = () -> didReset.set(true);
+
+        mImsConfigTracker.onConfigurationChanged(newConfig, resetStateRunner);
+        assertFalse("IME shouldn't restart if config hasn't changed",
+                didReset.get());
+
+        // Screen density changed but IME doesn't handle configChanges
+        newConfig.densityDpi = 99;
+        mImsConfigTracker.onConfigurationChanged(newConfig, resetStateRunner);
+        assertTrue("IME should restart for unhandled configChanges",
+                didReset.get());
+
+        didReset.set(false);
+        // opt-in IME to handle all configuration changes.
+        mImsConfigTracker.setHandledConfigChanges(ActivityInfo.CONFIG_RESOURCES_UNUSED);
+        mImsConfigTracker.onConfigurationChanged(newConfig, resetStateRunner);
+        assertFalse("IME shouldn't restart since it handles configChanges",
+                didReset.get());
+    }
 }
diff --git a/core/tests/overlaytests/handle_config_change/Android.bp b/core/tests/overlaytests/handle_config_change/Android.bp
new file mode 100644
index 0000000..2b31d0a
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+    default_team: "trendy_team_android_resources",
+}
+
+java_test_host {
+    name: "HandleConfigChangeHostTests",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    static_libs: [
+        "compatibility-host-util-axt",
+        "flag-junit-host",
+        "android.content.res.flags-aconfig-java-host",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+    // All APKs required by the tests
+    data: [
+        ":OverlayResApp",
+    ],
+    per_testcase_directory: true,
+}
diff --git a/core/tests/overlaytests/handle_config_change/AndroidTest.xml b/core/tests/overlaytests/handle_config_change/AndroidTest.xml
new file mode 100644
index 0000000..05ea036
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<configuration description="Config for the handle config change test cases">
+    <option name="test-tag" value="HandleConfigChangeHostTests" />
+    <option name="test-suite-tag" value="apct" />
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" />
+        <option name="set-global-setting" key="verifier_engprod" value="1" />
+        <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
+        <option name="restore-settings" value="true" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="OverlayResApp.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest">
+        <option name="class" value="com.android.overlaytest.HandleConfigChangeHostTests" />
+    </test>
+</configuration>
diff --git a/core/tests/overlaytests/handle_config_change/src/com/android/overlaytest/HandleConfigChangeHostTests.java b/core/tests/overlaytests/handle_config_change/src/com/android/overlaytest/HandleConfigChangeHostTests.java
new file mode 100644
index 0000000..e91716a
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/src/com/android/overlaytest/HandleConfigChangeHostTests.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest;
+
+import android.content.res.Flags;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.host.HostFlagsValueProvider;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class HandleConfigChangeHostTests extends BaseHostJUnit4Test {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            HostFlagsValueProvider.createCheckFlagsRule(this::getDevice);
+    private static final String DEVICE_TEST_PKG1 = "com.android.overlaytest.overlayresapp";
+    private static final String DEVICE_TEST_CLASS = "OverlayResTest";
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_HANDLE_ALL_CONFIG_CHANGES)
+    public void testOverlayRes() throws Exception {
+        runDeviceTests(DEVICE_TEST_PKG1, DEVICE_TEST_PKG1 + "." + DEVICE_TEST_CLASS,
+                "overlayRes_onConfigurationChanged");
+    }
+}
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp
new file mode 100644
index 0000000..e0f1012
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "OverlayResApp",
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+    platform_apis: true,
+    certificate: "platform",
+
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.rules",
+        "androidx.test.core",
+        "compatibility-device-util-axt",
+        "truth",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    test_suites: [
+        "device-tests",
+    ],
+
+    manifest: "AndroidManifest.xml",
+}
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/AndroidManifest.xml b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/AndroidManifest.xml
new file mode 100644
index 0000000..617e879
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.overlaytest.overlayresapp"
+    xmlns:tools="http://schemas.android.com/tools">
+
+  <application>
+    <uses-library android:name="android.test.runner" />
+    <activity
+        android:name=".OverlayResActivity"
+        android:exported="false"
+        android:configChanges="assetsPaths">
+    </activity>
+  </application>
+
+  <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                   android:targetPackage="com.android.overlaytest.overlayresapp" />
+
+</manifest>
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/integers.xml b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/integers.xml
new file mode 100644
index 0000000..493333f
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/integers.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <integer name="test_integer">0</integer>
+</resources>
\ No newline at end of file
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/strings.xml b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/strings.xml
new file mode 100644
index 0000000..72820d3
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+  <string name="app_name">My Application</string>
+  <string name="test_string">Test String</string>
+</resources>
\ No newline at end of file
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResActivity.java b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResActivity.java
new file mode 100644
index 0000000..96143ae
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.overlayresapp;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * A test activity to verify that the assets paths configuration changes are received if the
+ * overlay targeting state is changed.
+ */
+public class OverlayResActivity extends Activity {
+    private Runnable mConfigurationChangedCallback;
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        final Runnable callback = mConfigurationChangedCallback;
+        if (callback != null) {
+            callback.run();
+        }
+    }
+
+    /** Registers the callback of onConfigurationChanged. */
+    public void setConfigurationChangedCallback(Runnable callback) {
+        mConfigurationChangedCallback = callback;
+    }
+}
diff --git a/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResTest.java b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResTest.java
new file mode 100644
index 0000000..1c37719
--- /dev/null
+++ b/core/tests/overlaytests/handle_config_change/test-apps/OverlayResApp/src/com/android/overlaytest/overlayresapp/OverlayResTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest.overlayresapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.om.FabricatedOverlay;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.util.TypedValue;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class OverlayResTest {
+    // Default timeout value
+    private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+    private static final String TEST_OVERLAY_NAME = "Test";
+    private static final String TEST_RESOURCE_INTEGER = "integer/test_integer";
+    private static final String TEST_RESOURCE_STRING = "string/test_string";
+    private static final int TEST_INTEGER = 0;
+    private static final int TEST_FRRO_INTEGER = 1;
+    private static final String TEST_STRING = "Test String";
+    private static final String TEST_FRRO_STRING = "FRRO Test String";
+    private OverlayResActivity mActivity;
+    private Context mContext;
+    private OverlayManager mOverlayManager;
+    private int mUserId;
+    private UserHandle mUserHandle;
+
+    @Rule
+    public ActivityScenarioRule<OverlayResActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(OverlayResActivity.class);
+
+    @Before
+    public void setUp() {
+        mActivityScenarioRule.getScenario().onActivity(activity -> {
+            assertThat(activity).isNotNull();
+            mActivity = activity;
+        });
+        mContext = mActivity.getApplicationContext();
+        mOverlayManager = mContext.getSystemService(OverlayManager.class);
+        mUserId = UserHandle.myUserId();
+        mUserHandle = UserHandle.of(mUserId);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        final OverlayManagerTransaction.Builder cleanUp = new OverlayManagerTransaction.Builder();
+        mOverlayManager.getOverlayInfosForTarget(mContext.getPackageName(), mUserHandle).forEach(
+                info -> {
+                    if (info.isFabricated()) {
+                        cleanUp.unregisterFabricatedOverlay(info.getOverlayIdentifier());
+                    }
+                });
+        mOverlayManager.commit(cleanUp.build());
+
+    }
+
+    @Test
+    public void overlayRes_onConfigurationChanged() throws Exception {
+        final CountDownLatch latch1 = new CountDownLatch(1);
+        mActivity.setConfigurationChangedCallback(() -> {
+            Resources r = mActivity.getApplicationContext().getResources();
+            assertThat(r.getInteger(R.integer.test_integer)).isEqualTo(TEST_FRRO_INTEGER);
+            assertThat(r.getString(R.string.test_string)).isEqualTo(TEST_FRRO_STRING);
+            latch1.countDown();
+        });
+
+        // Create and enable FRRO
+        final FabricatedOverlay overlay = new FabricatedOverlay.Builder(
+                mContext.getPackageName(), TEST_OVERLAY_NAME, mContext.getPackageName())
+                .setResourceValue(TEST_RESOURCE_INTEGER, TypedValue.TYPE_INT_DEC, TEST_FRRO_INTEGER)
+                .setResourceValue(TEST_RESOURCE_STRING, TypedValue.TYPE_STRING, TEST_FRRO_STRING)
+                .build();
+
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .registerFabricatedOverlay(overlay)
+                .build());
+
+        OverlayInfo info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+        assertNotNull(info);
+        assertThat(info.isEnabled()).isFalse();
+
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .setEnabled(overlay.getIdentifier(), true, mUserId)
+                .build());
+
+        info = mOverlayManager.getOverlayInfo(overlay.getIdentifier(), mUserHandle);
+        assertNotNull(info);
+        assertThat(info.isEnabled()).isTrue();
+
+        if (!latch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Fail to wait configuration changes for build and enabling frro, "
+                    + "onConfigurationChanged() has not been invoked.");
+        }
+
+        final CountDownLatch latch2 = new CountDownLatch(1);
+        mActivity.setConfigurationChangedCallback(() -> {
+            Resources r = mActivity.getApplicationContext().getResources();
+            assertThat(r.getInteger(R.integer.test_integer)).isEqualTo(TEST_INTEGER);
+            assertThat(r.getString(R.string.test_string)).isEqualTo(TEST_STRING);
+            latch2.countDown();
+        });
+
+        // unregister FRRO
+        mOverlayManager.commit(new OverlayManagerTransaction.Builder()
+                .unregisterFabricatedOverlay(overlay.getIdentifier())
+                .build());
+
+        if (!latch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Fail to wait configuration changes after unregister frro,"
+                    + " onConfigurationChanged() has not been invoked.");
+        }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
index 14af508..2965793 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugActivity.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spa.debug
 
-import android.net.Uri
 import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
@@ -41,8 +40,6 @@
 import com.android.settingslib.spa.framework.util.SESSION_BROWSE
 import com.android.settingslib.spa.framework.util.SESSION_SEARCH
 import com.android.settingslib.spa.framework.util.createIntent
-import com.android.settingslib.spa.slice.fromEntry
-import com.android.settingslib.spa.slice.presenter.SliceDemo
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.HomeScaffold
@@ -52,7 +49,6 @@
 private const val ROUTE_ROOT = "root"
 private const val ROUTE_All_PAGES = "pages"
 private const val ROUTE_All_ENTRIES = "entries"
-private const val ROUTE_All_SLICES = "slices"
 private const val ROUTE_PAGE = "page"
 private const val ROUTE_ENTRY = "entry"
 private const val PARAM_NAME_PAGE_ID = "pid"
@@ -87,7 +83,6 @@
                 composable(route = ROUTE_ROOT) { RootPage() }
                 composable(route = ROUTE_All_PAGES) { AllPages() }
                 composable(route = ROUTE_All_ENTRIES) { AllEntries() }
-                composable(route = ROUTE_All_SLICES) { AllSlices() }
                 composable(
                     route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}",
                     arguments = listOf(
@@ -109,8 +104,6 @@
         val entryRepository by spaEnvironment.entryRepository
         val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() }
         val allEntry = remember { entryRepository.getAllEntries() }
-        val allSliceEntry =
-            remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
         HomeScaffold(title = "Settings Debug") {
             Preference(object : PreferenceModel {
                 override val title = "List All Pages (${allPageWithEntry.size})"
@@ -120,10 +113,6 @@
                 override val title = "List All Entries (${allEntry.size})"
                 override val onClick = navigator(route = ROUTE_All_ENTRIES)
             })
-            Preference(object : PreferenceModel {
-                override val title = "List All Slices (${allSliceEntry.size})"
-                override val onClick = navigator(route = ROUTE_All_SLICES)
-            })
         }
     }
 
@@ -152,18 +141,6 @@
         }
     }
 
-    @Composable
-    fun AllSlices() {
-        val entryRepository by spaEnvironment.entryRepository
-        val authority = spaEnvironment.sliceProviderAuthorities
-        val allSliceEntry =
-            remember { entryRepository.getAllEntries().filter { it.hasSliceSupport } }
-        RegularScaffold(title = "All Slices (${allSliceEntry.size})") {
-            for (entry in allSliceEntry) {
-                SliceDemo(sliceUri = Uri.Builder().fromEntry(entry, authority).build())
-            }
-        }
-    }
 
     @Composable
     fun OnePage(arguments: Bundle?) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
index 444a3f0..06d105b 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
@@ -70,7 +70,6 @@
         "allowSearch = $isAllowSearch",
         "isSearchDynamic = $isSearchDataDynamic",
         "isSearchMutable = $hasMutableStatus",
-        "hasSlice = $hasSliceSupport",
         "------ SEARCH ------",
         "search_path = $entryPathWithTitle",
         searchData?.debugContent() ?: "no search data",
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
index 780933d3c..e5bbb8f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
@@ -50,7 +50,6 @@
     INTENT_TARGET_PACKAGE("intentTargetPackage"),
     INTENT_TARGET_CLASS("intentTargetClass"),
     INTENT_EXTRAS("intentExtras"),
-    SLICE_URI("sliceUri"),
     ENTRY_DISABLED("entryDisabled"),
 }
 
@@ -71,7 +70,6 @@
             ColumnEnum.INTENT_TARGET_PACKAGE,
             ColumnEnum.INTENT_TARGET_CLASS,
             ColumnEnum.INTENT_EXTRAS,
-            ColumnEnum.SLICE_URI,
         )
     ),
     SEARCH_DYNAMIC_DATA_QUERY(
@@ -85,7 +83,6 @@
             ColumnEnum.INTENT_TARGET_PACKAGE,
             ColumnEnum.INTENT_TARGET_CLASS,
             ColumnEnum.INTENT_EXTRAS,
-            ColumnEnum.SLICE_URI,
         )
     ),
     SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
@@ -115,7 +112,6 @@
             ColumnEnum.INTENT_TARGET_PACKAGE,
             ColumnEnum.INTENT_TARGET_CLASS,
             ColumnEnum.INTENT_EXTRAS,
-            ColumnEnum.SLICE_URI,
             ColumnEnum.ENTRY_DISABLED,
         )
     ),
@@ -130,7 +126,6 @@
             ColumnEnum.INTENT_TARGET_PACKAGE,
             ColumnEnum.INTENT_TARGET_CLASS,
             ColumnEnum.INTENT_EXTRAS,
-            ColumnEnum.SLICE_URI,
             ColumnEnum.ENTRY_DISABLED,
         )
     ),
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index eacb28c..65f700c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -32,8 +32,6 @@
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.util.SESSION_SEARCH
 import com.android.settingslib.spa.framework.util.createIntent
-import com.android.settingslib.spa.slice.fromEntry
-
 
 private const val TAG = "SpaSearchProvider"
 
@@ -217,11 +215,6 @@
                 .add(ColumnEnum.INTENT_TARGET_CLASS.id, spaEnvironment.browseActivityClass?.name)
                 .add(ColumnEnum.INTENT_EXTRAS.id, marshall(intent.extras))
         }
-        if (entry.hasSliceSupport)
-            row.add(
-                ColumnEnum.SLICE_URI.id, Uri.Builder()
-                    .fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
-            )
     }
 
     private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
@@ -252,11 +245,6 @@
                 .add(ColumnEnum.INTENT_TARGET_CLASS.id, spaEnvironment.browseActivityClass?.name)
                 .add(ColumnEnum.INTENT_EXTRAS.id, marshall(intent.extras))
         }
-        if (entry.hasSliceSupport)
-            row.add(
-                ColumnEnum.SLICE_URI.id, Uri.Builder()
-                    .fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
-            )
         // Fetch status data. We can add runtime arguments later if necessary
         val statusData = entry.getStatusData() ?: return
         row.add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
new file mode 100644
index 0000000..d7b7cfe
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractorTest.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+
+import android.app.Flags
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+import android.provider.Settings.Global.ZEN_MODE_OFF
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.notification.data.repository.FakeZenModeRepository
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class ModesTileDataInteractorTest : SysuiTestCase() {
+    private val zenModeRepository = FakeZenModeRepository()
+
+    private val underTest = ModesTileDataInteractor(zenModeRepository)
+
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    @Test
+    fun availableWhenFlagIsOn() = runTest {
+        val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+        assertThat(availability).containsExactly(true)
+    }
+
+    @DisableFlags(Flags.FLAG_MODES_UI)
+    @Test
+    fun unavailableWhenFlagIsOff() = runTest {
+        val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+        assertThat(availability).containsExactly(false)
+    }
+
+    @EnableFlags(Flags.FLAG_MODES_UI)
+    @Test
+    fun dataMatchesTheRepository() = runTest {
+        val dataList: List<ModesTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+        runCurrent()
+
+        // Enable zen mode
+        zenModeRepository.updateZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS)
+        runCurrent()
+
+        // Change zen mode: it's still enabled, so this shouldn't cause another emission
+        zenModeRepository.updateZenMode(ZEN_MODE_NO_INTERRUPTIONS)
+        runCurrent()
+
+        // Disable zen mode
+        zenModeRepository.updateZenMode(ZEN_MODE_OFF)
+        runCurrent()
+
+        assertThat(dataList.map { it.isActivated }).containsExactly(false, true, false)
+    }
+
+    private companion object {
+
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/res/layout/window_magnification_settings_view.xml b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
index 12e226a..afd4fa7 100644
--- a/packages/SystemUI/res/layout/window_magnification_settings_view.xml
+++ b/packages/SystemUI/res/layout/window_magnification_settings_view.xml
@@ -33,9 +33,7 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:singleLine="true"
-            android:scrollHorizontally="true"
-            android:ellipsize="marquee"
+            android:layout_marginEnd="@dimen/magnification_setting_view_item_horizontal_spacing"
             android:text="@string/accessibility_magnifier_size"
             android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
             android:focusable="true"
@@ -120,9 +118,7 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
-            android:singleLine="true"
-            android:scrollHorizontally="true"
-            android:ellipsize="marquee"
+            android:layout_marginEnd="@dimen/magnification_setting_view_item_horizontal_spacing"
             android:text="@string/accessibility_allow_diagonal_scrolling"
             android:textAppearance="@style/TextAppearance.MagnificationSetting.Title"
             android:labelFor="@id/magnifier_horizontal_lock_switch"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 80b9ec7..7f7e634 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -104,7 +104,7 @@
 
     <!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
     <string name="quick_settings_tiles_stock" translatable="false">
-        internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices
+        internet,bt,flashlight,dnd,modes,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream,font_scaling,record_issue,hearing_devices
     </string>
 
     <!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 40bdc3e..eda7bb0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1323,6 +1323,7 @@
     <dimen name="magnifier_drag_handle_padding">3dp</dimen>
     <!-- Magnification settings panel -->
     <dimen name="magnification_setting_view_margin">24dp</dimen>
+    <dimen name="magnification_setting_view_item_horizontal_spacing">12dp</dimen>
     <dimen name="magnification_setting_text_size">18sp</dimen>
     <dimen name="magnification_setting_background_padding">24dp</dimen>
     <dimen name="magnification_setting_background_corner_radius">28dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 9f1f1a0..52e5dea 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -716,6 +716,8 @@
     <!-- QuickSettings: Do not disturb - Priority only [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Do not disturb - Alarms only [CHAR LIMIT=NONE] -->
     <!-- QuickSettings: Do not disturb - Total silence [CHAR LIMIT=NONE] -->
+    <!-- QuickSettings: Priority modes [CHAR LIMIT=NONE] -->
+    <string name="quick_settings_modes_label">Priority modes</string>
     <!-- QuickSettings: Bluetooth [CHAR LIMIT=NONE] -->
     <string name="quick_settings_bluetooth_label">Bluetooth</string>
     <!-- QuickSettings: Bluetooth (Multiple) [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7475eb2..36912ac 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1604,7 +1604,6 @@
         <item name="android:fontFamily">google-sans</item>
         <item name="android:textColor">?androidprv:attr/textColorPrimary</item>
         <item name="android:textSize">@dimen/magnification_setting_text_size</item>
-        <item name="android:singleLine">true</item>
     </style>
 
     <style name="TextAppearance.MagnificationSetting.EditButton">
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index ad09b46..c702927 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -85,6 +85,16 @@
         <item>On</item>
     </string-array>
 
+    <!-- State names for modes (Priority modes) tile: unavailable, off, on.
+         This subtitle is shown when the tile is in that particular state but does not set its own
+         subtitle, so some of these may never appear on screen. They should still be translated as
+         if they could appear. [CHAR LIMIT=32] -->
+    <string-array name="tile_states_modes">
+        <item>Unavailable</item>
+        <item>Off</item>
+        <item>On</item>
+    </string-array>
+
     <!-- State names for flashlight tile: unavailable, off, on.
          This subtitle is shown when the tile is in that particular state but does not set its own
          subtitle, so some of these may never appear on screen. They should still be translated as
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index f2a68a8..5f6f21a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -55,7 +55,6 @@
 import android.widget.LinearLayout;
 import android.widget.SeekBar;
 import android.widget.Switch;
-import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -90,7 +89,6 @@
 
     private SeekBarWithIconButtonsView mZoomSeekbar;
     private LinearLayout mAllowDiagonalScrollingView;
-    private TextView mAllowDiagonalScrollingTitle;
     private Switch mAllowDiagonalScrollingSwitch;
     private LinearLayout mPanelView;
     private LinearLayout mSettingView;
@@ -98,7 +96,6 @@
     private ImageButton mMediumButton;
     private ImageButton mLargeButton;
     private Button mDoneButton;
-    private TextView mSizeTitle;
     private Button mEditButton;
     private ImageButton mFullScreenButton;
     private int mLastSelectedButtonIndex = MagnificationSize.NONE;
@@ -522,11 +519,8 @@
         mMediumButton = mSettingView.findViewById(R.id.magnifier_medium_button);
         mLargeButton = mSettingView.findViewById(R.id.magnifier_large_button);
         mDoneButton = mSettingView.findViewById(R.id.magnifier_done_button);
-        mSizeTitle = mSettingView.findViewById(R.id.magnifier_size_title);
         mEditButton = mSettingView.findViewById(R.id.magnifier_edit_button);
         mFullScreenButton = mSettingView.findViewById(R.id.magnifier_full_button);
-        mAllowDiagonalScrollingTitle =
-                mSettingView.findViewById(R.id.magnifier_horizontal_lock_title);
 
         mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
         mZoomSeekbar.setMax((int) (mZoomSeekbar.getChangeMagnitude()
@@ -550,8 +544,6 @@
         mDoneButton.setOnClickListener(mButtonClickListener);
         mFullScreenButton.setOnClickListener(mButtonClickListener);
         mEditButton.setOnClickListener(mButtonClickListener);
-        mSizeTitle.setSelected(true);
-        mAllowDiagonalScrollingTitle.setSelected(true);
 
         mSettingView.setOnApplyWindowInsetsListener((v, insets) -> {
             // Adds a pending post check to avoiding redundant calculation because this callback
@@ -578,6 +570,7 @@
             // CONFIG_FONT_SCALE: font size change
             // CONFIG_LOCALE: language change
             // CONFIG_DENSITY: display size change
+            mParams.width = getPanelWidth(mContext);
             mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
 
             boolean showSettingPanelAfterConfigChange = mIsVisible;
@@ -660,9 +653,22 @@
         mCallback.onEditMagnifierSizeMode(enable);
     }
 
-    private static LayoutParams createLayoutParams(Context context) {
+    private int getPanelWidth(Context context) {
+        // The magnification settings panel width is limited to the minimum of
+        //     1. display width
+        //     2. panel done button width + left and right padding
+        // So we can directly calculate the proper panel width at runtime
+        int displayWidth = mWindowManager.getCurrentWindowMetrics().getBounds().width();
+        int contentWidth = context.getResources()
+                .getDimensionPixelSize(R.dimen.magnification_setting_button_done_width);
+        int padding = context.getResources()
+                .getDimensionPixelSize(R.dimen.magnification_setting_background_padding);
+        return Math.min(displayWidth, contentWidth + 2 * padding);
+    }
+
+    private LayoutParams createLayoutParams(Context context) {
         final LayoutParams params = new LayoutParams(
-                LayoutParams.WRAP_CONTENT,
+                getPanelWidth(context),
                 LayoutParams.WRAP_CONTENT,
                 LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                 LayoutParams.FLAG_NOT_FOCUSABLE,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
index 961d6aa..f041f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java
@@ -186,7 +186,7 @@
     }
 
     @Override
-    public void onDeviceItemGearClicked(@NonNull  DeviceItem deviceItem, @NonNull View view) {
+    public void onDeviceItemGearClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
         dismissDialogIfExists();
         Intent intent = new Intent(ACTION_BLUETOOTH_DEVICE_DETAILS);
         Bundle bundle = new Bundle();
@@ -198,7 +198,7 @@
     }
 
     @Override
-    public void onDeviceItemOnClicked(@NonNull  DeviceItem deviceItem, @NonNull View view) {
+    public void onDeviceItemOnClicked(@NonNull DeviceItem deviceItem, @NonNull View view) {
         CachedBluetoothDevice cachedBluetoothDevice = deviceItem.getCachedBluetoothDevice();
         switch (deviceItem.getType()) {
             case ACTIVE_MEDIA_BLUETOOTH_DEVICE, CONNECTED_BLUETOOTH_DEVICE ->
@@ -226,8 +226,8 @@
             final int activePresetIndex = mPresetsController.getActivePresetIndex();
             refreshPresetInfoAdapter(presetInfos, activePresetIndex);
             mPresetSpinner.setVisibility(
-                    (activeHearingDevice != null && !mPresetInfoAdapter.isEmpty()) ? VISIBLE
-                            : GONE);
+                    (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
+                            && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
         });
     }
 
@@ -303,6 +303,11 @@
         mLocalBluetoothManager.getEventManager().unregisterCallback(this);
     }
 
+    @VisibleForTesting
+    void setHearingDevicesPresetsController(HearingDevicesPresetsController controller) {
+        mPresetsController = controller;
+    }
+
     private void setupDeviceListView(SystemUIDialog dialog) {
         mDeviceList.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
         mHearingDeviceItemList = getHearingDevicesList();
@@ -311,12 +316,15 @@
     }
 
     private void setupPresetSpinner(SystemUIDialog dialog) {
-        mPresetsController = new HearingDevicesPresetsController(mProfileManager, mPresetCallback);
+        if (mPresetsController == null) {
+            mPresetsController = new HearingDevicesPresetsController(mProfileManager,
+                    mPresetCallback);
+        }
         final CachedBluetoothDevice activeHearingDevice = getActiveHearingDevice(
                 mHearingDeviceItemList);
         mPresetsController.setActiveHearingDevice(activeHearingDevice);
 
-        mPresetInfoAdapter = new ArrayAdapter<String>(dialog.getContext(),
+        mPresetInfoAdapter = new ArrayAdapter<>(dialog.getContext(),
                 R.layout.hearing_devices_preset_spinner_selected,
                 R.id.hearing_devices_preset_option_text);
         mPresetInfoAdapter.setDropDownViewResource(
@@ -350,7 +358,8 @@
         final int activePresetIndex = mPresetsController.getActivePresetIndex();
         refreshPresetInfoAdapter(presetInfos, activePresetIndex);
         mPresetSpinner.setVisibility(
-                (activeHearingDevice != null && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
+                (activeHearingDevice != null && activeHearingDevice.isConnectedHapClientDevice()
+                        && !mPresetInfoAdapter.isEmpty()) ? VISIBLE : GONE);
     }
 
     private void setupPairNewDeviceButton(SystemUIDialog dialog, @Visibility int visibility) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
index f34389e..53594bb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/SubtitleArrayMapping.kt
@@ -20,12 +20,14 @@
 /** Return the subtitle resource Id of the given tile. */
 object SubtitleArrayMapping {
     private val subtitleIdsMap: HashMap<String, Int> = HashMap()
+
     init {
         subtitleIdsMap["internet"] = R.array.tile_states_internet
         subtitleIdsMap["wifi"] = R.array.tile_states_wifi
         subtitleIdsMap["cell"] = R.array.tile_states_cell
         subtitleIdsMap["battery"] = R.array.tile_states_battery
         subtitleIdsMap["dnd"] = R.array.tile_states_dnd
+        subtitleIdsMap["modes"] = R.array.tile_states_modes
         subtitleIdsMap["flashlight"] = R.array.tile_states_flashlight
         subtitleIdsMap["rotation"] = R.array.tile_states_rotation
         subtitleIdsMap["bt"] = R.array.tile_states_bt
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
new file mode 100644
index 0000000..da4d2f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+
+import android.app.Flags
+import android.os.UserHandle
+import android.provider.Settings.Global.ZEN_MODE_OFF
+import com.android.settingslib.notification.data.repository.ZenModeRepository
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+class ModesTileDataInteractor @Inject constructor(val zenModeRepository: ZenModeRepository) :
+    QSTileDataInteractor<ModesTileModel> {
+    // TODO(b/346519570): This should be checking for any enabled modes.
+    private val zenModeActive =
+        zenModeRepository.globalZenMode.map { it != ZEN_MODE_OFF }.distinctUntilChanged()
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<ModesTileModel> {
+        return zenModeActive.map { ModesTileModel(isActivated = it) }
+    }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(Flags.modesUi())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
new file mode 100644
index 0000000..e2fea84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileUserActionInteractor.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.interactor
+
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+class ModesTileUserActionInteractor @Inject constructor() :
+    QSTileUserActionInteractor<ModesTileModel> {
+    override suspend fun handleInput(input: QSTileInput<ModesTileModel>) {
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    // TODO(b/346519570) open dialog
+                }
+                is QSTileUserAction.LongClick -> {
+                    // TODO(b/346519570) open settings
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
new file mode 100644
index 0000000..e44413a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/model/ModesTileModel.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.domain.model
+data class ModesTileModel(val isActivated: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
new file mode 100644
index 0000000..07b393e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.modes.ui
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+class ModesTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ModesTileModel> {
+    override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            val iconRes =
+                if (data.isActivated) {
+                    R.drawable.qs_dnd_icon_on
+                } else {
+                    R.drawable.qs_dnd_icon_off
+                }
+            val icon = Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null)
+            this.icon = { icon }
+            if (data.isActivated) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                secondaryLabel = "Some modes enabled idk" // TODO(b/346519570)
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel = "Off" // TODO(b/346519570)
+            }
+            contentDescription = label
+            supportedActions =
+                setOf(
+                    QSTileState.UserAction.CLICK,
+                    QSTileState.UserAction.LONG_CLICK,
+                )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index f98a88f..e48c28d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -698,7 +698,7 @@
     }
 
     public void setHeadsUpIsVisible() {
-        if (row != null) row.setHeadsUpIsVisible();
+        if (row != null) row.markHeadsUpSeen();
     }
 
     //TODO: i'm imagining a world where this isn't just the row, but I could be rwong
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 582d847..1cbb16e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -859,8 +859,8 @@
     }
 
     @Override
-    public void setHeadsUpIsVisible() {
-        super.setHeadsUpIsVisible();
+    public void markHeadsUpSeen() {
+        super.markHeadsUpSeen();
         mMustStayOnScreen = false;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 2af119f..6becbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -637,7 +637,10 @@
         return false;
     }
 
-    public void setHeadsUpIsVisible() {
+    /**
+     * Called, when the notification has been seen by the user in the heads up state.
+     */
+    public void markHeadsUpSeen() {
     }
 
     public boolean showingPulsing() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
index d1e5ab0..83de226 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ExpandableViewState.java
@@ -176,7 +176,7 @@
             expandableView.setInShelf(inShelf);
 
             if (headsUpIsVisible) {
-                expandableView.setHeadsUpIsVisible();
+                expandableView.markHeadsUpSeen();
             }
         }
     }
@@ -231,7 +231,7 @@
         expandableView.setInShelf(this.inShelf);
 
         if (headsUpIsVisible) {
-            expandableView.setHeadsUpIsVisible();
+            expandableView.markHeadsUpSeen();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
index 52cb48b..8d73983 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ui/viewbinder/HeadsUpNotificationViewBinder.kt
@@ -51,8 +51,8 @@
                             }
                             removed.forEach { key ->
                                 val row = obtainView(key)
-                                parentView.generateHeadsUpAnimation(row, /* isHeadsUp = */ false)
-                                row.setHeadsUpIsVisible()
+                                parentView.generateHeadsUpAnimation(row, /* isHeadsUp= */ false)
+                                row.markHeadsUpSeen()
                             }
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
index 2ce2bc8..81e41d67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -45,6 +45,10 @@
 import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor
 import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileDataInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.interactor.ModesTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.modes.domain.model.ModesTileModel
+import com.android.systemui.qs.tiles.impl.modes.ui.ModesTileMapper
 import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor
 import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel
@@ -85,35 +89,35 @@
     @IntoMap
     @StringKey(FLASHLIGHT_TILE_SPEC)
     fun provideAirplaneModeAvailabilityInteractor(
-            impl: FlashlightTileDataInteractor
+        impl: FlashlightTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(LOCATION_TILE_SPEC)
     fun provideLocationAvailabilityInteractor(
-            impl: LocationTileDataInteractor
+        impl: LocationTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(ALARM_TILE_SPEC)
     fun provideAlarmAvailabilityInteractor(
-            impl: AlarmTileDataInteractor
+        impl: AlarmTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(UIMODENIGHT_TILE_SPEC)
     fun provideUiModeNightAvailabilityInteractor(
-            impl: UiModeNightTileDataInteractor
+        impl: UiModeNightTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     @Binds
     @IntoMap
     @StringKey(WORK_MODE_TILE_SPEC)
     fun provideWorkModeAvailabilityInteractor(
-            impl: WorkModeTileDataInteractor
+        impl: WorkModeTileDataInteractor
     ): QSTileAvailabilityInteractor
 
     companion object {
@@ -125,6 +129,7 @@
         const val CAMERA_TOGGLE_TILE_SPEC = "cameratoggle"
         const val MIC_TOGGLE_TILE_SPEC = "mictoggle"
         const val DND_TILE_SPEC = "dnd"
+        const val MODES_TILE_SPEC = "modes"
 
         /** Inject flashlight config */
         @Provides
@@ -327,7 +332,7 @@
         @IntoMap
         @StringKey(CAMERA_TOGGLE_TILE_SPEC)
         fun provideCameraToggleAvailabilityInteractor(
-                factory: SensorPrivacyToggleTileDataInteractor.Factory
+            factory: SensorPrivacyToggleTileDataInteractor.Factory
         ): QSTileAvailabilityInteractor {
             return factory.create(CAMERA)
         }
@@ -369,12 +374,11 @@
         @IntoMap
         @StringKey(MIC_TOGGLE_TILE_SPEC)
         fun provideMicToggleModeAvailabilityInteractor(
-                factory: SensorPrivacyToggleTileDataInteractor.Factory
+            factory: SensorPrivacyToggleTileDataInteractor.Factory
         ): QSTileAvailabilityInteractor {
             return factory.create(MICROPHONE)
         }
 
-
         /** Inject microphone toggle config */
         @Provides
         @IntoMap
@@ -389,6 +393,37 @@
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
             )
+
+        @Provides
+        @IntoMap
+        @StringKey(MODES_TILE_SPEC)
+        fun provideModesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(MODES_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_dnd_icon_off,
+                        labelRes = R.string.quick_settings_modes_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject ModesTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(MODES_TILE_SPEC)
+        fun provideModesTileViewModel(
+            factory: QSTileViewModelFactory.Static<ModesTileModel>,
+            mapper: ModesTileMapper,
+            stateInteractor: ModesTileDataInteractor,
+            userActionInteractor: ModesTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(MODES_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
     }
 
     /** Inject FlashlightTile into tileMap in QSModule */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index 8f7dc7cf..5ea5c21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -16,8 +16,9 @@
 
 package com.android.systemui.accessibility.hearingaid;
 
+import static android.bluetooth.BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
+
 import static com.android.systemui.accessibility.hearingaid.HearingDevicesDialogDelegate.LIVE_CAPTION_INTENT;
-import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -26,9 +27,13 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapPresetInfo;
+import android.bluetooth.BluetoothProfile;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -42,6 +47,7 @@
 import android.testing.TestableLooper;
 import android.view.View;
 import android.widget.LinearLayout;
+import android.widget.Spinner;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -86,14 +92,15 @@
     public MockitoRule mockito = MockitoJUnit.rule();
 
     private static final String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF";
+    private static final String DEVICE_NAME = "test_name";
     private static final String TEST_PKG = "pkg";
     private static final String TEST_CLS = "cls";
     private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PKG, TEST_CLS);
     private static final String TEST_LABEL = "label";
+    private static final int TEST_PRESET_INDEX = 1;
+    private static final String TEST_PRESET_NAME = "test_preset";
 
     @Mock
-    private SystemUIDialog.Factory mSystemUIDialogFactory;
-    @Mock
     private SystemUIDialogManager mSystemUIDialogManager;
     @Mock
     private SysUiState mSysUiState;
@@ -118,6 +125,8 @@
     @Mock
     private CachedBluetoothDevice mCachedDevice;
     @Mock
+    private BluetoothDevice mDevice;
+    @Mock
     private DeviceItem mHearingDeviceItem;
     @Mock
     private PackageManager mPackageManager;
@@ -125,7 +134,10 @@
     private ActivityInfo mActivityInfo;
     @Mock
     private Drawable mDrawable;
+    @Mock
+    private HearingDevicesPresetsController mPresetsController;
     private SystemUIDialog mDialog;
+    private SystemUIDialog.Factory mDialogFactory;
     private HearingDevicesDialogDelegate mDialogDelegate;
     private TestableLooper mTestableLooper;
     private final List<CachedBluetoothDevice> mDevices = new ArrayList<>();
@@ -141,23 +153,23 @@
         when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mDevices);
         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
         when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState);
+        when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice.isConnected()).thenReturn(true);
+        when(mCachedDevice.getDevice()).thenReturn(mDevice);
         when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
+        when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
+        when(mCachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
+        when(mCachedDevice.isConnectedHearingAidDevice()).thenReturn(true);
+        when(mCachedDevice.isConnectedHapClientDevice()).thenReturn(true);
         when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice);
+
         mContext.setMockPackageManager(mPackageManager);
-
-        setUpPairNewDeviceDialog();
-
-        when(mSystemUIDialogFactory.create(any(SystemUIDialog.Delegate.class)))
-                .thenReturn(mDialog);
-    }
-
-    @Test
-    public void createDialog_dialogShown() {
-        assertThat(mDialogDelegate.createDialog()).isEqualTo(mDialog);
+        mDevices.add(mCachedDevice);
     }
 
     @Test
     public void clickPairNewDeviceButton_intentActionMatch() {
+        setUpPairNewDeviceDialog();
         mDialog.show();
 
         getPairNewDeviceButton(mDialog).performClick();
@@ -185,6 +197,7 @@
 
     @Test
     public void onDeviceItemOnClicked_connectedDevice_disconnect() {
+        setUpDeviceListDialog();
         when(mHearingDeviceItem.getType()).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE);
 
         mDialogDelegate.onDeviceItemOnClicked(mHearingDeviceItem, new View(mContext));
@@ -231,50 +244,100 @@
         assertThat(relatedToolsView.getChildCount()).isEqualTo(2);
     }
 
+    @Test
+    public void showDialog_noPreset_presetGone() {
+        when(mPresetsController.getAllPresetInfo()).thenReturn(new ArrayList<>());
+        when(mPresetsController.getActivePresetIndex()).thenReturn(PRESET_INDEX_UNAVAILABLE);
+
+        setUpDeviceListDialog();
+        mDialog.show();
+
+        Spinner spinner = (Spinner) getPresetSpinner(mDialog);
+        assertThat(spinner.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
+    public void showDialog_presetExist_presetSelected() {
+        BluetoothHapPresetInfo info = getTestPresetInfo();
+        when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
+        when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+
+        setUpDeviceListDialog();
+        mDialog.show();
+
+        Spinner spinner = (Spinner) getPresetSpinner(mDialog);
+        assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
+    }
+
+    @Test
+    public void onActiveDeviceChanged_presetExist_presetSelected() {
+        setUpDeviceListDialog();
+        mDialog.show();
+        BluetoothHapPresetInfo info = getTestPresetInfo();
+        when(mPresetsController.getAllPresetInfo()).thenReturn(List.of(info));
+        when(mPresetsController.getActivePresetIndex()).thenReturn(TEST_PRESET_INDEX);
+
+        mDialogDelegate.onActiveDeviceChanged(mCachedDevice, BluetoothProfile.LE_AUDIO);
+        mTestableLooper.processAllMessages();
+
+        Spinner spinner = (Spinner) getPresetSpinner(mDialog);
+        assertThat(spinner.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(spinner.getSelectedItemPosition()).isEqualTo(0);
+    }
+
+
+
     private void setUpPairNewDeviceDialog() {
+        mDialogFactory = new SystemUIDialog.Factory(
+                mContext,
+                mSystemUIDialogManager,
+                mSysUiState,
+                getFakeBroadcastDispatcher(),
+                mDialogTransitionAnimator
+        );
         mDialogDelegate = new HearingDevicesDialogDelegate(
                 mContext,
                 true,
-                mSystemUIDialogFactory,
+                mDialogFactory,
                 mActivityStarter,
                 mDialogTransitionAnimator,
                 mLocalBluetoothManager,
                 new Handler(mTestableLooper.getLooper()),
                 mAudioManager
         );
-        mDialog = new SystemUIDialog(
-                mContext,
-                0,
-                DEFAULT_DISMISS_ON_DEVICE_LOCK,
-                mSystemUIDialogManager,
-                mSysUiState,
-                getFakeBroadcastDispatcher(),
-                mDialogTransitionAnimator,
-                mDialogDelegate
-        );
+
+        mDialog = mDialogDelegate.createDialog();
     }
 
     private void setUpDeviceListDialog() {
+        mDialogFactory = new SystemUIDialog.Factory(
+                mContext,
+                mSystemUIDialogManager,
+                mSysUiState,
+                getFakeBroadcastDispatcher(),
+                mDialogTransitionAnimator
+        );
         mDialogDelegate = new HearingDevicesDialogDelegate(
                 mContext,
                 false,
-                mSystemUIDialogFactory,
+                mDialogFactory,
                 mActivityStarter,
                 mDialogTransitionAnimator,
                 mLocalBluetoothManager,
                 new Handler(mTestableLooper.getLooper()),
                 mAudioManager
         );
-        mDialog = new SystemUIDialog(
-                mContext,
-                0,
-                DEFAULT_DISMISS_ON_DEVICE_LOCK,
-                mSystemUIDialogManager,
-                mSysUiState,
-                getFakeBroadcastDispatcher(),
-                mDialogTransitionAnimator,
-                mDialogDelegate
-        );
+
+        mDialog = mDialogDelegate.createDialog();
+        mDialogDelegate.setHearingDevicesPresetsController(mPresetsController);
+    }
+
+    private BluetoothHapPresetInfo getTestPresetInfo() {
+        BluetoothHapPresetInfo info = mock(BluetoothHapPresetInfo.class);
+        when(info.getName()).thenReturn(TEST_PRESET_NAME);
+        when(info.getIndex()).thenReturn(TEST_PRESET_INDEX);
+        return info;
     }
 
     private View getPairNewDeviceButton(SystemUIDialog dialog) {
@@ -285,6 +348,10 @@
         return dialog.requireViewById(R.id.related_tools_container);
     }
 
+    private View getPresetSpinner(SystemUIDialog dialog) {
+        return dialog.requireViewById(R.id.preset_spinner);
+    }
+
     @After
     public void reset() {
         if (mDialogDelegate != null) {
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 9b37418..515e704 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -22,6 +22,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.brightness.clamper.HdrClamper;
+import com.android.server.display.config.HighBrightnessModeData;
 import com.android.server.display.feature.DisplayManagerFlags;
 
 import java.io.PrintWriter;
@@ -157,7 +158,7 @@
     private void updateHdrClamper(DisplayDeviceInfo info, IBinder token,
             DisplayDeviceConfig displayDeviceConfig) {
         if (mUseHdrClamper) {
-            DisplayDeviceConfig.HighBrightnessModeData hbmData =
+            HighBrightnessModeData hbmData =
                     displayDeviceConfig.getHighBrightnessModeData();
             float minimumHdrPercentOfScreen =
                     hbmData == null ? -1f : hbmData.minimumHdrPercentOfScreen;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index e4db634..f5231ae 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -53,9 +53,9 @@
 import com.android.server.display.config.DisplayConfiguration;
 import com.android.server.display.config.DisplayQuirks;
 import com.android.server.display.config.EvenDimmerBrightnessData;
-import com.android.server.display.config.HbmTiming;
 import com.android.server.display.config.HdrBrightnessData;
 import com.android.server.display.config.HighBrightnessMode;
+import com.android.server.display.config.HighBrightnessModeData;
 import com.android.server.display.config.HysteresisLevels;
 import com.android.server.display.config.IdleScreenRefreshRateTimeout;
 import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
@@ -75,8 +75,6 @@
 import com.android.server.display.config.RefreshRateThrottlingMap;
 import com.android.server.display.config.RefreshRateThrottlingPoint;
 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.SensorData;
 import com.android.server.display.config.ThermalStatus;
 import com.android.server.display.config.ThermalThrottling;
@@ -302,6 +300,19 @@
  *         <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis>
  *         <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>
  *         <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>
+ *         <minimumHdrPercentOfScreenForNbm>0.2</minimumHdrPercentOfScreenForNbm>
+ *         <minimumHdrPercentOfScreenForHbm>0.5</minimumHdrPercentOfScreenForHbm>
+ *         <allowInLowPowerMode>true</allowInLowPowerMode>
+ *         <sdrHdrRatioMap>
+ *             <point>
+ *                 <first>2.0</first>
+ *                 <second>4.0</second>
+ *             </point>
+ *             <point>
+ *                 <first>100</first>
+ *                 <second>8.0</second>
+ *             </point>
+ *         </sdrHdrRatioMap>
  *      </hdrBrightnessConfig>
  *      <luxThrottling>
  *        <brightnessLimitMap>
@@ -659,9 +670,6 @@
     // Invalid value of AutoBrightness brightening and darkening light debounce
     private static final int INVALID_AUTO_BRIGHTNESS_LIGHT_DEBOUNCE = -1;
 
-    @VisibleForTesting
-    static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
-
     private final Context mContext;
 
     // The details of the ambient light sensor associated with this display.
@@ -743,13 +751,13 @@
     private Spline mNitsToBacklightSpline;
 
     private List<String> mQuirks;
-    private boolean mIsHighBrightnessModeEnabled = false;
+    @Nullable
     private HighBrightnessModeData mHbmData;
     @Nullable
     private PowerThrottlingConfigData mPowerThrottlingConfigData;
     private DensityMapping mDensityMapping;
     private String mLoadedFrom = null;
-    private Spline mSdrToHdrRatioSpline;
+
 
     // Represents the auto-brightness brightening light debounce.
     private long mAutoBrightnessBrighteningLightDebounce =
@@ -872,7 +880,7 @@
     private final DisplayManagerFlags mFlags;
 
     @VisibleForTesting
-    DisplayDeviceConfig(Context context, DisplayManagerFlags flags) {
+    public DisplayDeviceConfig(Context context, DisplayManagerFlags flags) {
         mContext = context;
         mFlags = flags;
     }
@@ -1155,7 +1163,7 @@
      * @return true if there is sdrHdrRatioMap, false otherwise.
      */
     public boolean hasSdrToHdrRatioSpline() {
-        return mSdrToHdrRatioSpline != null;
+        return mHbmData != null && mHbmData.sdrToHdrRatioSpline != null;
     }
 
     /**
@@ -1165,7 +1173,8 @@
      * @return the HDR brightness or BRIGHTNESS_INVALID when no mapping exists.
      */
     public float getHdrBrightnessFromSdr(float brightness, float maxDesiredHdrSdrRatio) {
-        if (mSdrToHdrRatioSpline == null) {
+        Spline sdrToHdrSpline = mHbmData != null ? mHbmData.sdrToHdrRatioSpline : null;
+        if (sdrToHdrSpline == null) {
             return PowerManager.BRIGHTNESS_INVALID;
         }
 
@@ -1175,7 +1184,7 @@
             return PowerManager.BRIGHTNESS_INVALID;
         }
 
-        float ratio = Math.min(mSdrToHdrRatioSpline.interpolate(nits), maxDesiredHdrSdrRatio);
+        float ratio = Math.min(sdrToHdrSpline.interpolate(nits), maxDesiredHdrSdrRatio);
         float hdrNits = nits * ratio;
         if (getNitsToBacklightSpline() == null) {
             return PowerManager.BRIGHTNESS_INVALID;
@@ -1321,13 +1330,11 @@
      * @return high brightness mode configuration data for the display.
      */
     public HighBrightnessModeData getHighBrightnessModeData() {
-        if (!mIsHighBrightnessModeEnabled || mHbmData == null) {
+        if  (mHbmData == null || !mHbmData.isHighBrightnessModeEnabled) {
             return null;
         }
 
-        HighBrightnessModeData hbmData = new HighBrightnessModeData();
-        mHbmData.copyTo(hbmData);
-        return hbmData;
+        return mHbmData;
     }
 
     /**
@@ -1604,11 +1611,10 @@
                 + ", mBacklightMaximum=" + mBacklightMaximum
                 + ", mBrightnessDefault=" + mBrightnessDefault
                 + ", mQuirks=" + mQuirks
-                + ", mIsHighBrightnessModeEnabled=" + mIsHighBrightnessModeEnabled
                 + "\n"
                 + "mLuxThrottlingData=" + mLuxThrottlingData
                 + ", mHbmData=" + mHbmData
-                + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+
                 + ", mThermalBrightnessThrottlingDataMapByThrottlingId="
                 + mThermalBrightnessThrottlingDataMapByThrottlingId
                 + "\n"
@@ -1715,7 +1721,7 @@
     }
 
     @VisibleForTesting
-    boolean initFromFile(File configFile) {
+    public boolean initFromFile(File configFile) {
         if (!configFile.exists()) {
             // Display configuration files aren't required to exist.
             return false;
@@ -1740,7 +1746,23 @@
                 loadBrightnessMap(config);
                 loadThermalThrottlingConfig(config);
                 loadPowerThrottlingConfigData(config);
-                loadHighBrightnessModeData(config);
+                // Backlight and evenDimmer data should be loaded for HbmData
+                mHbmData = HighBrightnessModeData.loadHighBrightnessModeData(config, (hbm) -> {
+                    float transitionPointBacklightScale = hbm.getTransitionPoint_all().floatValue();
+                    if (transitionPointBacklightScale >= mBacklightMaximum) {
+                        throw new IllegalArgumentException("HBM transition point invalid. "
+                                + mHbmData.transitionPoint + " is not less than "
+                                + mBacklightMaximum);
+                    }
+                    return  getBrightnessFromBacklight(transitionPointBacklightScale);
+                });
+                if (mHbmData.isHighBrightnessModeEnabled && mHbmData.refreshRateLimit != null) {
+                    // TODO(b/331650248): cleanup, DMD can use mHbmData.refreshRateLimit
+                    mRefreshRateLimitations.add(new RefreshRateLimitation(
+                            DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+                            mHbmData.refreshRateLimit));
+                }
+
                 loadLuxThrottling(config);
                 loadQuirks(config);
                 loadBrightnessRamps(config);
@@ -1938,40 +1960,6 @@
         constrainNitsAndBacklightArrays();
     }
 
-    private Spline loadSdrHdrRatioMap(HighBrightnessMode hbmConfig) {
-        final SdrHdrRatioMap sdrHdrRatioMap = hbmConfig.getSdrHdrRatioMap_all();
-
-        if (sdrHdrRatioMap == null) {
-            return null;
-        }
-
-        final List<SdrHdrRatioPoint> points = sdrHdrRatioMap.getPoint();
-        final int size = points.size();
-        if (size == 0) {
-            return null;
-        }
-
-        float[] nits = new float[size];
-        float[] ratios = new float[size];
-
-        int i = 0;
-        for (SdrHdrRatioPoint point : points) {
-            nits[i] = point.getSdrNits().floatValue();
-            if (i > 0) {
-                if (nits[i] < nits[i - 1]) {
-                    Slog.e(TAG, "sdrHdrRatioMap must be non-decreasing, ignoring rest "
-                            + " of configuration. nits: " + nits[i] + " < "
-                            + nits[i - 1]);
-                    return null;
-                }
-            }
-            ratios[i] = point.getHdrRatio().floatValue();
-            ++i;
-        }
-
-        return Spline.createSpline(nits, ratios);
-    }
-
     private void loadThermalThrottlingConfig(DisplayConfiguration config) {
         final ThermalThrottling throttlingConfig = config.getThermalThrottling();
         if (throttlingConfig == null) {
@@ -2525,49 +2513,6 @@
         }
     }
 
-    private void loadHighBrightnessModeData(DisplayConfiguration config) {
-        final HighBrightnessMode hbm = config.getHighBrightnessMode();
-        if (hbm != null) {
-            mIsHighBrightnessModeEnabled = hbm.getEnabled();
-            mHbmData = new HighBrightnessModeData();
-            mHbmData.minimumLux = hbm.getMinimumLux_all().floatValue();
-            float transitionPointBacklightScale = hbm.getTransitionPoint_all().floatValue();
-            if (transitionPointBacklightScale >= mBacklightMaximum) {
-                throw new IllegalArgumentException("HBM transition point invalid. "
-                        + mHbmData.transitionPoint + " is not less than "
-                        + mBacklightMaximum);
-            }
-            mHbmData.transitionPoint =
-                    getBrightnessFromBacklight(transitionPointBacklightScale);
-            final HbmTiming hbmTiming = hbm.getTiming_all();
-            mHbmData.timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000;
-            mHbmData.timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000;
-            mHbmData.timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000;
-            mHbmData.allowInLowPowerMode = hbm.getAllowInLowPowerMode_all();
-            final RefreshRateRange rr = hbm.getRefreshRate_all();
-            if (rr != null) {
-                final float min = rr.getMinimum().floatValue();
-                final float max = rr.getMaximum().floatValue();
-                mRefreshRateLimitations.add(new RefreshRateLimitation(
-                        DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, min, max));
-            }
-            BigDecimal minHdrPctOfScreen = hbm.getMinimumHdrPercentOfScreen_all();
-            if (minHdrPctOfScreen != null) {
-                mHbmData.minimumHdrPercentOfScreen = minHdrPctOfScreen.floatValue();
-                if (mHbmData.minimumHdrPercentOfScreen > 1
-                        || mHbmData.minimumHdrPercentOfScreen < 0) {
-                    Slog.w(TAG, "Invalid minimum HDR percent of screen: "
-                            + String.valueOf(mHbmData.minimumHdrPercentOfScreen));
-                    mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
-                }
-            } else {
-                mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
-            }
-
-            mSdrToHdrRatioSpline = loadSdrHdrRatioMap(hbm);
-        }
-    }
-
     private void loadLuxThrottling(DisplayConfiguration config) {
         LuxThrottling cfg = config.getLuxThrottling();
         if (cfg != null) {
@@ -2921,73 +2866,6 @@
     }
 
     /**
-     * Container for high brightness mode configuration data.
-     */
-    static class HighBrightnessModeData {
-        /** Minimum lux needed to enter high brightness mode */
-        public float minimumLux;
-
-        /** Brightness level at which we transition from normal to high-brightness. */
-        public float transitionPoint;
-
-        /** Whether HBM is allowed when {@code Settings.Global.LOW_POWER_MODE} is active. */
-        public boolean allowInLowPowerMode;
-
-        /** Time window for HBM. */
-        public long timeWindowMillis;
-
-        /** Maximum time HBM is allowed to be during in a {@code timeWindowMillis}. */
-        public long timeMaxMillis;
-
-        /** Minimum time that HBM can be on before being enabled. */
-        public long timeMinMillis;
-
-        /** Minimum HDR video size to enter high brightness mode */
-        public float minimumHdrPercentOfScreen;
-
-        HighBrightnessModeData() {}
-
-        HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis,
-                long timeMaxMillis, long timeMinMillis, boolean allowInLowPowerMode,
-                float minimumHdrPercentOfScreen) {
-            this.minimumLux = minimumLux;
-            this.transitionPoint = transitionPoint;
-            this.timeWindowMillis = timeWindowMillis;
-            this.timeMaxMillis = timeMaxMillis;
-            this.timeMinMillis = timeMinMillis;
-            this.allowInLowPowerMode = allowInLowPowerMode;
-            this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
-        }
-
-        /**
-         * Copies the HBM data to the specified parameter instance.
-         * @param other the instance to copy data to.
-         */
-        public void copyTo(@NonNull HighBrightnessModeData other) {
-            other.minimumLux = minimumLux;
-            other.timeWindowMillis = timeWindowMillis;
-            other.timeMaxMillis = timeMaxMillis;
-            other.timeMinMillis = timeMinMillis;
-            other.transitionPoint = transitionPoint;
-            other.allowInLowPowerMode = allowInLowPowerMode;
-            other.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
-        }
-
-        @Override
-        public String toString() {
-            return "HBM{"
-                    + "minLux: " + minimumLux
-                    + ", transition: " + transitionPoint
-                    + ", timeWindow: " + timeWindowMillis + "ms"
-                    + ", timeMax: " + timeMaxMillis + "ms"
-                    + ", timeMin: " + timeMinMillis + "ms"
-                    + ", allowInLowPowerMode: " + allowInLowPowerMode
-                    + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen
-                    + "} ";
-        }
-    }
-
-    /**
      * Container for Power throttling configuration data.
      * TODO(b/302814899): extract to separate class.
      */
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 76a561b..58309c2 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -87,6 +87,7 @@
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategyConstants;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.config.HighBrightnessModeData;
 import com.android.server.display.config.HysteresisLevels;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
@@ -2017,7 +2018,7 @@
         final DisplayDeviceConfig ddConfig = mDisplayDevice.getDisplayDeviceConfig();
         final IBinder displayToken = mDisplayDevice.getDisplayTokenLocked();
         final String displayUniqueId = mDisplayDevice.getUniqueId();
-        final DisplayDeviceConfig.HighBrightnessModeData hbmData =
+        final HighBrightnessModeData hbmData =
                 ddConfig != null ? ddConfig.getHighBrightnessModeData() : null;
         final DisplayDeviceInfo info = mDisplayDevice.getDisplayDeviceInfoLocked();
         return mInjector.getHighBrightnessModeController(mHandler, info.width, info.height,
@@ -3251,7 +3252,7 @@
 
         HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
                 int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
-                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                float brightnessMax, HighBrightnessModeData hbmData,
                 HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
                 Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
                 Context context) {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 47176fe..da9eef2 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -36,8 +36,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.display.BrightnessSynchronizer;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
 import com.android.server.display.DisplayManagerService.Clock;
+import com.android.server.display.config.HighBrightnessModeData;
 import com.android.server.display.utils.DebugUtils;
 
 import java.io.PrintWriter;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
index 902daa4..5c2db35 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java
@@ -133,7 +133,7 @@
             // new token not null and hdr min % of the screen is set, subscribe.
             // e.g. for virtual display, HBM data will be missing and HdrListener
             // should not be registered
-            if (displayToken != null && mHdrListener.mHdrMinPixels >= 0) {
+            if (displayToken != null && mHdrListener.mHdrMinPixels >= 0 && hasBrightnessLimits())  {
                 mHdrListener.register(displayToken);
                 mRegisteredDisplayToken = displayToken;
             }
@@ -179,6 +179,10 @@
         pw.println("  mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
     }
 
+    private boolean hasBrightnessLimits() {
+        return mHdrBrightnessData != null && !mHdrBrightnessData.maxBrightnessLimits.isEmpty();
+    }
+
     private void reset() {
         if (mMaxBrightness == PowerManager.BRIGHTNESS_MAX
                 && mDesiredMaxBrightness == PowerManager.BRIGHTNESS_MAX && mTransitionRate == -1f
@@ -214,11 +218,11 @@
             mDesiredMaxBrightness = expectedMaxBrightness;
             long debounceTime;
             if (mDesiredMaxBrightness > mMaxBrightness) {
-                debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis;
-                mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampIncrease;
+                debounceTime = mHdrBrightnessData.brightnessIncreaseDebounceMillis;
+                mDesiredTransitionRate = mHdrBrightnessData.screenBrightnessRampIncrease;
             } else {
-                debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
-                mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampDecrease;
+                debounceTime = mHdrBrightnessData.brightnessDecreaseDebounceMillis;
+                mDesiredTransitionRate = mHdrBrightnessData.screenBrightnessRampDecrease;
             }
 
             mHandler.removeCallbacks(mDebouncer);
@@ -232,7 +236,7 @@
         float foundAmbientBoundary = Float.MAX_VALUE;
         float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
         for (Map.Entry<Float, Float> brightnessPoint :
-                data.mMaxBrightnessLimits.entrySet()) {
+                data.maxBrightnessLimits.entrySet()) {
             float ambientBoundary = brightnessPoint.getKey();
             // find ambient lux upper boundary closest to current ambient lux
             if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) {
diff --git a/services/core/java/com/android/server/display/config/DisplayDeviceConfigUtils.java b/services/core/java/com/android/server/display/config/DisplayDeviceConfigUtils.java
new file mode 100644
index 0000000..5b4e8d5
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/DisplayDeviceConfigUtils.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.Spline;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.function.Function;
+
+public class DisplayDeviceConfigUtils {
+    private static final String TAG = "DisplayDeviceConfigUtils";
+
+    /**
+     * Create Spline from generic data
+     * @param points - points for Spline in format (x0, y0), (x1, y1) etc
+     * @param xExtractor - extract X component from generic data
+     * @param yExtractor - extract Y component from generic data
+     */
+    @Nullable
+    public static <T> Spline createSpline(List<T> points, Function<T, BigDecimal> xExtractor,
+            Function<T, BigDecimal> yExtractor) {
+        int size = points.size();
+        if (size == 0) {
+            return null;
+        }
+
+        float[] x = new float[size];
+        float[] y = new float[size];
+
+        int i = 0;
+        for (T point : points) {
+            x[i] = xExtractor.apply(point).floatValue();
+            if (i > 0) {
+                if (x[i] <= x[i - 1]) {
+                    Slog.e(TAG, "spline control points must be strictly increasing, ignoring "
+                            + "configuration. x: " + x[i] + " <= " + x[i - 1]);
+                    return null;
+                }
+            }
+            y[i] = yExtractor.apply(point).floatValue();
+            ++i;
+        }
+
+        return Spline.createSpline(x, y);
+    }
+}
diff --git a/services/core/java/com/android/server/display/config/HdrBrightnessData.java b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
index 837fbf7..c940807 100644
--- a/services/core/java/com/android/server/display/config/HdrBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/HdrBrightnessData.java
@@ -16,63 +16,138 @@
 
 package com.android.server.display.config;
 
+import static com.android.server.display.config.HighBrightnessModeData.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+
 import android.annotation.Nullable;
+import android.util.Spline;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
 
+import java.math.BigDecimal;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 /**
  * Brightness config for HDR content
+ * <pre>
+ * {@code
+ * <displayConfiguration>
+ *     ...
+ *     <hdrBrightnessConfig>
+ *         <brightnessMap>
+ *             <point>
+ *                 <first>500</first>
+ *                 <second>0.3</second>
+ *             </point>
+ *             <point>
+ *                 <first>1200</first>
+ *                 <second>0.6</second>
+ *             </point>
+ *         </brightnessMap>
+ *         <brightnessIncreaseDebounceMillis>1000</brightnessIncreaseDebounceMillis>
+ *         <brightnessIncreaseDurationMillis>10000</brightnessIncreaseDurationMillis>
+ *         <brightnessDecreaseDebounceMillis>13000</brightnessDecreaseDebounceMillis>
+ *         <brightnessDecreaseDurationMillis>10000</brightnessDecreaseDurationMillis>
+ *         <minimumHdrPercentOfScreenForNbm>0.2</minimumHdrPercentOfScreenForNbm>
+ *         <minimumHdrPercentOfScreenForHbm>0.5</minimumHdrPercentOfScreenForHbm>
+ *         <allowInLowPowerMode>true</allowInLowPowerMode>
+ *         <sdrHdrRatioMap>
+ *             <point>
+ *                 <first>2.0</first>
+ *                 <second>4.0</second>
+ *             </point>
+ *             <point>
+ *                 <first>100</first>
+ *                 <second>8.0</second>
+ *             </point>
+ *         </sdrHdrRatioMap>
+ *     </hdrBrightnessConfig>
+ *     ...
+ * </displayConfiguration>
+ * }
+ * </pre>
  */
 public class HdrBrightnessData {
+    private static final String TAG = "HdrBrightnessData";
 
     /**
      * Lux to brightness map
      */
-    public final Map<Float, Float> mMaxBrightnessLimits;
+    public final Map<Float, Float> maxBrightnessLimits;
 
     /**
      * Debounce time for brightness increase
      */
-    public final long mBrightnessIncreaseDebounceMillis;
+    public final long brightnessIncreaseDebounceMillis;
 
     /**
      * Brightness increase animation speed
      */
-    public final float mScreenBrightnessRampIncrease;
+    public final float screenBrightnessRampIncrease;
 
     /**
      * Debounce time for brightness decrease
      */
-    public final long mBrightnessDecreaseDebounceMillis;
+    public final long brightnessDecreaseDebounceMillis;
 
     /**
      * Brightness decrease animation speed
      */
-    public final float mScreenBrightnessRampDecrease;
+    public final float screenBrightnessRampDecrease;
+
+    /**
+     * Min Hdr layer size to start hdr brightness boost up to high brightness mode transition point
+     */
+    public final float minimumHdrPercentOfScreenForNbm;
+
+    /**
+     * Min Hdr layer size to start hdr brightness boost above high brightness mode transition point
+     */
+    public final float minimumHdrPercentOfScreenForHbm;
+
+    /**
+     * If Hdr brightness boost allowed in low power mode
+     */
+    public final boolean allowInLowPowerMode;
+
+    /**
+     * brightness to boost ratio spline
+     */
+    @Nullable
+    public final Spline sdrToHdrRatioSpline;
 
     @VisibleForTesting
     public HdrBrightnessData(Map<Float, Float> maxBrightnessLimits,
             long brightnessIncreaseDebounceMillis, float screenBrightnessRampIncrease,
-            long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease) {
-        mMaxBrightnessLimits = maxBrightnessLimits;
-        mBrightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
-        mScreenBrightnessRampIncrease = screenBrightnessRampIncrease;
-        mBrightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
-        mScreenBrightnessRampDecrease = screenBrightnessRampDecrease;
+            long brightnessDecreaseDebounceMillis, float screenBrightnessRampDecrease,
+            float minimumHdrPercentOfScreenForNbm, float minimumHdrPercentOfScreenForHbm,
+            boolean allowInLowPowerMode, @Nullable Spline sdrToHdrRatioSpline) {
+        this.maxBrightnessLimits = maxBrightnessLimits;
+        this.brightnessIncreaseDebounceMillis = brightnessIncreaseDebounceMillis;
+        this.screenBrightnessRampIncrease = screenBrightnessRampIncrease;
+        this.brightnessDecreaseDebounceMillis = brightnessDecreaseDebounceMillis;
+        this.screenBrightnessRampDecrease = screenBrightnessRampDecrease;
+        this.minimumHdrPercentOfScreenForNbm = minimumHdrPercentOfScreenForNbm;
+        this.minimumHdrPercentOfScreenForHbm = minimumHdrPercentOfScreenForHbm;
+        this.allowInLowPowerMode = allowInLowPowerMode;
+        this.sdrToHdrRatioSpline = sdrToHdrRatioSpline;
     }
 
     @Override
     public String toString() {
         return "HdrBrightnessData {"
-                + "mMaxBrightnessLimits: " + mMaxBrightnessLimits
-                + ", mBrightnessIncreaseDebounceMillis: " + mBrightnessIncreaseDebounceMillis
-                + ", mScreenBrightnessRampIncrease: " + mScreenBrightnessRampIncrease
-                + ", mBrightnessDecreaseDebounceMillis: " + mBrightnessDecreaseDebounceMillis
-                + ", mScreenBrightnessRampDecrease: " + mScreenBrightnessRampDecrease
+                + "mMaxBrightnessLimits: " + maxBrightnessLimits
+                + ", mBrightnessIncreaseDebounceMillis: " + brightnessIncreaseDebounceMillis
+                + ", mScreenBrightnessRampIncrease: " + screenBrightnessRampIncrease
+                + ", mBrightnessDecreaseDebounceMillis: " + brightnessDecreaseDebounceMillis
+                + ", mScreenBrightnessRampDecrease: " + screenBrightnessRampDecrease
+                + ", minimumHdrPercentOfScreenForNbm: " + minimumHdrPercentOfScreenForNbm
+                + ", minimumHdrPercentOfScreenForHbm: " + minimumHdrPercentOfScreenForHbm
+                + ", allowInLowPowerMode: " + allowInLowPowerMode
+                + ", sdrToHdrRatioSpline: " + sdrToHdrRatioSpline
                 + "} ";
     }
 
@@ -83,7 +158,7 @@
     public static HdrBrightnessData loadConfig(DisplayConfiguration config) {
         HdrBrightnessConfig hdrConfig = config.getHdrBrightnessConfig();
         if (hdrConfig == null) {
-            return null;
+            return getFallbackData(config.getHighBrightnessMode());
         }
 
         List<NonNegativeFloatToFloatPoint> points = hdrConfig.getBrightnessMap().getPoint();
@@ -92,10 +167,59 @@
             brightnessLimits.put(point.getFirst().floatValue(), point.getSecond().floatValue());
         }
 
+        float minHdrPercentForHbm = hdrConfig.getMinimumHdrPercentOfScreenForHbm() != null
+                ? hdrConfig.getMinimumHdrPercentOfScreenForHbm().floatValue()
+                : getFallbackHdrPercent(config.getHighBrightnessMode());
+
+        float minHdrPercentForNbm = hdrConfig.getMinimumHdrPercentOfScreenForNbm() != null
+                ? hdrConfig.getMinimumHdrPercentOfScreenForNbm().floatValue() : minHdrPercentForHbm;
+
         return new HdrBrightnessData(brightnessLimits,
                 hdrConfig.getBrightnessIncreaseDebounceMillis().longValue(),
                 hdrConfig.getScreenBrightnessRampIncrease().floatValue(),
                 hdrConfig.getBrightnessDecreaseDebounceMillis().longValue(),
-                hdrConfig.getScreenBrightnessRampDecrease().floatValue());
+                hdrConfig.getScreenBrightnessRampDecrease().floatValue(),
+                minHdrPercentForNbm, minHdrPercentForHbm, hdrConfig.getAllowInLowPowerMode(),
+                getSdrHdrRatioSpline(hdrConfig, config.getHighBrightnessMode()));
+    }
+
+    @Nullable
+    private static HdrBrightnessData getFallbackData(HighBrightnessMode hbm) {
+        if (hbm == null) {
+            return null;
+        }
+        float fallbackPercent = getFallbackHdrPercent(hbm);
+        Spline fallbackSpline = getFallbackSdrHdrRatioSpline(hbm);
+        return new HdrBrightnessData(Collections.emptyMap(),
+                0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET,
+                0, DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET,
+                fallbackPercent, fallbackPercent, false, fallbackSpline);
+    }
+
+    private static float getFallbackHdrPercent(HighBrightnessMode hbm) {
+        BigDecimal minHdrPctOfScreen = hbm != null ? hbm.getMinimumHdrPercentOfScreen_all() : null;
+        return minHdrPctOfScreen != null ? minHdrPctOfScreen.floatValue()
+                : HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+    }
+
+    @Nullable
+    private static Spline getSdrHdrRatioSpline(HdrBrightnessConfig hdrConfig,
+            HighBrightnessMode hbm) {
+        NonNegativeFloatToFloatMap sdrHdrRatioMap = hdrConfig.getSdrHdrRatioMap();
+        if (sdrHdrRatioMap == null) {
+            return getFallbackSdrHdrRatioSpline(hbm);
+        }
+        return DisplayDeviceConfigUtils.createSpline(sdrHdrRatioMap.getPoint(),
+                NonNegativeFloatToFloatPoint::getFirst, NonNegativeFloatToFloatPoint::getSecond);
+    }
+
+    @Nullable
+    private static Spline getFallbackSdrHdrRatioSpline(HighBrightnessMode hbm) {
+        SdrHdrRatioMap fallbackMap = hbm != null ? hbm.getSdrHdrRatioMap_all() : null;
+        if (fallbackMap == null) {
+            return null;
+        }
+        return DisplayDeviceConfigUtils.createSpline(fallbackMap.getPoint(),
+                SdrHdrRatioPoint::getSdrNits, SdrHdrRatioPoint::getHdrRatio);
     }
 }
diff --git a/services/core/java/com/android/server/display/config/HighBrightnessModeData.java b/services/core/java/com/android/server/display/config/HighBrightnessModeData.java
new file mode 100644
index 0000000..dc2f976
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/HighBrightnessModeData.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config;
+
+import android.annotation.Nullable;
+import android.util.Slog;
+import android.util.Spline;
+import android.view.SurfaceControl;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.math.BigDecimal;
+import java.util.function.Function;
+
+/**
+ * Container for high brightness mode configuration data.
+ */
+public class HighBrightnessModeData {
+    private static final String TAG = "HighBrightnessModeData";
+
+    static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
+
+    /** Minimum lux needed to enter high brightness mode */
+    public final float minimumLux;
+
+    /** Brightness level at which we transition from normal to high-brightness. */
+    public final float transitionPoint;
+
+    /** Whether HBM is allowed when {@code Settings.Global.LOW_POWER_MODE} is active. */
+    public final boolean allowInLowPowerMode;
+
+    /** Time window for HBM. */
+    public final long timeWindowMillis;
+
+    /** Maximum time HBM is allowed to be during in a {@code timeWindowMillis}. */
+    public final long timeMaxMillis;
+
+    /** Minimum time that HBM can be on before being enabled. */
+    public final long timeMinMillis;
+
+    /** Minimum HDR video size to enter high brightness mode */
+    public final float minimumHdrPercentOfScreen;
+
+    @Nullable
+    public final Spline sdrToHdrRatioSpline;
+
+    @Nullable
+    public final SurfaceControl.RefreshRateRange refreshRateLimit;
+
+    public final boolean isHighBrightnessModeEnabled;
+
+    @VisibleForTesting
+    public HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis,
+            long timeMaxMillis, long timeMinMillis, boolean allowInLowPowerMode,
+            float minimumHdrPercentOfScreen, @Nullable Spline sdrToHdrRatioSpline,
+            @Nullable SurfaceControl.RefreshRateRange refreshRateLimit,
+            boolean isHighBrightnessModeEnabled) {
+        this.minimumLux = minimumLux;
+        this.transitionPoint = transitionPoint;
+        this.timeWindowMillis = timeWindowMillis;
+        this.timeMaxMillis = timeMaxMillis;
+        this.timeMinMillis = timeMinMillis;
+        this.allowInLowPowerMode = allowInLowPowerMode;
+        this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen;
+        this.sdrToHdrRatioSpline = sdrToHdrRatioSpline;
+        this.refreshRateLimit = refreshRateLimit;
+        this.isHighBrightnessModeEnabled = isHighBrightnessModeEnabled;
+    }
+
+    @Override
+    public String toString() {
+        return "HBM{"
+                + "minLux: " + minimumLux
+                + ", transition: " + transitionPoint
+                + ", timeWindow: " + timeWindowMillis + "ms"
+                + ", timeMax: " + timeMaxMillis + "ms"
+                + ", timeMin: " + timeMinMillis + "ms"
+                + ", allowInLowPowerMode: " + allowInLowPowerMode
+                + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen
+                + ", mSdrToHdrRatioSpline=" + sdrToHdrRatioSpline
+                + ", refreshRateLimit=" + refreshRateLimit
+                + ", isHighBrightnessModeEnabled=" + isHighBrightnessModeEnabled
+                + "} ";
+    }
+
+    /**
+     * Loads HighBrightnessModeData from DisplayConfiguration
+     */
+    public static HighBrightnessModeData loadHighBrightnessModeData(DisplayConfiguration config,
+            Function<HighBrightnessMode, Float> transitionPointProvider) {
+        final HighBrightnessMode hbm = config.getHighBrightnessMode();
+        float minimumLux = 0f;
+        float transitionPoint = 0f;
+        long timeWindowMillis = 0L;
+        long timeMaxMillis = 0L;
+        long timeMinMillis = 0L;
+        boolean allowInLowPowerMode = false;
+        float minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+        Spline sdrToHdrRatioSpline = null;
+        SurfaceControl.RefreshRateRange refreshRateLimit = null;
+        boolean isEnabled = false;
+
+        if (hbm != null) {
+            minimumLux = hbm.getMinimumLux_all().floatValue();
+            transitionPoint = transitionPointProvider.apply(hbm);
+            HbmTiming hbmTiming = hbm.getTiming_all();
+            timeWindowMillis = hbmTiming.getTimeWindowSecs_all().longValue() * 1000;
+            timeMaxMillis = hbmTiming.getTimeMaxSecs_all().longValue() * 1000;
+            timeMinMillis = hbmTiming.getTimeMinSecs_all().longValue() * 1000;
+            allowInLowPowerMode = hbm.getAllowInLowPowerMode_all();
+            BigDecimal minHdrPctOfScreen = hbm.getMinimumHdrPercentOfScreen_all();
+            if (minHdrPctOfScreen != null) {
+                minimumHdrPercentOfScreen = minHdrPctOfScreen.floatValue();
+                if (minimumHdrPercentOfScreen > 1 || minimumHdrPercentOfScreen < 0) {
+                    Slog.w(TAG, "Invalid minimum HDR percent of screen: "
+                            + minimumHdrPercentOfScreen);
+                    minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
+                }
+            }
+
+            sdrToHdrRatioSpline = loadSdrHdrRatioMap(hbm);
+            RefreshRateRange rr = hbm.getRefreshRate_all();
+            if (rr != null) {
+                refreshRateLimit = new SurfaceControl.RefreshRateRange(
+                        rr.getMinimum().floatValue(), rr.getMaximum().floatValue());
+            }
+            isEnabled = hbm.getEnabled();
+        }
+        return new HighBrightnessModeData(minimumLux, transitionPoint,
+                timeWindowMillis, timeMaxMillis, timeMinMillis, allowInLowPowerMode,
+                minimumHdrPercentOfScreen, sdrToHdrRatioSpline, refreshRateLimit, isEnabled);
+
+    }
+
+    private static Spline loadSdrHdrRatioMap(HighBrightnessMode hbmConfig) {
+        final SdrHdrRatioMap sdrHdrRatioMap = hbmConfig.getSdrHdrRatioMap_all();
+        if (sdrHdrRatioMap == null) {
+            return null;
+        }
+        return DisplayDeviceConfigUtils.createSpline(sdrHdrRatioMap.getPoint(),
+                SdrHdrRatioPoint::getSdrNits, SdrHdrRatioPoint::getHdrRatio);
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index f84d55f..7daf958 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1038,13 +1038,15 @@
             // Called on ActivityManager thread.
             final int userId = user.getUserIdentifier();
             SecureSettingsWrapper.onUserStarting(userId);
-            synchronized (ImfLock.class) {
-                if (mService.mConcurrentMultiUserModeEnabled) {
-                    if (mService.mCurrentUserId != userId && mService.mSystemReady) {
-                        mService.initializeVisibleBackgroundUserLocked(userId);
+            mService.mIoHandler.post(() -> {
+                synchronized (ImfLock.class) {
+                    if (mService.mConcurrentMultiUserModeEnabled) {
+                        if (mService.mCurrentUserId != userId && mService.mSystemReady) {
+                            mService.initializeVisibleBackgroundUserLocked(userId);
+                        }
                     }
                 }
-            }
+            });
         }
 
     }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 8ee02dc4..303371b 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2629,18 +2629,28 @@
 
         String packageName = pkgLite.packageName;
         synchronized (mPm.mLock) {
-            // Package which currently owns the data that the new package will own if installed.
-            // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
-            // will be null whereas dataOwnerPkg will contain information about the package
-            // which was uninstalled while keeping its data.
-            AndroidPackage dataOwnerPkg = mPm.mPackages.get(packageName);
             PackageSetting dataOwnerPs = mPm.mSettings.getPackageLPr(packageName);
-            if (dataOwnerPkg  == null) {
-                if (dataOwnerPs != null) {
-                    dataOwnerPkg = dataOwnerPs.getPkg();
+            if (dataOwnerPs == null) {
+                if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
+                    String errorMsg = "Required installed version code was "
+                            + requiredInstalledVersionCode
+                            + " but package is not installed";
+                    Slog.w(TAG, errorMsg);
+                    return Pair.create(
+                            PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION, errorMsg);
                 }
+                // The package doesn't exist in the system, don't need to check the version
+                // replacing.
+                return Pair.create(PackageManager.INSTALL_SUCCEEDED, null);
             }
 
+            // Package which currently owns the data that the new package will own if installed.
+            // If an app is uninstalled while keeping data (e.g. adb uninstall -k), dataOwnerPkg
+            // will be null whereas dataOwnerPs will contain information about the package
+            // which was uninstalled while keeping its data. The AndroidPackage object that the
+            // PackageSetting refers to is the same object that is stored in mPackages.
+            AndroidPackage dataOwnerPkg = dataOwnerPs.getPkg();
+
             if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
                 if (dataOwnerPkg == null) {
                     String errorMsg = "Required installed version code was "
@@ -2662,7 +2672,27 @@
                 }
             }
 
-            if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
+            // If dataOwnerPkg is null but dataOwnerPs is not null, there is always data on
+            // some users. Wwe should do the downgrade check. E.g. DELETE_KEEP_DATA and
+            // archived apps
+            if (dataOwnerPkg == null) {
+                if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
+                        dataOwnerPs.isDebuggable())) {
+                    // The data exists on some users and downgrade is not permitted; a lower
+                    // version of the app will not be allowed.
+                    try {
+                        PackageManagerServiceUtils.checkDowngrade(dataOwnerPs, pkgLite);
+                    } catch (PackageManagerException e) {
+                        String errorMsg = "Downgrade detected on app uninstalled with"
+                                + " DELETE_KEEP_DATA: " + e.getMessage();
+                        Slog.w(TAG, errorMsg);
+                        return Pair.create(
+                                PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE, errorMsg);
+                    }
+                }
+                // dataOwnerPs.getPkg() is not null on system apps case. Don't need to consider
+                // system apps case like below.
+            } else if (dataOwnerPkg != null && !dataOwnerPkg.isSdkLibrary()) {
                 if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                         dataOwnerPkg.isDebuggable())) {
                     // Downgrade is not permitted; a lower version of the app will not be allowed
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 924b36c..c3cac20 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -1419,10 +1419,23 @@
 
     /**
      * Check and throw if the given before/after packages would be considered a
-     * downgrade.
+     * downgrade with {@link PackageSetting}.
      */
-    public static void checkDowngrade(AndroidPackage before, PackageInfoLite after)
-            throws PackageManagerException {
+    public static void checkDowngrade(@NonNull PackageSetting before,
+            @NonNull PackageInfoLite after) throws PackageManagerException {
+        if (after.getLongVersionCode() < before.getVersionCode()) {
+            throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
+                    "Update version code " + after.versionCode + " is older than current "
+                            + before.getVersionCode());
+        }
+    }
+
+    /**
+     * Check and throw if the given before/after packages would be considered a
+     * downgrade with {@link AndroidPackage}.
+     */
+    public static void checkDowngrade(@NonNull AndroidPackage before,
+            @NonNull PackageInfoLite after) throws PackageManagerException {
         if (after.getLongVersionCode() < before.getLongVersionCode()) {
             throw new PackageManagerException(INSTALL_FAILED_VERSION_DOWNGRADE,
                     "Update version code " + after.versionCode + " is older than current "
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 7870b17..82df527 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -97,6 +97,7 @@
                 FORCE_QUERYABLE_OVERRIDE,
                 SCANNED_AS_STOPPED_SYSTEM_APP,
                 PENDING_RESTORE,
+                DEBUGGABLE,
         })
         public @interface Flags {
         }
@@ -105,6 +106,7 @@
         private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 2;
         private static final int SCANNED_AS_STOPPED_SYSTEM_APP = 1 << 3;
         private static final int PENDING_RESTORE = 1 << 4;
+        private static final int DEBUGGABLE = 1 << 5;
     }
     private int mBooleans;
 
@@ -562,6 +564,20 @@
         return getBoolean(Booleans.PENDING_RESTORE);
     }
 
+    /**
+     * @see PackageState#isDebuggable
+     */
+    public PackageSetting setDebuggable(boolean value) {
+        setBoolean(Booleans.DEBUGGABLE, value);
+        onChanged();
+        return this;
+    }
+
+    @Override
+    public boolean isDebuggable() {
+        return getBoolean(Booleans.DEBUGGABLE);
+    }
+
     @Override
     public String toString() {
         return "PackageSetting{"
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 9ab6016..d8ce38e 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -437,6 +437,9 @@
             pkgSetting.setIsOrphaned(true);
         }
 
+        // update debuggable to packageSetting
+        pkgSetting.setDebuggable(parsedPackage.isDebuggable());
+
         // Take care of first install / last update times.
         final long scanFileTime = getLastModifiedTime(parsedPackage);
         final long existingFirstInstallTime = userId == UserHandle.USER_ALL
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 3956552..0d16b00 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -3255,6 +3255,9 @@
         if (pkg.isPendingRestore()) {
             serializer.attributeBoolean(null, "pendingRestore", true);
         }
+        if (pkg.isDebuggable()) {
+            serializer.attributeBoolean(null, "debuggable", true);
+        }
         if (pkg.isLoading()) {
             serializer.attributeBoolean(null, "isLoading", true);
         }
@@ -3269,7 +3272,6 @@
         serializer.attributeInt(null, "appMetadataSource",
                 pkg.getAppMetadataSource());
 
-
         writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(),
                 pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional());
 
@@ -4059,6 +4061,7 @@
         long versionCode = 0;
         boolean installedForceQueryable = false;
         boolean isPendingRestore = false;
+        boolean isDebuggable = false;
         float loadingProgress = 0;
         long loadingCompletedTime = 0;
         UUID domainSetId;
@@ -4085,6 +4088,7 @@
             updateAvailable = parser.getAttributeBoolean(null, "updateAvailable", false);
             installedForceQueryable = parser.getAttributeBoolean(null, "forceQueryable", false);
             isPendingRestore = parser.getAttributeBoolean(null, "pendingRestore", false);
+            isDebuggable = parser.getAttributeBoolean(null, "debuggable", false);
             loadingProgress = parser.getAttributeFloat(null, "loadingProgress", 0);
             loadingCompletedTime = parser.getAttributeLongHex(null, "loadingCompletedTime", 0);
 
@@ -4259,6 +4263,7 @@
                     .setUpdateAvailable(updateAvailable)
                     .setForceQueryableOverride(installedForceQueryable)
                     .setPendingRestore(isPendingRestore)
+                    .setDebuggable(isDebuggable)
                     .setLoadingProgress(loadingProgress)
                     .setLoadingCompletedTime(loadingCompletedTime)
                     .setAppMetadataFilePath(appMetadataFilePath)
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index e0ee199..5876188 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -274,6 +274,14 @@
     boolean isPendingRestore();
 
     /**
+     * @see ApplicationInfo#FLAG_DEBUGGABLE
+     * @see R.styleable#AndroidManifestApplication_debuggable
+     * @see AndroidPackage#isDebuggable
+     * @hide
+     */
+    boolean isDebuggable();
+
+    /**
      * Retrieves the shared user app ID. Note that the actual shared user data is not available here
      * and must be queried separately.
      *
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4f8f57a..8768074 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -64,6 +64,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
+import static android.content.pm.ActivityInfo.CONFIG_RESOURCES_UNUSED;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
@@ -9835,6 +9836,15 @@
      */
     private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) {
         int configChanged = info.getRealConfigChanged();
+        if (android.content.res.Flags.handleAllConfigChanges()) {
+            if ((configChanged & CONFIG_RESOURCES_UNUSED) != 0) {
+                // Don't relaunch any activities that claim they do not use resources at all.
+                // If they still do, the onConfigurationChanged() callback will get called to
+                // let them know anyway.
+                return false;
+            }
+        }
+
         boolean onlyVrUiModeChanged = onlyVrUiModeChanged(changes, changesConfig);
 
         // Override for apps targeting pre-O sdks
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index ed08d21..5719810 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -314,6 +314,7 @@
 
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+    void notifyConfigurationChanged(nsecs_t when) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
             const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) override;
@@ -331,7 +332,6 @@
 
     void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
                       uint32_t policyFlags) override;
-    void notifyConfigurationChanged(nsecs_t when) override;
     // ANR-related callbacks -- start
     void notifyNoFocusedWindowAnr(const std::shared_ptr<InputApplicationHandle>& handle) override;
     void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<gui::Pid> pid,
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 eeb8b9b..ec7406a 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -315,6 +315,42 @@
         <xs:element name="screenBrightnessRampDecrease" type="nonNegativeDecimal">
             <xs:annotation name="final"/>
         </xs:element>
+
+        <!-- The minimum HDR layer % at which hdr boost will be applied with transition point cap.
+             Should be lower or equal to minimumHdrPercentOfScreenForHbm. -->
+        <xs:element name="minimumHdrPercentOfScreenForNbm" type="nonNegativeDecimal"
+            minOccurs="0" maxOccurs="1">
+            <xs:annotation name="nullable"/>
+            <xs:annotation name="final"/>
+        </xs:element>
+        <!-- The minimum HDR layer size at which hdr boost will be applied. -->
+        <xs:element name="minimumHdrPercentOfScreenForHbm" type="nonNegativeDecimal"
+            minOccurs="0" maxOccurs="1">
+            <xs:annotation name="nullable"/>
+            <xs:annotation name="final"/>
+        </xs:element>
+        <!-- If hdr boost is allowed in low power mode. -->
+        <xs:element name="allowInLowPowerMode" type="xs:boolean" minOccurs="0" maxOccurs="1">
+            <xs:annotation name="nonnull"/>
+            <xs:annotation name="final"/>
+        </xs:element>
+        <!-- sdrNits, hdrRatio This LUT specifies how to boost HDR brightness at given SDR brightness (nits). -->
+        <!-- sdr brightness to hdr boost ratio map
+           Format: first = sdrNits, second = hdrRatio. E.g. :
+           <sdrHdrRatioMap>
+               <point>
+                   <first>2.0</first>   // sdrNits
+                   <second>4.0</second> // hdrRatio
+               </point>
+               ....
+           </sdrHdrRatioMap>
+        -->
+        <xs:element type="nonNegativeFloatToFloatMap" name="sdrHdrRatioMap" minOccurs="0" maxOccurs="1">
+            <xs:annotation name="nullable"/>
+            <xs:annotation name="final"/>
+        </xs:element>
+
+
     </xs:complexType>
 
     <!-- Maps to PowerManager.THERMAL_STATUS_* values. -->
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 757b23a..68d74cf 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -226,16 +226,24 @@
 
   public class HdrBrightnessConfig {
     ctor public HdrBrightnessConfig();
+    method @NonNull public final boolean getAllowInLowPowerMode();
     method public final java.math.BigInteger getBrightnessDecreaseDebounceMillis();
     method public final java.math.BigInteger getBrightnessIncreaseDebounceMillis();
     method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getBrightnessMap();
+    method @Nullable public final java.math.BigDecimal getMinimumHdrPercentOfScreenForHbm();
+    method @Nullable public final java.math.BigDecimal getMinimumHdrPercentOfScreenForNbm();
     method public final java.math.BigDecimal getScreenBrightnessRampDecrease();
     method public final java.math.BigDecimal getScreenBrightnessRampIncrease();
+    method @Nullable public final com.android.server.display.config.NonNegativeFloatToFloatMap getSdrHdrRatioMap();
+    method public final void setAllowInLowPowerMode(@NonNull boolean);
     method public final void setBrightnessDecreaseDebounceMillis(java.math.BigInteger);
     method public final void setBrightnessIncreaseDebounceMillis(java.math.BigInteger);
     method public final void setBrightnessMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
+    method public final void setMinimumHdrPercentOfScreenForHbm(@Nullable java.math.BigDecimal);
+    method public final void setMinimumHdrPercentOfScreenForNbm(@Nullable java.math.BigDecimal);
     method public final void setScreenBrightnessRampDecrease(java.math.BigDecimal);
     method public final void setScreenBrightnessRampIncrease(java.math.BigDecimal);
+    method public final void setSdrHdrRatioMap(@Nullable com.android.server.display.config.NonNegativeFloatToFloatMap);
   }
 
   public class HighBrightnessMode {
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
index 961fb9a7..bacde10 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -268,16 +268,9 @@
         // Public local InputMethodManagerService.
         LocalServices.removeServiceForTest(InputMethodManagerInternal.class);
         lifecycle.onStart();
-        try {
-            // After this boot phase, services can broadcast Intents.
-            lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
-        } catch (SecurityException e) {
-            // Security exception to permission denial is expected in test, mocking out to ensure
-            // InputMethodManagerService as system ready state.
-            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
-                throw e;
-            }
-        }
+
+        // After this boot phase, services can broadcast Intents.
+        lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
 
         // Call InputMethodManagerService#addClient() as a preparation to start interacting with it.
         mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index 89b4aea..7138306 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -1027,6 +1027,25 @@
     }
 
     @Test
+    public void testWriteReadDebuggable() {
+        Settings settings = makeSettings();
+        PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
+        packageSetting.setAppId(Process.FIRST_APPLICATION_UID);
+        packageSetting.setPkg(PackageImpl.forTesting(PACKAGE_NAME_1).hideAsParsed()
+                .setUid(packageSetting.getAppId())
+                .hideAsFinal());
+
+        packageSetting.setDebuggable(true);
+        settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
+
+        settings.writeLPr(computer, /* sync= */ true);
+        settings.mPackages.clear();
+
+        assertThat(settings.readLPw(computer, createFakeUsers()), is(true));
+        assertThat(settings.getPackageLPr(PACKAGE_NAME_1).isDebuggable(), is(true));
+    }
+
+    @Test
     public void testWriteReadArchiveState() {
         Settings settings = makeSettings();
         PackageSetting packageSetting = createPackageSetting(PACKAGE_NAME_1);
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 9a25b1a..3437923 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -54,6 +54,7 @@
 
 import com.android.internal.R;
 import com.android.server.display.config.HdrBrightnessData;
+import com.android.server.display.config.HighBrightnessModeData;
 import com.android.server.display.config.HysteresisLevels;
 import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
 import com.android.server.display.config.RefreshRateData;
@@ -436,7 +437,7 @@
     public void testHighBrightnessModeDataFromDisplayConfig() throws IOException {
         setupDisplayDeviceConfigFromDisplayConfigFile();
 
-        DisplayDeviceConfig.HighBrightnessModeData hbmData =
+        HighBrightnessModeData hbmData =
                 mDisplayDeviceConfig.getHighBrightnessModeData();
         assertNotNull(hbmData);
         assertEquals(BRIGHTNESS[1], hbmData.transitionPoint, ZERO_DELTA);
@@ -671,14 +672,14 @@
         HdrBrightnessData data = mDisplayDeviceConfig.getHdrBrightnessData();
 
         assertNotNull(data);
-        assertEquals(2, data.mMaxBrightnessLimits.size());
-        assertEquals(13000, data.mBrightnessDecreaseDebounceMillis);
-        assertEquals(0.1f, data.mScreenBrightnessRampDecrease, SMALL_DELTA);
-        assertEquals(1000, data.mBrightnessIncreaseDebounceMillis);
-        assertEquals(0.11f, data.mScreenBrightnessRampIncrease, SMALL_DELTA);
+        assertEquals(2, data.maxBrightnessLimits.size());
+        assertEquals(13000, data.brightnessDecreaseDebounceMillis);
+        assertEquals(0.1f, data.screenBrightnessRampDecrease, SMALL_DELTA);
+        assertEquals(1000, data.brightnessIncreaseDebounceMillis);
+        assertEquals(0.11f, data.screenBrightnessRampIncrease, SMALL_DELTA);
 
-        assertEquals(0.3f, data.mMaxBrightnessLimits.get(500f), SMALL_DELTA);
-        assertEquals(0.6f, data.mMaxBrightnessLimits.get(1200f), SMALL_DELTA);
+        assertEquals(0.3f, data.maxBrightnessLimits.get(500f), SMALL_DELTA);
+        assertEquals(0.6f, data.maxBrightnessLimits.get(1200f), SMALL_DELTA);
     }
 
     private void verifyConfigValuesFromConfigResource() {
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 2018e1a..5c29156 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -83,6 +83,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.HighBrightnessModeData;
 import com.android.server.display.config.HysteresisLevels;
 import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -2425,7 +2426,7 @@
         @Override
         HighBrightnessModeController getHighBrightnessModeController(Handler handler, int width,
                 int height, IBinder displayToken, String displayUniqueId, float brightnessMin,
-                float brightnessMax, DisplayDeviceConfig.HighBrightnessModeData hbmData,
+                float brightnessMax, HighBrightnessModeData hbmData,
                 HighBrightnessModeController.HdrBrightnessDeviceConfig hdrBrightnessCfg,
                 Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata,
                 Context context) {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 8e01a11..cde87b9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -23,7 +23,6 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
-import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
 import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
 
 import static org.junit.Assert.assertEquals;
@@ -56,8 +55,8 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.internal.util.test.FakeSettingsProviderRule;
-import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
 import com.android.server.display.HighBrightnessModeController.Injector;
+import com.android.server.display.config.HighBrightnessModeData;
 import com.android.server.testutils.OffsettableClock;
 
 import org.junit.Before;
@@ -77,6 +76,7 @@
     private static final long TIME_ALLOWED_IN_WINDOW_MILLIS = 12 * 1000;
     private static final long TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS = 5 * 1000;
     private static final boolean ALLOW_IN_LOW_POWER_MODE = false;
+    private static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f;
 
     private static final float DEFAULT_MIN = 0.01f;
     private static final float DEFAULT_MAX = 0.80f;
@@ -103,7 +103,8 @@
     private static final HighBrightnessModeData DEFAULT_HBM_DATA =
             new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS,
                     TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS,
-                    ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT);
+                    ALLOW_IN_LOW_POWER_MODE, HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
+                    null, null, true);
 
     @Before
     public void setUp() {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
index 7e7ccf7..7132bc1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataMapperTest.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.when;
 
+import com.android.server.display.config.HighBrightnessModeData;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -39,7 +41,7 @@
     private DisplayDeviceConfig mDdcMock;
 
     @Mock
-    private DisplayDeviceConfig.HighBrightnessModeData mHbmDataMock;
+    private HighBrightnessModeData mHbmDataMock;
 
     private HighBrightnessModeMetadataMapper mHighBrightnessModeMetadataMapper;
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
index c785ea6..7212856 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java
@@ -34,6 +34,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.display.AutomaticBrightnessController;
+import com.android.server.display.config.DisplayDeviceConfigTestUtilsKt;
 import com.android.server.display.config.HdrBrightnessData;
 import com.android.server.testutils.OffsettableClock;
 import com.android.server.testutils.TestHandler;
@@ -54,13 +55,14 @@
     private static final float FLOAT_TOLERANCE = 0.0001f;
     private static final long SEND_TIME_TOLERANCE = 100;
 
-    private static final HdrBrightnessData TEST_HDR_DATA = new HdrBrightnessData(
-            Map.of(500f, 0.6f),
-            /* brightnessIncreaseDebounceMillis= */ 1000,
-            /* screenBrightnessRampIncrease= */ 0.02f,
-            /* brightnessDecreaseDebounceMillis= */ 3000,
-            /* screenBrightnessRampDecrease= */0.04f
-    );
+    private static final HdrBrightnessData TEST_HDR_DATA = DisplayDeviceConfigTestUtilsKt
+            .createHdrBrightnessData(
+                    Map.of(500f, 0.6f),
+                    /* brightnessIncreaseDebounceMillis= */ 1000,
+                    /* screenBrightnessRampIncrease= */ 0.02f,
+                    /* brightnessDecreaseDebounceMillis= */ 3000,
+                    /* screenBrightnessRampDecrease= */0.04f
+            );
 
     private static final int WIDTH = 600;
     private static final int HEIGHT = 800;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
new file mode 100644
index 0000000..3b3d6f7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/config/DisplayDeviceConfigTestUtils.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config
+
+import android.util.Spline
+import android.util.Xml
+import com.android.server.display.config.HighBrightnessModeData.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.OutputStreamWriter
+import org.xmlpull.v1.XmlSerializer
+
+fun createRefreshRateData(
+    defaultRefreshRate: Int = 60,
+    defaultPeakRefreshRate: Int = 60,
+    defaultRefreshRateInHbmHdr: Int = 60,
+    defaultRefreshRateInHbmSunlight: Int = 60,
+    lowPowerSupportedModes: List<SupportedModeData> = emptyList(),
+    lowLightBlockingZoneSupportedModes: List<SupportedModeData> = emptyList()
+): RefreshRateData {
+    return RefreshRateData(
+        defaultRefreshRate, defaultPeakRefreshRate,
+        defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight,
+        lowPowerSupportedModes, lowLightBlockingZoneSupportedModes
+    )
+}
+
+@JvmOverloads
+fun createHdrBrightnessData(
+    maxBrightnessLimits: Map<Float, Float> = mapOf(Pair(500f, 0.6f)),
+    brightnessIncreaseDebounceMillis: Long = 1000,
+    screenBrightnessRampIncrease: Float = 0.02f,
+    brightnessDecreaseDebounceMillis: Long = 3000,
+    screenBrightnessRampDecrease: Float = 0.04f,
+    minimumHdrPercentOfScreenForNbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
+    minimumHdrPercentOfScreenForHbm: Float = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT,
+    allowInLowPowerMode: Boolean = false,
+    sdrToHdrRatioSpline: Spline? = null
+): HdrBrightnessData {
+    return HdrBrightnessData(
+        maxBrightnessLimits,
+        brightnessIncreaseDebounceMillis,
+        screenBrightnessRampIncrease,
+        brightnessDecreaseDebounceMillis,
+        screenBrightnessRampDecrease,
+        minimumHdrPercentOfScreenForNbm,
+        minimumHdrPercentOfScreenForHbm,
+        allowInLowPowerMode,
+        sdrToHdrRatioSpline
+    )
+}
+
+fun XmlSerializer.highBrightnessMode(
+    enabled: String = "true",
+    transitionPoint: String = "0.67",
+    minimumLux: String = "2500",
+    timeWindowSecs: String = "200",
+    timeMaxSecs: String = "30",
+    timeMinSecs: String = "3",
+    refreshRateRange: Pair<String, String>? = null,
+    allowInLowPowerMode: String? = null,
+    minimumHdrPercentOfScreen: String? = null,
+    sdrHdrRatioMap: List<Pair<String, String>>? = null,
+) {
+    element("highBrightnessMode") {
+        attribute("", "enabled", enabled)
+        element("transitionPoint", transitionPoint)
+        element("minimumLux", minimumLux)
+        element("timing") {
+            element("timeWindowSecs", timeWindowSecs)
+            element("timeMaxSecs", timeMaxSecs)
+            element("timeMinSecs", timeMinSecs)
+        }
+        pair("refreshRate", "minimum", "maximum", refreshRateRange)
+        element("allowInLowPowerMode", allowInLowPowerMode)
+        element("minimumHdrPercentOfScreen", minimumHdrPercentOfScreen)
+        map("sdrHdrRatioMap", "point", "sdrNits", "hdrRatio", sdrHdrRatioMap)
+    }
+}
+
+fun XmlSerializer.hdrBrightnessConfig(
+    brightnessMap: List<Pair<String, String>> = listOf(Pair("500", "0.6")),
+    brightnessIncreaseDebounceMillis: String = "1000",
+    screenBrightnessRampIncrease: String = "0.02",
+    brightnessDecreaseDebounceMillis: String = "3000",
+    screenBrightnessRampDecrease: String = "0.04",
+    minimumHdrPercentOfScreenForNbm: String? = null,
+    minimumHdrPercentOfScreenForHbm: String? = null,
+    allowInLowPowerMode: String? = null,
+    sdrHdrRatioMap: List<Pair<String, String>>? = null,
+) {
+    element("hdrBrightnessConfig") {
+        map("brightnessMap", "point", "first", "second", brightnessMap)
+        element("brightnessIncreaseDebounceMillis", brightnessIncreaseDebounceMillis)
+        element("screenBrightnessRampIncrease", screenBrightnessRampIncrease)
+        element("brightnessDecreaseDebounceMillis", brightnessDecreaseDebounceMillis)
+        element("screenBrightnessRampDecrease", screenBrightnessRampDecrease)
+        element("minimumHdrPercentOfScreenForNbm", minimumHdrPercentOfScreenForNbm)
+        element("minimumHdrPercentOfScreenForHbm", minimumHdrPercentOfScreenForHbm)
+        element("allowInLowPowerMode", allowInLowPowerMode)
+        map("sdrHdrRatioMap", "point", "first", "second", sdrHdrRatioMap)
+    }
+}
+
+fun createDisplayConfiguration(content: XmlSerializer.() -> Unit = { }): DisplayConfiguration {
+    val byteArrayOutputStream = ByteArrayOutputStream()
+    val xmlSerializer = Xml.newSerializer()
+    OutputStreamWriter(byteArrayOutputStream).use { writer ->
+        xmlSerializer.setOutput(writer)
+        xmlSerializer.startDocument("UTF-8", true)
+        xmlSerializer.startTag("", "displayConfiguration")
+        xmlSerializer.content()
+        xmlSerializer.endTag("", "displayConfiguration")
+        xmlSerializer.endDocument()
+    }
+    return XmlParser.read(ByteArrayInputStream(byteArrayOutputStream.toByteArray()))
+}
+
+private fun XmlSerializer.map(
+    rootName: String,
+    nodeName: String,
+    keyName: String,
+    valueName: String,
+    map: List<Pair<String, String>>?
+) {
+    map?.let { m ->
+        element(rootName) {
+            m.forEach { e -> pair(nodeName, keyName, valueName, e) }
+        }
+    }
+}
+
+private fun XmlSerializer.pair(
+    nodeName: String,
+    keyName: String,
+    valueName: String,
+    pair: Pair<String, String>?
+) {
+    pair?.let {
+        element(nodeName) {
+            element(keyName, pair.first)
+            element(valueName, pair.second)
+        }
+    }
+}
+
+private fun XmlSerializer.element(name: String, content: String?) {
+    if (content != null) {
+        startTag("", name)
+        text(content)
+        endTag("", name)
+    }
+}
+
+private fun XmlSerializer.element(name: String, content: XmlSerializer.() -> Unit) {
+    startTag("", name)
+    content()
+    endTag("", name)
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
new file mode 100644
index 0000000..19c6924
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/config/HdrBrightnessDataTest.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.config
+
+import android.util.Spline.createSpline
+import androidx.test.filters.SmallTest
+import com.android.server.display.DisplayBrightnessState
+import com.android.server.display.config.HighBrightnessModeData.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class HdrBrightnessDataTest {
+
+    @Test
+    fun `test HdrBrightnessData default configuration`() {
+        val displayConfiguration = createDisplayConfiguration {
+            hdrBrightnessConfig(
+                brightnessDecreaseDebounceMillis = "3000",
+                screenBrightnessRampDecrease = "0.05",
+                brightnessIncreaseDebounceMillis = "2000",
+                screenBrightnessRampIncrease = "0.03",
+                brightnessMap = listOf(Pair("500", "0.6"), Pair("600", "0.7")),
+                minimumHdrPercentOfScreenForNbm = null,
+                minimumHdrPercentOfScreenForHbm = null,
+                allowInLowPowerMode = null,
+                sdrHdrRatioMap = null,
+            )
+        }
+
+        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        assertThat(hdrBrightnessData).isNotNull()
+
+        assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(3000)
+        assertThat(hdrBrightnessData.screenBrightnessRampDecrease).isEqualTo(0.05f)
+        assertThat(hdrBrightnessData.brightnessIncreaseDebounceMillis).isEqualTo(2000)
+        assertThat(hdrBrightnessData.screenBrightnessRampIncrease).isEqualTo(0.03f)
+
+        assertThat(hdrBrightnessData.maxBrightnessLimits).hasSize(2)
+        assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(500f, 0.6f)
+        assertThat(hdrBrightnessData.maxBrightnessLimits).containsEntry(600f, 0.7f)
+
+        assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(
+            HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
+        )
+        assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(
+            HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT
+        )
+        assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
+        assertThat(hdrBrightnessData.sdrToHdrRatioSpline).isNull()
+    }
+
+    @Test
+    fun `test HdrBrightnessData fallback configuration`() {
+        val displayConfiguration = createDisplayConfiguration {
+            hdrBrightnessConfig(
+                minimumHdrPercentOfScreenForNbm = null,
+                minimumHdrPercentOfScreenForHbm = null,
+                allowInLowPowerMode = null,
+                sdrHdrRatioMap = null,
+            )
+            highBrightnessMode(
+                minimumHdrPercentOfScreen = "0.2",
+                sdrHdrRatioMap = listOf(Pair("2.0", "4.0"), Pair("5.0", "8.0"))
+            )
+        }
+
+        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        assertThat(hdrBrightnessData).isNotNull()
+
+        assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
+        assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f)
+        assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
+
+        val expectedSpline = createSpline(floatArrayOf(2.0f, 5.0f), floatArrayOf(4.0f, 8.0f))
+        assertThat(hdrBrightnessData.sdrToHdrRatioSpline.toString())
+            .isEqualTo(expectedSpline.toString())
+    }
+
+    @Test
+    fun `test HdrBrightnessData fallback configuration no hdrBrightnessConfig`() {
+        val displayConfiguration = createDisplayConfiguration {
+            highBrightnessMode(
+                minimumHdrPercentOfScreen = "0.2",
+                sdrHdrRatioMap = listOf(Pair("2.0", "4.0"), Pair("5.0", "8.0"))
+            )
+        }
+
+        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        assertThat(hdrBrightnessData).isNotNull()
+
+        assertThat(hdrBrightnessData!!.brightnessDecreaseDebounceMillis).isEqualTo(0)
+        assertThat(hdrBrightnessData.screenBrightnessRampDecrease)
+            .isEqualTo(DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET)
+        assertThat(hdrBrightnessData.brightnessIncreaseDebounceMillis).isEqualTo(0)
+        assertThat(hdrBrightnessData.screenBrightnessRampIncrease)
+            .isEqualTo(DisplayBrightnessState.CUSTOM_ANIMATION_RATE_NOT_SET)
+
+        assertThat(hdrBrightnessData.maxBrightnessLimits).hasSize(0)
+
+        assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForNbm).isEqualTo(0.2f)
+        assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.2f)
+        assertThat(hdrBrightnessData.allowInLowPowerMode).isFalse()
+
+        val expectedSpline = createSpline(floatArrayOf(2.0f, 5.0f), floatArrayOf(4.0f, 8.0f))
+        assertThat(hdrBrightnessData.sdrToHdrRatioSpline.toString())
+            .isEqualTo(expectedSpline.toString())
+    }
+
+    @Test
+    fun `test HdrBrightnessData configuration no configuration`() {
+        val displayConfiguration = createDisplayConfiguration()
+
+        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        assertThat(hdrBrightnessData).isNull()
+    }
+
+    @Test
+    fun `test HdrBrightnessData real configuration`() {
+        val displayConfiguration = createDisplayConfiguration {
+            hdrBrightnessConfig(
+                minimumHdrPercentOfScreenForNbm = "0.3",
+                minimumHdrPercentOfScreenForHbm = "0.6",
+                allowInLowPowerMode = "true",
+                sdrHdrRatioMap = listOf(Pair("3.0", "5.0"), Pair("6.0", "8.0"))
+            )
+            highBrightnessMode(
+                minimumHdrPercentOfScreen = "0.2",
+                sdrHdrRatioMap = listOf(Pair("2.0", "4.0"), Pair("5.0", "8.0"))
+            )
+        }
+
+        val hdrBrightnessData = HdrBrightnessData.loadConfig(displayConfiguration)
+        assertThat(hdrBrightnessData).isNotNull()
+
+        assertThat(hdrBrightnessData!!.minimumHdrPercentOfScreenForNbm).isEqualTo(0.3f)
+        assertThat(hdrBrightnessData.minimumHdrPercentOfScreenForHbm).isEqualTo(0.6f)
+        assertThat(hdrBrightnessData.allowInLowPowerMode).isTrue()
+
+        val expectedSpline = createSpline(floatArrayOf(3.0f, 6.0f), floatArrayOf(5.0f, 8.0f))
+        assertThat(hdrBrightnessData.sdrToHdrRatioSpline.toString())
+            .isEqualTo(expectedSpline.toString())
+    }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
index 95702aa..3c77ec9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/BrightnessObserverTest.kt
@@ -25,6 +25,7 @@
 import com.android.server.display.DisplayDeviceConfig
 import com.android.server.display.config.RefreshRateData
 import com.android.server.display.config.SupportedModeData
+import com.android.server.display.config.createRefreshRateData
 import com.android.server.display.feature.DisplayManagerFlags
 import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
 import com.android.server.testutils.TestHandler
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
index e431c8c..4fc574a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt
@@ -29,6 +29,7 @@
 import com.android.server.display.DisplayDeviceConfig
 import com.android.server.display.config.RefreshRateData
 import com.android.server.display.config.SupportedModeData
+import com.android.server.display.config.createRefreshRateData
 import com.android.server.display.feature.DisplayManagerFlags
 import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider
 import com.android.server.display.mode.SupportedRefreshRatesVote.RefreshRates
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
index 5b07166..0b34fce 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt
@@ -16,9 +16,6 @@
 
 package com.android.server.display.mode
 
-import com.android.server.display.config.RefreshRateData
-import com.android.server.display.config.SupportedModeData
-
 internal fun createVotesSummary(
         isDisplayResolutionRangeVotingEnabled: Boolean = true,
         supportedModesVoteEnabled: Boolean = true,
@@ -29,15 +26,3 @@
             loggingEnabled, supportsFrameRateOverride)
 }
 
-fun createRefreshRateData(
-        defaultRefreshRate: Int = 60,
-        defaultPeakRefreshRate: Int = 60,
-        defaultRefreshRateInHbmHdr: Int = 60,
-        defaultRefreshRateInHbmSunlight: Int = 60,
-        lowPowerSupportedModes: List<SupportedModeData> = emptyList(),
-        lowLightBlockingZoneSupportedModes: List<SupportedModeData> = emptyList()
-): RefreshRateData {
-        return RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate,
-                defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight,
-                lowPowerSupportedModes, lowLightBlockingZoneSupportedModes)
-}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 669cddb..cbf2c2f 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -18,9 +18,9 @@
 
 #include <unordered_set>
 
-#include "android-base/logging.h"
-
 #include "ResourceUtils.h"
+#include "android-base/logging.h"
+#include "process/SymbolTable.h"
 #include "trace/TraceBuffer.h"
 #include "util/Util.h"
 #include "xml/XmlActionExecutor.h"
@@ -172,6 +172,58 @@
   return NameIsJavaClassName(el, attr, diag);
 }
 
+static bool UpdateConfigChangesIfNeeded(xml::Element* el, IAaptContext* context) {
+  xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "configChanges");
+  if (attr == nullptr) {
+    return true;
+  }
+
+  if (attr->value != "allKnown" && attr->value.find("allKnown") != std::string::npos) {
+    context->GetDiagnostics()->Error(
+        android::DiagMessage(el->line_number)
+        << "If you want to declare 'allKnown' in attribute 'android:configChanges' in <" << el->name
+        << ">, " << attr->value << " is not allowed', allKnown has to be used "
+        << "by itself, for example: 'android:configChanges=allKnown', it cannot be combined with "
+        << "the other flags");
+    return false;
+  }
+
+  if (attr->value == "allKnown") {
+    SymbolTable* symbol_table = context->GetExternalSymbols();
+    const SymbolTable::Symbol* symbol =
+        symbol_table->FindByName(ResourceName("android", ResourceType::kAttr, "configChanges"));
+
+    if (symbol == nullptr) {
+      context->GetDiagnostics()->Error(
+          android::DiagMessage(el->line_number)
+          << "Cannot find symbol for android:configChanges with min sdk: "
+          << context->GetMinSdkVersion());
+    }
+
+    std::stringstream new_value;
+
+    const auto& symbols = symbol->attribute->symbols;
+    for (auto it = symbols.begin(); it != symbols.end(); ++it) {
+      // Skip 'resourcesUnused' which is the flag to fully disable activity restart specifically
+      // for games.
+      if (it->symbol.name.value().entry == "resourcesUnused") {
+        continue;
+      }
+      if (it != symbols.begin()) {
+        new_value << "|";
+      }
+      new_value << it->symbol.name.value().entry;
+    }
+    const auto& old_value = attr->value;
+    auto new_value_str = new_value.str();
+    context->GetDiagnostics()->Note(android::DiagMessage(el->line_number)
+                                    << "Updating value of 'android:configChanges' from "
+                                    << old_value << " to " << new_value_str);
+    attr->value = std::move(new_value_str);
+  }
+  return true;
+}
+
 static bool RequiredNameIsJavaPackage(xml::Element* el, android::SourcePathDiagnostics* diag) {
   xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
   if (attr == nullptr) {
@@ -382,8 +434,9 @@
   }
 }
 
-bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagnostics* diag) {
+bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, IAaptContext* context) {
   // First verify some options.
+  android::IDiagnostics* diag = context->GetDiagnostics();
   if (options_.rename_manifest_package) {
     if (!util::IsJavaPackageName(options_.rename_manifest_package.value())) {
       diag->Error(android::DiagMessage() << "invalid manifest package override '"
@@ -432,6 +485,8 @@
   // Common component actions.
   xml::XmlNodeAction component_action;
   component_action.Action(RequiredNameIsJavaClassName);
+  component_action.Action(
+      [context](xml::Element* el) -> bool { return UpdateConfigChangesIfNeeded(el, context); });
   component_action["intent-filter"] = intent_filter_action;
   component_action["preferred"] = intent_filter_action;
   component_action["meta-data"] = meta_data_action;
@@ -778,7 +833,7 @@
   }
 
   xml::XmlActionExecutor executor;
-  if (!BuildRules(&executor, context->GetDiagnostics())) {
+  if (!BuildRules(&executor, context)) {
     return false;
   }
 
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index df0ece6..748a828 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -110,7 +110,7 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(ManifestFixer);
 
-  bool BuildRules(xml::XmlActionExecutor* executor, android::IDiagnostics* diag);
+  bool BuildRules(xml::XmlActionExecutor* executor, IAaptContext* context);
 
   ManifestFixerOptions options_;
 };
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 3cfdf78..5008627 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -37,27 +37,30 @@
             .SetCompilationPackage("android")
             .SetPackageId(0x01)
             .SetNameManglerPolicy(NameManglerPolicy{"android"})
-            .AddSymbolSource(
-                test::StaticSymbolSourceBuilder()
-                    .AddSymbol(
-                        "android:attr/package", ResourceId(0x01010000),
-                        test::AttributeBuilder()
-                            .SetTypeMask(android::ResTable_map::TYPE_STRING)
-                            .Build())
-                    .AddSymbol(
-                        "android:attr/minSdkVersion", ResourceId(0x01010001),
-                        test::AttributeBuilder()
-                            .SetTypeMask(android::ResTable_map::TYPE_STRING |
-                                         android::ResTable_map::TYPE_INTEGER)
-                            .Build())
-                    .AddSymbol(
-                        "android:attr/targetSdkVersion", ResourceId(0x01010002),
-                        test::AttributeBuilder()
-                            .SetTypeMask(android::ResTable_map::TYPE_STRING |
-                                         android::ResTable_map::TYPE_INTEGER)
-                            .Build())
-                    .AddSymbol("android:string/str", ResourceId(0x01060000))
-                    .Build())
+            .AddSymbolSource(test::StaticSymbolSourceBuilder()
+                                 .AddSymbol("android:attr/package", ResourceId(0x01010000),
+                                            test::AttributeBuilder()
+                                                .SetTypeMask(android::ResTable_map::TYPE_STRING)
+                                                .Build())
+                                 .AddSymbol("android:attr/minSdkVersion", ResourceId(0x01010001),
+                                            test::AttributeBuilder()
+                                                .SetTypeMask(android::ResTable_map::TYPE_STRING |
+                                                             android::ResTable_map::TYPE_INTEGER)
+                                                .Build())
+                                 .AddSymbol("android:attr/targetSdkVersion", ResourceId(0x01010002),
+                                            test::AttributeBuilder()
+                                                .SetTypeMask(android::ResTable_map::TYPE_STRING |
+                                                             android::ResTable_map::TYPE_INTEGER)
+                                                .Build())
+                                 .AddSymbol("android:string/str", ResourceId(0x01060000))
+                                 .AddSymbol("android:attr/configChanges", ResourceId(0x01010003),
+                                            test::AttributeBuilder()
+                                                .AddItem("testConfigChange1", 1)
+                                                .AddItem("testConfigChange2", 2)
+                                                .AddItem("resourcesUnused", 4)
+                                                .SetTypeMask(android::ResTable_map::TYPE_STRING)
+                                                .Build())
+                                 .Build())
             .Build();
   }
 
@@ -1591,4 +1594,72 @@
     </manifest>)";
   EXPECT_THAT(Verify(input), NotNull());
 }
+
+TEST_F(ManifestFixerTest, AllKnownNotDeclaredProperly) {
+  std::string input = R"(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android">
+      <application>
+        <activity android:name=".MainActivity"
+                  android:configChanges="allKnown|testConfigChange1">
+        </activity>
+      </application>
+    </manifest>)";
+  auto doc = Verify(input);
+  EXPECT_THAT(doc, IsNull());
+}
+
+TEST_F(ManifestFixerTest, ModifyAttributeValueForAllKnown) {
+  std::string input = R"(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android">
+      <application>
+        <activity android:name=".MainActivity"
+                  android:configChanges="allKnown">
+        </activity>
+      </application>
+    </manifest>)";
+  auto doc = Verify(input);
+  EXPECT_THAT(doc, NotNull());
+
+  xml::Element* el;
+  xml::Attribute* attr;
+
+  el = doc->root.get();
+  ASSERT_THAT(el, NotNull());
+  el = el->FindChild({}, "application");
+  ASSERT_THAT(el, NotNull());
+  el = el->FindChild({}, "activity");
+  ASSERT_THAT(el, NotNull());
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "configChanges");
+  ASSERT_THAT(attr->value, "testConfigChange1|testConfigChange2");
+}
+
+TEST_F(ManifestFixerTest, DoNothingForOtherConfigChanges) {
+  std::string input = R"(
+    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android">
+      <application>
+        <activity android:name=".MainActivity"
+                  android:configChanges="testConfigChange2">
+        </activity>
+      </application>
+    </manifest>)";
+  auto doc = Verify(input);
+  EXPECT_THAT(doc, NotNull());
+
+  xml::Element* el;
+  xml::Attribute* attr;
+
+  el = doc->root.get();
+  ASSERT_THAT(el, NotNull());
+  el = el->FindChild({}, "application");
+  ASSERT_THAT(el, NotNull());
+  el = el->FindChild({}, "activity");
+  ASSERT_THAT(el, NotNull());
+
+  attr = el->FindAttribute(xml::kSchemaAndroid, "configChanges");
+  ASSERT_THAT(attr->value, "testConfigChange2");
+}
 }  // namespace aapt