Merge "Implement screenshot smart actions with new action provider" into main
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8913d6d..e53bd39 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6982,7 +6982,7 @@
                             }
                         } else {
                             // No package, perhaps it was removed?
-                            Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
+                            Slog.d(TAG, "Package [" + packages[i] + "] reported as REPLACED,"
                                     + " but missing application info. Assuming REMOVED.");
                             mPackages.remove(packages[i]);
                             mResourcePackages.remove(packages[i]);
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index 1425063..6b2baa7 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -36,13 +36,6 @@
 }
 
 flag {
-  name: "cross_user_suspension_enabled"
-  namespace: "enterprise"
-  description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users."
-  bug: "263464464"
-}
-
-flag {
   name: "cross_user_suspension_enabled_ro"
   namespace: "enterprise"
   description: "Allow holders of INTERACT_ACROSS_USERS_FULL to suspend apps in different users."
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8bc5e8c..9e316a2 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3771,6 +3771,10 @@
      *   <li><em>{@link android.content.Intent#EXTRA_PHONE_NUMBER}</em> -
      *       the phone number originally intended to be dialed.</li>
      * </ul>
+     * <p class="note">Starting in Android 15, this broadcast is no longer sent as an ordered
+     * broadcast.  The <code>resultData</code> no longer has any effect and will not determine the
+     * actual routing of the call.  Further, receivers of this broadcast do not get foreground
+     * priority and cannot launch background activities.</p>
      * <p>Once the broadcast is finished, the resultData is used as the actual
      * number to call.  If  <code>null</code>, no call will be placed.</p>
      * <p>It is perfectly acceptable for multiple receivers to process the
@@ -3811,8 +3815,8 @@
      * {@link android.telecom.CallRedirectionService} API.  Apps that perform call screening
      * should use the {@link android.telecom.CallScreeningService} API.  Apps which need to be
      * notified of basic call state should use
-     * {@link android.telephony.PhoneStateListener#onCallStateChanged(int, String)} to determine
-     * when a new outgoing call is placed.
+     * {@link android.telephony.TelephonyCallback.CallStateListener} to determine when a new
+     * outgoing call is placed.
      */
     @Deprecated
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java
index 64fc4c2..e583627 100644
--- a/core/java/android/hardware/devicestate/DeviceState.java
+++ b/core/java/android/hardware/devicestate/DeviceState.java
@@ -399,16 +399,8 @@
         public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(mIdentifier);
             dest.writeString8(mName);
-
-            dest.writeInt(mSystemProperties.size());
-            for (int i = 0; i < mSystemProperties.size(); i++) {
-                dest.writeInt(mSystemProperties.valueAt(i));
-            }
-
-            dest.writeInt(mPhysicalProperties.size());
-            for (int i = 0; i < mPhysicalProperties.size(); i++) {
-                dest.writeInt(mPhysicalProperties.valueAt(i));
-            }
+            dest.writeArraySet(mSystemProperties);
+            dest.writeArraySet(mPhysicalProperties);
         }
 
         @NonNull
@@ -417,16 +409,11 @@
             public DeviceState.Configuration createFromParcel(Parcel source) {
                 int identifier = source.readInt();
                 String name = source.readString8();
-                ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>();
-                int systemPropertySize = source.readInt();
-                for (int i = 0; i < systemPropertySize; i++) {
-                    systemProperties.add(source.readInt());
-                }
-                ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>();
-                int physicalPropertySize = source.readInt();
-                for (int j = 0; j < physicalPropertySize; j++) {
-                    physicalProperties.add(source.readInt());
-                }
+                ArraySet<@SystemDeviceStateProperties Integer> systemProperties =
+                        (ArraySet<Integer>) source.readArraySet(null /* classLoader */);
+                ArraySet<@PhysicalDeviceStateProperties Integer> physicalProperties =
+                        (ArraySet<Integer>) source.readArraySet(null /* classLoader */);
+
                 return new DeviceState.Configuration(identifier, name, systemProperties,
                         physicalProperties);
             }
diff --git a/core/java/android/hardware/devicestate/OWNERS b/core/java/android/hardware/devicestate/OWNERS
new file mode 100644
index 0000000..d9b0e2e
--- /dev/null
+++ b/core/java/android/hardware/devicestate/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/devicestate/OWNERS
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 6c6e8b2..188ad8f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -203,9 +203,7 @@
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"},
-            value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
-                    FRAME_RATE_COMPATIBILITY_EXACT, FRAME_RATE_COMPATIBILITY_NO_VOTE,
-                    FRAME_RATE_COMPATIBILITY_MIN, FRAME_RATE_COMPATIBILITY_GTE})
+            value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE})
     public @interface FrameRateCompatibility {}
 
     // From native_window.h. Keep these in sync.
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 356eac3..e6a2a6c 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -3956,6 +3956,9 @@
 
     @Override
     public int getNavigationBarColor() {
+        if (mEdgeToEdgeEnforced) {
+            return Color.TRANSPARENT;
+        }
         return mNavigationBarColor;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index ce8a460..0867a44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2329,6 +2329,12 @@
             mMainExecutor.execute(() ->
                     mController.showUserEducation(new Point(positionX, positionY)));
         }
+
+        @Override
+        public void setBubbleBarLocation(BubbleBarLocation location) {
+            mMainExecutor.execute(() ->
+                    mController.setBubbleBarLocation(location));
+        }
     }
 
     private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 7a5afec..16134d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -19,6 +19,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import com.android.wm.shell.bubbles.IBubblesListener;
+import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 
 /**
  * Interface that is exposed to remote callers (launcher) to manipulate the bubbles feature when
@@ -42,4 +43,5 @@
 
     oneway void showUserEducation(in int positionX, in int positionY) = 8;
 
+    oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9;
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
new file mode 100644
index 0000000..3c5beeb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarLocation.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.bubbles;
+
+parcelable BubbleBarLocation;
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 235456c..3b4fb9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1069,7 +1069,7 @@
         }
 
         private boolean allAppsAreTranslucent(ArrayList<TaskState> tasks) {
-            if (tasks == null || tasks.isEmpty()) {
+            if (tasks == null) {
                 return false;
             }
             for (int i = tasks.size() - 1; i >= 0; --i) {
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 2a0648d..7ed67dc 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -23,6 +23,9 @@
 import android.annotation.TestApi;
 import android.app.Activity;
 import android.app.ActivityOptions.LaunchCookie;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.Disabled;
+import android.compat.annotation.Overridable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -66,6 +69,18 @@
     private static final String TAG = "MediaProjectionManager";
 
     /**
+     * This change id ensures that users are presented with a choice of capturing a single app
+     * or the entire screen when initiating a MediaProjection session, overriding the usage of
+     * MediaProjectionConfig#createConfigForDefaultDisplay.
+     *
+     * @hide
+     */
+    @ChangeId
+    @Overridable
+    @Disabled
+    public static final long OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L;
+
+    /**
      * Intent extra to customize the permission dialog based on the host app's preferences.
      * @hide
      */
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index af89f63..4bfc629 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -25,6 +25,13 @@
 }
 
 flag {
+    name: "notification_heads_up_cycling"
+    namespace: "systemui"
+    description: "Heads-up notification cycling animation for the Notification Avalanche feature."
+    bug: "316404716"
+}
+
+flag {
    name: "notification_minimalism_prototype"
    namespace: "systemui"
    description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
index 28fd785..f6f07a5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt
@@ -32,8 +32,10 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
@@ -62,7 +64,10 @@
         ) {
             OutlinedIconToggleButton(
                 modifier =
-                    Modifier.height(64.dp).fillMaxWidth().semantics { contentDescription = label },
+                    Modifier.height(64.dp).fillMaxWidth().semantics {
+                        role = Role.Switch
+                        contentDescription = label
+                    },
                 checked = viewModel.isChecked,
                 onCheckedChange = onCheckedChange,
                 shape = RoundedCornerShape(28.dp),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
new file mode 100644
index 0000000..312c14d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.data.repository
+
+import android.content.applicationContext
+import android.os.UserManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.PolicyRestriction
+import com.android.systemui.utils.UserRestrictionChecker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessPolicyRepositoryImplTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val fakeUserRepository = kosmos.fakeUserRepository
+
+    private val mockUserRestrictionChecker: UserRestrictionChecker = mock {
+        whenever(checkIfRestrictionEnforced(any(), anyString(), anyInt())).thenReturn(null)
+        whenever(hasBaseUserRestriction(any(), anyString(), anyInt())).thenReturn(false)
+    }
+
+    private val underTest =
+        with(kosmos) {
+            BrightnessPolicyRepositoryImpl(
+                userRepository,
+                mockUserRestrictionChecker,
+                applicationContext,
+                testDispatcher,
+            )
+        }
+
+    @Test
+    fun noRestrictionByDefaultForAllUsers() =
+        with(kosmos) {
+            testScope.runTest {
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+
+                fakeUserRepository.asMainUser()
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+            }
+        }
+
+    @Test
+    fun restrictDefaultUser() =
+        with(kosmos) {
+            testScope.runTest {
+                val enforcedAdmin: EnforcedAdmin =
+                    EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+                whenever(
+                        mockUserRestrictionChecker.checkIfRestrictionEnforced(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.getSelectedUserInfo().id)
+                        )
+                    )
+                    .thenReturn(enforcedAdmin)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin))
+
+                fakeUserRepository.asMainUser()
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+            }
+        }
+
+    @Test
+    fun restrictMainUser() =
+        with(kosmos) {
+            testScope.runTest {
+                val enforcedAdmin: EnforcedAdmin =
+                    EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION)
+
+                whenever(
+                        mockUserRestrictionChecker.checkIfRestrictionEnforced(
+                            any(),
+                            eq(RESTRICTION),
+                            eq(userRepository.mainUserId)
+                        )
+                    )
+                    .thenReturn(enforcedAdmin)
+
+                val restrictions by collectLastValue(underTest.restrictionPolicy)
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction)
+
+                fakeUserRepository.asMainUser()
+
+                assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin))
+            }
+        }
+
+    private companion object {
+        val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
new file mode 100644
index 0000000..e39ad4f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.brightness.data.repository
+
+import android.hardware.display.BrightnessInfo
+import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE
+import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ScreenBrightnessDisplayManagerRepositoryTest : SysuiTestCase() {
+    @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    private val kosmos = testKosmos()
+
+    private var currentBrightnessInfo = BrightnessInfo()
+
+    @Mock private lateinit var displayManager: DisplayManager
+    @Mock private lateinit var display: Display
+
+    private val displayId = 0
+
+    private lateinit var underTest: ScreenBrightnessDisplayManagerRepository
+
+    @Before
+    fun setUp() {
+        underTest =
+            ScreenBrightnessDisplayManagerRepository(
+                displayId,
+                displayManager,
+                kosmos.applicationCoroutineScope,
+                kosmos.testDispatcher,
+            )
+
+        whenever(displayManager.getDisplay(displayId)).thenReturn(display)
+        // Using then answer so it will be retrieved in every call
+        whenever(display.brightnessInfo).thenAnswer { currentBrightnessInfo }
+    }
+
+    @Test
+    fun startingBrightnessInfo() =
+        with(kosmos) {
+            testScope.runTest {
+                val brightness by collectLastValue(underTest.linearBrightness)
+                val minBrightness by collectLastValue(underTest.minLinearBrightness)
+                val maxBrightness by collectLastValue(underTest.maxLinearBrightness)
+                runCurrent()
+
+                assertThat(brightness?.floatValue).isEqualTo(currentBrightnessInfo.brightness)
+                assertThat(minBrightness?.floatValue)
+                    .isEqualTo(currentBrightnessInfo.brightnessMinimum)
+                assertThat(maxBrightness?.floatValue)
+                    .isEqualTo(currentBrightnessInfo.brightnessMaximum)
+            }
+        }
+
+    @Test
+    fun followsChangingBrightnessInfo() =
+        with(kosmos) {
+            testScope.runTest {
+                val listenerCaptor = argumentCaptor<DisplayManager.DisplayListener>()
+
+                val brightness by collectLastValue(underTest.linearBrightness)
+                val minBrightness by collectLastValue(underTest.minLinearBrightness)
+                val maxBrightness by collectLastValue(underTest.maxLinearBrightness)
+                runCurrent()
+
+                verify(displayManager)
+                    .registerDisplayListener(
+                        capture(listenerCaptor),
+                        eq(null),
+                        eq(EVENT_FLAG_DISPLAY_BRIGHTNESS),
+                    )
+
+                val newBrightness = BrightnessInfo(0.6f, 0.3f, 0.9f)
+                changeBrightnessInfoAndNotify(newBrightness, listenerCaptor.value)
+
+                assertThat(brightness?.floatValue).isEqualTo(currentBrightnessInfo.brightness)
+                assertThat(minBrightness?.floatValue)
+                    .isEqualTo(currentBrightnessInfo.brightnessMinimum)
+                assertThat(maxBrightness?.floatValue)
+                    .isEqualTo(currentBrightnessInfo.brightnessMaximum)
+            }
+        }
+
+    @Test
+    fun minMaxWhenNotCollecting() =
+        with(kosmos) {
+            testScope.runTest {
+                currentBrightnessInfo = BrightnessInfo(0.5f, 0.1f, 0.7f)
+                val (min, max) = underTest.getMinMaxLinearBrightness()
+                assertThat(min.floatValue).isEqualTo(currentBrightnessInfo.brightnessMinimum)
+                assertThat(max.floatValue).isEqualTo(currentBrightnessInfo.brightnessMaximum)
+            }
+        }
+
+    @Test
+    fun minMaxWhenCollecting() =
+        with(kosmos) {
+            testScope.runTest {
+                val listenerCaptor = argumentCaptor<DisplayManager.DisplayListener>()
+
+                val brightness by collectLastValue(underTest.linearBrightness)
+                runCurrent()
+
+                verify(displayManager)
+                    .registerDisplayListener(
+                        capture(listenerCaptor),
+                        eq(null),
+                        eq(EVENT_FLAG_DISPLAY_BRIGHTNESS),
+                    )
+
+                changeBrightnessInfoAndNotify(
+                    BrightnessInfo(0.5f, 0.1f, 0.7f),
+                    listenerCaptor.value
+                )
+                runCurrent()
+
+                val (min, max) = underTest.getMinMaxLinearBrightness()
+                assertThat(min.floatValue).isEqualTo(currentBrightnessInfo.brightnessMinimum)
+                assertThat(max.floatValue).isEqualTo(currentBrightnessInfo.brightnessMaximum)
+            }
+        }
+
+    @Test
+    fun setTemporaryBrightness_insideBounds() =
+        with(kosmos) {
+            testScope.runTest {
+                val brightness = 0.3f
+                underTest.setTemporaryBrightness(LinearBrightness(brightness))
+                runCurrent()
+
+                verify(displayManager).setTemporaryBrightness(displayId, brightness)
+                verify(displayManager, never()).setBrightness(anyInt(), anyFloat())
+            }
+        }
+
+    @Test
+    fun setTemporaryBrightness_outsideBounds() =
+        with(kosmos) {
+            testScope.runTest {
+                val brightness = 1.3f
+                underTest.setTemporaryBrightness(LinearBrightness(brightness))
+                runCurrent()
+
+                verify(displayManager)
+                    .setTemporaryBrightness(displayId, currentBrightnessInfo.brightnessMaximum)
+                verify(displayManager, never()).setBrightness(anyInt(), anyFloat())
+            }
+        }
+
+    @Test
+    fun setBrightness_insideBounds() =
+        with(kosmos) {
+            testScope.runTest {
+                val brightness = 0.3f
+                underTest.setBrightness(LinearBrightness(brightness))
+                runCurrent()
+
+                verify(displayManager).setBrightness(displayId, brightness)
+                verify(displayManager, never()).setTemporaryBrightness(anyInt(), anyFloat())
+            }
+        }
+
+    @Test
+    fun setBrightness_outsideBounds() =
+        with(kosmos) {
+            testScope.runTest {
+                val brightness = 1.3f
+                underTest.setBrightness(LinearBrightness(brightness))
+                runCurrent()
+
+                verify(displayManager)
+                    .setBrightness(displayId, currentBrightnessInfo.brightnessMaximum)
+                verify(displayManager, never()).setTemporaryBrightness(anyInt(), anyFloat())
+            }
+        }
+
+    private fun changeBrightnessInfoAndNotify(
+        newValue: BrightnessInfo,
+        listener: DisplayManager.DisplayListener,
+    ) {
+        currentBrightnessInfo = newValue
+        listener.onDisplayChanged(displayId)
+    }
+
+    companion object {
+        fun BrightnessInfo(
+            brightness: Float = 0f,
+            minBrightness: Float = 0f,
+            maxBrightness: Float = 1f,
+        ): BrightnessInfo {
+            return BrightnessInfo(
+                brightness,
+                minBrightness,
+                maxBrightness,
+                HIGH_BRIGHTNESS_MODE_OFF,
+                1f,
+                BRIGHTNESS_MAX_REASON_NONE,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
new file mode 100644
index 0000000..85a4bcf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt
@@ -0,0 +1,116 @@
+/*
+ * 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.brightness.domain.interactor
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.RestrictedLockUtils
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.brightness.data.repository.BrightnessPolicyRepository
+import com.android.systemui.brightness.data.repository.brightnessPolicyRepository
+import com.android.systemui.brightness.data.repository.fakeBrightnessPolicyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.utils.PolicyRestriction
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessPolicyEnforcementInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val mockActivityStarter = kosmos.activityStarter
+    private val fakeBrightnessPolicyEnforcementInteractor = kosmos.fakeBrightnessPolicyRepository
+
+    private val underTest =
+        with(kosmos) {
+            BrightnessPolicyEnforcementInteractor(
+                brightnessPolicyRepository,
+                activityStarter,
+            )
+        }
+
+    @Test
+    fun restriction() =
+        with(kosmos) {
+            testScope.runTest {
+                fakeBrightnessPolicyRepository.setCurrentUserUnrestricted()
+
+                val restriction by collectLastValue(underTest.brightnessPolicyRestriction)
+
+                assertThat(restriction).isEqualTo(PolicyRestriction.NoRestriction)
+
+                fakeBrightnessPolicyRepository.setCurrentUserRestricted()
+
+                assertThat(restriction).isInstanceOf(PolicyRestriction.Restricted::class.java)
+            }
+        }
+
+    @Test
+    fun startRestrictionDialog() =
+        with(kosmos) {
+            testScope.runTest {
+                val enforcedAdmin =
+                    EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+                            BrightnessPolicyRepository.RESTRICTION
+                        )
+                        .apply {
+                            component = TEST_COMPONENT
+                            user = UserHandle.of(TEST_USER)
+                        }
+
+                underTest.startAdminSupportDetailsDialog(
+                    PolicyRestriction.Restricted(enforcedAdmin)
+                )
+
+                val intentCaptor = argumentCaptor<Intent>()
+
+                verify(mockActivityStarter)
+                    .postStartActivityDismissingKeyguard(
+                        capture(intentCaptor),
+                        eq(0),
+                    )
+
+                val expectedIntent =
+                    RestrictedLockUtils.getShowAdminSupportDetailsIntent(enforcedAdmin)
+
+                with(intentCaptor.value) {
+                    assertThat(action).isEqualTo(expectedIntent.action)
+                    assertThat(extras!!.kindofEquals(expectedIntent.extras)).isTrue()
+                }
+            }
+        }
+
+    private companion object {
+        val TEST_COMPONENT = ComponentName("pkg", ".cls")
+        val TEST_USER = 10
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
new file mode 100644
index 0000000..33c44f8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
@@ -0,0 +1,146 @@
+/*
+ * 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.brightness.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.display.BrightnessUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository
+import com.android.systemui.brightness.data.repository.screenBrightnessRepository
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ScreenBrightnessInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    private val underTest = ScreenBrightnessInteractor(kosmos.screenBrightnessRepository)
+
+    @Test
+    fun gammaBrightness() =
+        with(kosmos) {
+            testScope.runTest {
+                val gammaBrightness by collectLastValue(underTest.gammaBrightness)
+
+                val brightness = 0.3f
+                val min = 0f
+                val max = 1f
+
+                with(fakeScreenBrightnessRepository) {
+                    setBrightness(LinearBrightness(brightness))
+                    setMinMaxBrightness(LinearBrightness(min), LinearBrightness(max))
+                }
+                runCurrent()
+
+                assertThat(gammaBrightness?.value)
+                    .isEqualTo(BrightnessUtils.convertLinearToGammaFloat(brightness, min, max))
+            }
+        }
+
+    @Test
+    fun gammaBrightness_constrained() =
+        with(kosmos) {
+            testScope.runTest {
+                val gammaBrightness by collectLastValue(underTest.gammaBrightness)
+
+                val brightness = 0.3f
+                val min = 0.2f
+                val max = 0.8f
+
+                with(fakeScreenBrightnessRepository) {
+                    setBrightness(LinearBrightness(brightness))
+                    setMinMaxBrightness(LinearBrightness(min), LinearBrightness(max))
+                }
+                runCurrent()
+
+                assertThat(gammaBrightness?.value)
+                    .isEqualTo(BrightnessUtils.convertLinearToGammaFloat(brightness, min, max))
+            }
+        }
+
+    @Test
+    fun setTemporaryBrightness() =
+        with(kosmos) {
+            testScope.runTest {
+                val temporaryBrightness by
+                    collectLastValue(fakeScreenBrightnessRepository.temporaryBrightness)
+                val brightness by collectLastValue(underTest.gammaBrightness)
+
+                val gammaBrightness = 30000
+                underTest.setTemporaryBrightness(GammaBrightness(gammaBrightness))
+
+                val (min, max) = fakeScreenBrightnessRepository.getMinMaxLinearBrightness()
+
+                val expectedTemporaryBrightness =
+                    BrightnessUtils.convertGammaToLinearFloat(
+                        gammaBrightness,
+                        min.floatValue,
+                        max.floatValue
+                    )
+                assertThat(temporaryBrightness!!.floatValue)
+                    .isWithin(1e-5f)
+                    .of(expectedTemporaryBrightness)
+                assertThat(brightness!!.value).isNotEqualTo(gammaBrightness)
+            }
+        }
+
+    @Test
+    fun setBrightness() =
+        with(kosmos) {
+            testScope.runTest {
+                val brightness by collectLastValue(fakeScreenBrightnessRepository.linearBrightness)
+
+                val gammaBrightness = 30000
+                underTest.setBrightness(GammaBrightness(gammaBrightness))
+
+                val (min, max) = fakeScreenBrightnessRepository.getMinMaxLinearBrightness()
+
+                val expectedBrightness =
+                    BrightnessUtils.convertGammaToLinearFloat(
+                        gammaBrightness,
+                        min.floatValue,
+                        max.floatValue
+                    )
+                assertThat(brightness!!.floatValue).isWithin(1e-5f).of(expectedBrightness)
+            }
+        }
+
+    @Test
+    fun maxGammaBrightness() {
+        assertThat(underTest.maxGammaBrightness)
+            .isEqualTo(GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX))
+    }
+
+    @Test
+    fun minGammaBrightness() {
+        assertThat(underTest.minGammaBrightness)
+            .isEqualTo(GammaBrightness(BrightnessUtils.GAMMA_SPACE_MIN))
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
new file mode 100644
index 0000000..0058ee4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
@@ -0,0 +1,167 @@
+/*
+ * 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.brightness.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.display.BrightnessUtils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository
+import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor
+import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BrightnessSliderViewModelTest : SysuiTestCase() {
+
+    private val minBrightness = 0f
+    private val maxBrightness = 1f
+
+    private val kosmos = testKosmos()
+
+    private val underTest =
+        with(kosmos) {
+            BrightnessSliderViewModel(
+                screenBrightnessInteractor,
+                brightnessPolicyEnforcementInteractor,
+            )
+        }
+
+    @Before
+    fun setUp() {
+        kosmos.fakeScreenBrightnessRepository.setMinMaxBrightness(
+            LinearBrightness(minBrightness),
+            LinearBrightness(maxBrightness)
+        )
+    }
+
+    @Test
+    fun brightnessChangeInRepository_changeInFlow() =
+        with(kosmos) {
+            testScope.runTest {
+                val gammaBrightness by collectLastValue(underTest.currentBrightness)
+
+                var brightness = 0.6f
+                fakeScreenBrightnessRepository.setBrightness(LinearBrightness(brightness))
+
+                assertThat(gammaBrightness!!.value)
+                    .isEqualTo(
+                        BrightnessUtils.convertLinearToGammaFloat(
+                            brightness,
+                            minBrightness,
+                            maxBrightness
+                        )
+                    )
+
+                brightness = 0.2f
+                fakeScreenBrightnessRepository.setBrightness(LinearBrightness(brightness))
+
+                assertThat(gammaBrightness!!.value)
+                    .isEqualTo(
+                        BrightnessUtils.convertLinearToGammaFloat(
+                            brightness,
+                            minBrightness,
+                            maxBrightness
+                        )
+                    )
+            }
+        }
+
+    @Test
+    fun maxGammaBrightness() {
+        assertThat(underTest.maxBrightness)
+            .isEqualTo(GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX))
+    }
+
+    @Test
+    fun minGammaBrightness() {
+        assertThat(underTest.minBrightness)
+            .isEqualTo(GammaBrightness(BrightnessUtils.GAMMA_SPACE_MIN))
+    }
+
+    @Test
+    fun dragging_temporaryBrightnessSet_currentBrightnessDoesntChange() =
+        with(kosmos) {
+            testScope.runTest {
+                val temporaryBrightness by
+                    collectLastValue(fakeScreenBrightnessRepository.temporaryBrightness)
+                val brightness by collectLastValue(underTest.currentBrightness)
+
+                val newBrightness = underTest.maxBrightness.value / 3
+                val expectedTemporaryBrightness =
+                    BrightnessUtils.convertGammaToLinearFloat(
+                        newBrightness,
+                        minBrightness,
+                        maxBrightness
+                    )
+                val drag = Drag.Dragging(GammaBrightness(newBrightness))
+
+                underTest.onDrag(drag)
+
+                assertThat(temporaryBrightness!!.floatValue)
+                    .isWithin(1e-5f)
+                    .of(expectedTemporaryBrightness)
+                assertThat(brightness!!.value).isNotEqualTo(newBrightness)
+            }
+        }
+
+    @Test
+    fun draggingStopped_currentBrightnessChanges() =
+        with(kosmos) {
+            testScope.runTest {
+                val brightness by collectLastValue(underTest.currentBrightness)
+
+                val newBrightness = underTest.maxBrightness.value / 3
+                val drag = Drag.Stopped(GammaBrightness(newBrightness))
+
+                underTest.onDrag(drag)
+
+                assertThat(brightness!!.value).isEqualTo(newBrightness)
+            }
+        }
+
+    @Test
+    fun label() {
+        assertThat(underTest.label)
+            .isEqualTo(Text.Resource(R.string.quick_settings_brightness_dialog_title))
+    }
+
+    @Test
+    fun icon() {
+        assertThat(underTest.icon)
+            .isEqualTo(
+                Icon.Resource(
+                    R.drawable.ic_brightness_full,
+                    ContentDescription.Resource(underTest.label.res),
+                )
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 6a86801..0f8fc38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -63,7 +63,6 @@
 
 import java.util.Collections;
 import java.util.Optional;
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
@@ -119,6 +118,7 @@
     private static final float TOUCH_REGION = .3f;
     private static final int SCREEN_WIDTH_PX = 1024;
     private static final int SCREEN_HEIGHT_PX = 100;
+    private static final float MIN_BOUNCER_HEIGHT = .05f;
 
     private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100);
     private static final UserInfo CURRENT_USER_INFO = new UserInfo(
@@ -142,6 +142,7 @@
                 mFlingAnimationUtils,
                 mFlingAnimationUtilsClosing,
                 TOUCH_REGION,
+                MIN_BOUNCER_HEIGHT,
                 mUiEventLogger);
 
         when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
@@ -160,9 +161,9 @@
      */
     @Test
     public void testSessionStart() {
-        mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion);
+        mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null);
 
-        verify(mRegion).op(mRectCaptor.capture(), eq(Region.Op.UNION));
+        verify(mRegion).union(mRectCaptor.capture());
         final Rect bounds = mRectCaptor.getValue();
 
         final Rect expected = new Rect();
@@ -194,6 +195,85 @@
         UP,
     }
 
+    @Test
+    public void testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects() {
+        mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
+                new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX));
+        verify(mRegion).union(mRectCaptor.capture());
+        final Rect bounds = mRectCaptor.getValue();
+
+        final Rect expected = new Rect();
+        final float minBouncerHeight =
+                SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT;
+        final int minAllowableBottom = SCREEN_HEIGHT_PX - Math.round(minBouncerHeight);
+
+        expected.set(0, minAllowableBottom , SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
+
+        assertThat(bounds).isEqualTo(expected);
+
+        onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
+    }
+
+    @Test
+    public void testSwipeUp_exclusionRectAtTop_doesNotIntersectGestureArea() {
+        mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion,
+                new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX / 4));
+        verify(mRegion).union(mRectCaptor.capture());
+        final Rect bounds = mRectCaptor.getValue();
+
+        final Rect expected = new Rect();
+        final int gestureAreaTop = SCREEN_HEIGHT_PX - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
+        expected.set(0, gestureAreaTop, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
+
+        assertThat(bounds).isEqualTo(expected);
+        onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
+    }
+
+    @Test
+    public void testSwipeUp_exclusionRectBetweenNormalAndMinimumSwipeArea() {
+        final int normalSwipeAreaTop = SCREEN_HEIGHT_PX
+                - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION);
+        final int minimumSwipeAreaTop = SCREEN_HEIGHT_PX
+                - Math.round(SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT);
+
+        Rect exclusionRect = new Rect(0, 0, SCREEN_WIDTH_PX,
+                (normalSwipeAreaTop + minimumSwipeAreaTop) / 2);
+
+        mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, exclusionRect);
+
+        verify(mRegion).union(mRectCaptor.capture());
+
+        final Rect bounds = mRectCaptor.getValue();
+        final Rect expected = new Rect();
+
+        final int expectedSwipeAreaBottom = exclusionRect.bottom;
+        expected.set(0, expectedSwipeAreaBottom, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX);
+
+        assertThat(bounds).isEqualTo(expected);
+
+        onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController);
+    }
+
+    private static void onSessionStartHelper(BouncerSwipeTouchHandler touchHandler,
+            DreamTouchHandler.TouchSession touchSession,
+            NotificationShadeWindowController notificationShadeWindowController) {
+        touchHandler.onSessionStart(touchSession);
+        verify(notificationShadeWindowController).setForcePluginOpen(eq(true), any());
+        ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor =
+                ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class);
+        ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
+                ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
+        verify(touchSession).registerGestureListener(gestureListenerCaptor.capture());
+        verify(touchSession).registerInputListener(eventListenerCaptor.capture());
+
+        // A touch within range at the bottom of the screen should trigger listening
+        assertThat(gestureListenerCaptor.getValue()
+                .onScroll(Mockito.mock(MotionEvent.class),
+                        Mockito.mock(MotionEvent.class),
+                        1,
+                        2)).isTrue();
+    }
+
     /**
      * Makes sure swiping up when bouncer initially showing doesn't change the expansion amount.
      */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 30564bb..29f286f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -47,12 +48,14 @@
 @EnableFlags(NotificationThrottleHun.FLAG_NAME)
 class AvalancheControllerTest : SysuiTestCase() {
 
-    private val mAvalancheController = AvalancheController()
-
     // For creating mocks
     @get:Rule var rule: MockitoRule = MockitoJUnit.rule()
     @Mock private val runnableMock: Runnable? = null
 
+    // For creating AvalancheController
+    @Mock private lateinit var dumpManager: DumpManager
+    private lateinit var mAvalancheController: AvalancheController
+
     // For creating TestableHeadsUpManager
     @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null
     private val mUiEventLoggerFake = UiEventLoggerFake()
@@ -73,7 +76,10 @@
             )
             .then { i: InvocationOnMock -> i.getArgument(0) }
 
-        // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null
+        // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
+        // declaration, where mocks are null
+        mAvalancheController = AvalancheController(dumpManager)
+
         testableHeadsUpManager =
             TestableHeadsUpManager(
                 mContext,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 3dc4495..7c130be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -45,6 +45,7 @@
 
 import com.android.internal.logging.testing.UiEventLoggerFake;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -73,7 +74,9 @@
 
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
-    private AvalancheController mAvalancheController = new AvalancheController();
+
+    @Mock private DumpManager dumpManager;
+    private AvalancheController mAvalancheController;
 
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
@@ -130,6 +133,7 @@
     public void SysuiSetup() throws Exception {
         super.SysuiSetup();
         mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME);
+        mAvalancheController = new AvalancheController(dumpManager);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index 61a79d8..a8a75c0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -32,6 +32,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -76,7 +77,8 @@
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private ShadeInteractor mShadeInteractor;
-    private AvalancheController mAvalancheController = new AvalancheController();
+    @Mock private DumpManager dumpManager;
+    private AvalancheController mAvalancheController;
 
     private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
         TestableHeadsUpManagerPhone(
@@ -154,6 +156,8 @@
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
         mContext.getOrCreateTestableResources().addOverride(
                 R.integer.ambient_notification_extension_time, 500);
+
+        mAvalancheController = new AvalancheController(dumpManager);
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index 6f7f20b..462f36d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -35,7 +35,6 @@
 import com.android.systemui.volume.mediaDeviceSessionInteractor
 import com.android.systemui.volume.mediaOutputActionsInteractor
 import com.android.systemui.volume.mediaOutputInteractor
-import com.android.systemui.volume.panel.volumePanelViewModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -62,7 +61,6 @@
                 MediaOutputViewModel(
                     applicationContext,
                     testScope.backgroundScope,
-                    volumePanelViewModel,
                     mediaOutputActionsInteractor,
                     mediaDeviceSessionInteractor,
                     mediaOutputInteractor,
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fe8f2ff..f2288a4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1867,6 +1867,10 @@
         .2
     </item>
 
+    <item name="dream_overlay_bouncer_min_region_screen_percentage" format="float" type="dimen">
+        .05
+    </item>
+
     <!-- The padding applied to the dream overlay container -->
     <dimen name="dream_overlay_container_padding_start">0dp</dimen>
     <dimen name="dream_overlay_container_padding_end">0dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 893887f..d88b3dc 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shared.system.SysUiStatsLog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
@@ -158,6 +159,10 @@
     /** Show the bouncer if necessary and set the relevant states. */
     @JvmOverloads
     fun show(isScrimmed: Boolean) {
+        // When the scene container framework is enabled, instead of calling this, call
+        // SceneInteractor#changeScene(Scenes.Bouncer, ...).
+        SceneContainerFlag.assertInLegacyMode()
+
         if (primaryBouncerView.delegate == null && !Flags.composeBouncer()) {
             Log.d(
                 TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
new file mode 100644
index 0000000..2b9fc73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.dagger
+
+import com.android.systemui.brightness.data.repository.BrightnessPolicyRepository
+import com.android.systemui.brightness.data.repository.BrightnessPolicyRepositoryImpl
+import com.android.systemui.brightness.data.repository.ScreenBrightnessDisplayManagerRepository
+import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface ScreenBrightnessModule {
+
+    @Binds
+    fun bindScreenBrightnessRepository(
+        impl: ScreenBrightnessDisplayManagerRepository
+    ): ScreenBrightnessRepository
+
+    @Binds
+    fun bindPolicyRepository(impl: BrightnessPolicyRepositoryImpl): BrightnessPolicyRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt
new file mode 100644
index 0000000..608f301
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.data.model
+
+@JvmInline
+value class LinearBrightness(val floatValue: Float) {
+    fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness {
+        return if (floatValue < min.floatValue) {
+            min
+        } else if (floatValue > max.floatValue) {
+            max
+        } else {
+            this
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
new file mode 100644
index 0000000..c018ecb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.brightness.data.repository
+
+import android.content.Context
+import android.os.UserManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.utils.PolicyRestriction
+import com.android.systemui.utils.UserRestrictionChecker
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.mapLatest
+
+/** Checks whether the current user is restricted to change the brightness ([RESTRICTION]) */
+interface BrightnessPolicyRepository {
+
+    /**
+     * Indicates whether the current user is restricted to change the brightness. As there is no way
+     * to determine when a restriction has been added/removed. This value may be fetched eagerly and
+     * not updated (unless the user changes) per flow.
+     */
+    val restrictionPolicy: Flow<PolicyRestriction>
+
+    companion object {
+        const val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS
+    }
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class BrightnessPolicyRepositoryImpl
+@Inject
+constructor(
+    userRepository: UserRepository,
+    private val userRestrictionChecker: UserRestrictionChecker,
+    @Application private val applicationContext: Context,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : BrightnessPolicyRepository {
+    override val restrictionPolicy =
+        userRepository.selectedUserInfo
+            .mapLatest { user ->
+                userRestrictionChecker
+                    .checkIfRestrictionEnforced(
+                        applicationContext,
+                        BrightnessPolicyRepository.RESTRICTION,
+                        user.id
+                    )
+                    ?.let { PolicyRestriction.Restricted(it) }
+                    ?: PolicyRestriction.NoRestriction
+            }
+            .flowOn(backgroundDispatcher)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
new file mode 100644
index 0000000..9ed11d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
@@ -0,0 +1,186 @@
+/*
+ * 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.brightness.data.repository
+
+import android.annotation.SuppressLint
+import android.hardware.display.BrightnessInfo
+import android.hardware.display.DisplayManager
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.DisplayId
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for tracking brightness in the current display.
+ *
+ * Values are in a linear space, as used by [DisplayManager].
+ */
+interface ScreenBrightnessRepository {
+    /** Current brightness as a value between [minLinearBrightness] and [maxLinearBrightness] */
+    val linearBrightness: Flow<LinearBrightness>
+
+    /** Current minimum value for the brightness */
+    val minLinearBrightness: Flow<LinearBrightness>
+
+    /** Current maximum value for the brightness */
+    val maxLinearBrightness: Flow<LinearBrightness>
+
+    /** Gets the current values for min and max brightness */
+    suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness>
+
+    /**
+     * Sets the temporary value for the brightness. This should change the display brightness but
+     * not trigger any updates.
+     */
+    fun setTemporaryBrightness(value: LinearBrightness)
+
+    /** Sets the brightness definitively. */
+    fun setBrightness(value: LinearBrightness)
+}
+
+@SuppressLint("MissingPermission")
+@SysUISingleton
+class ScreenBrightnessDisplayManagerRepository
+@Inject
+constructor(
+    @DisplayId private val displayId: Int,
+    private val displayManager: DisplayManager,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundContext: CoroutineContext,
+) : ScreenBrightnessRepository {
+
+    private val apiQueue =
+        Channel<SetBrightnessMethod>(
+            capacity = UNLIMITED,
+        )
+
+    init {
+        applicationScope.launch(backgroundContext) {
+            for (call in apiQueue) {
+                val bounds = getMinMaxLinearBrightness()
+                val value = call.value.clamp(bounds.first, bounds.second).floatValue
+                when (call) {
+                    is SetBrightnessMethod.Temporary -> {
+                        displayManager.setTemporaryBrightness(displayId, value)
+                    }
+                    is SetBrightnessMethod.Permanent -> {
+                        displayManager.setBrightness(displayId, value)
+                    }
+                }
+            }
+        }
+    }
+
+    private val brightnessInfo: StateFlow<BrightnessInfo?> =
+        conflatedCallbackFlow {
+                val listener =
+                    object : DisplayManager.DisplayListener {
+                        override fun onDisplayAdded(displayId: Int) {}
+
+                        override fun onDisplayRemoved(displayId: Int) {}
+
+                        override fun onDisplayChanged(displayId: Int) {
+                            if (
+                                displayId == this@ScreenBrightnessDisplayManagerRepository.displayId
+                            ) {
+                                trySend(Unit)
+                            }
+                        }
+                    }
+                displayManager.registerDisplayListener(
+                    listener,
+                    null,
+                    DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS,
+                )
+
+                awaitClose { displayManager.unregisterDisplayListener(listener) }
+            }
+            .onStart { emit(Unit) }
+            .map { brightnessInfoValue() }
+            .flowOn(backgroundContext)
+            .stateIn(
+                applicationScope,
+                SharingStarted.WhileSubscribed(replayExpirationMillis = 0L),
+                null,
+            )
+
+    private suspend fun brightnessInfoValue(): BrightnessInfo? {
+        return withContext(backgroundContext) {
+            displayManager.getDisplay(displayId).brightnessInfo
+        }
+    }
+
+    override val minLinearBrightness =
+        brightnessInfo
+            .filterNotNull()
+            .map { LinearBrightness(it.brightnessMinimum) }
+            .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+
+    override val maxLinearBrightness =
+        brightnessInfo
+            .filterNotNull()
+            .map { LinearBrightness(it.brightnessMaximum) }
+            .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+
+    override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
+        val brightnessInfo = brightnessInfo.value ?: brightnessInfoValue()
+        val min = brightnessInfo?.brightnessMinimum ?: 0f
+        val max = brightnessInfo?.brightnessMaximum ?: 1f
+        return LinearBrightness(min) to LinearBrightness(max)
+    }
+
+    override val linearBrightness =
+        brightnessInfo
+            .filterNotNull()
+            .map { LinearBrightness(it.brightness) }
+            .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+
+    override fun setTemporaryBrightness(value: LinearBrightness) {
+        apiQueue.trySend(SetBrightnessMethod.Temporary(value))
+    }
+
+    override fun setBrightness(value: LinearBrightness) {
+        apiQueue.trySend(SetBrightnessMethod.Permanent(value))
+    }
+
+    private sealed interface SetBrightnessMethod {
+        val value: LinearBrightness
+        @JvmInline
+        value class Temporary(override val value: LinearBrightness) : SetBrightnessMethod
+        @JvmInline
+        value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractor.kt
new file mode 100644
index 0000000..fb00edf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractor.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.brightness.domain.interactor
+
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.brightness.data.repository.BrightnessPolicyRepository
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.utils.PolicyRestriction
+import javax.inject.Inject
+
+/** Interactor for enforcing the policy that may disallow brightness changing. */
+@SysUISingleton
+class BrightnessPolicyEnforcementInteractor
+@Inject
+constructor(
+    brightnessPolicyRepository: BrightnessPolicyRepository,
+    private val activityStarter: ActivityStarter,
+) {
+
+    /** Brightness policy restriction for the current user. */
+    val brightnessPolicyRestriction = brightnessPolicyRepository.restrictionPolicy
+
+    /**
+     * Starts the dialog with details about the current restriction for changing brightness. Should
+     * be triggered when a restricted user tries to change the brightness.
+     */
+    fun startAdminSupportDetailsDialog(restriction: PolicyRestriction.Restricted) {
+        val intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(restriction.admin)
+        activityStarter.postStartActivityDismissingKeyguard(intent, 0)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
new file mode 100644
index 0000000..799a0a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.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.brightness.domain.interactor
+
+import com.android.settingslib.display.BrightnessUtils
+import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.combine
+
+/**
+ * Converts between [GammaBrightness] and [LinearBrightness].
+ *
+ * @see BrightnessUtils
+ */
+@SysUISingleton
+class ScreenBrightnessInteractor
+@Inject
+constructor(
+    private val screenBrightnessRepository: ScreenBrightnessRepository,
+) {
+    /** Maximum value in the Gamma space for brightness */
+    val maxGammaBrightness = GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX)
+
+    /** Minimum value in the Gamma space for brightness */
+    val minGammaBrightness = GammaBrightness(BrightnessUtils.GAMMA_SPACE_MIN)
+
+    /**
+     * Brightness in the Gamma space for the current display. It will always represent a value
+     * between [minGammaBrightness] and [maxGammaBrightness]
+     */
+    val gammaBrightness =
+        with(screenBrightnessRepository) {
+            combine(
+                linearBrightness,
+                minLinearBrightness,
+                maxLinearBrightness,
+            ) { brightness, min, max ->
+                brightness.toGammaBrightness(min, max)
+            }
+        }
+
+    /** Sets the brightness temporarily, while the user is changing it. */
+    suspend fun setTemporaryBrightness(gammaBrightness: GammaBrightness) {
+        screenBrightnessRepository.setTemporaryBrightness(
+            gammaBrightness.clamp().toLinearBrightness()
+        )
+    }
+
+    /** Sets the brightness definitely. */
+    suspend fun setBrightness(gammaBrightness: GammaBrightness) {
+        screenBrightnessRepository.setBrightness(gammaBrightness.clamp().toLinearBrightness())
+    }
+
+    private suspend fun GammaBrightness.toLinearBrightness(): LinearBrightness {
+        val bounds = screenBrightnessRepository.getMinMaxLinearBrightness()
+        return LinearBrightness(
+            BrightnessUtils.convertGammaToLinearFloat(
+                value,
+                bounds.first.floatValue,
+                bounds.second.floatValue
+            )
+        )
+    }
+
+    private fun GammaBrightness.clamp(): GammaBrightness {
+        return GammaBrightness(value.coerceIn(minGammaBrightness.value, maxGammaBrightness.value))
+    }
+
+    private fun LinearBrightness.toGammaBrightness(
+        min: LinearBrightness,
+        max: LinearBrightness,
+    ): GammaBrightness {
+        return GammaBrightness(
+            BrightnessUtils.convertLinearToGammaFloat(floatValue, min.floatValue, max.floatValue)
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt
new file mode 100644
index 0000000..e20d003
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.shared
+
+import androidx.annotation.IntRange
+import com.android.settingslib.display.BrightnessUtils
+
+@JvmInline
+value class GammaBrightness(
+    @IntRange(
+        from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(),
+        to = BrightnessUtils.GAMMA_SPACE_MAX.toLong()
+    )
+    val value: Int
+)
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
new file mode 100644
index 0000000..c1be37a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.ui.compose
+
+import androidx.compose.animation.core.animateIntAsState
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import com.android.compose.PlatformSlider
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
+import com.android.systemui.brightness.ui.viewmodel.Drag
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.utils.PolicyRestriction
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+@Composable
+private fun BrightnessSlider(
+    gammaValue: Int,
+    valueRange: IntRange,
+    label: Text.Resource,
+    icon: Icon,
+    restriction: PolicyRestriction,
+    onRestrictedClick: (PolicyRestriction.Restricted) -> Unit,
+    onDrag: (Int) -> Unit,
+    onStop: (Int) -> Unit,
+    modifier: Modifier = Modifier,
+    formatter: (Int) -> String = { "$it" },
+) {
+    var value by remember(gammaValue) { mutableIntStateOf(gammaValue) }
+    val animatedValue by
+        animateIntAsState(targetValue = value, label = "BrightnessSliderAnimatedValue")
+    val floatValueRange = valueRange.first.toFloat()..valueRange.last.toFloat()
+    val isRestricted = restriction is PolicyRestriction.Restricted
+
+    PlatformSlider(
+        value = animatedValue.toFloat(),
+        valueRange = floatValueRange,
+        enabled = !isRestricted,
+        onValueChange = {
+            if (!isRestricted) {
+                value = it.toInt()
+                onDrag(value)
+            }
+        },
+        onValueChangeFinished = {
+            if (!isRestricted) {
+                onStop(value)
+            }
+        },
+        modifier =
+            modifier.clickable(
+                enabled = isRestricted,
+            ) {
+                if (restriction is PolicyRestriction.Restricted) {
+                    onRestrictedClick(restriction)
+                }
+            },
+        icon = { isDragging ->
+            if (isDragging) {
+                Text(text = formatter(value))
+            } else {
+                Icon(modifier = Modifier.size(24.dp), icon = icon)
+            }
+        },
+        label = {
+            Text(
+                text = stringResource(id = label.res),
+                style = MaterialTheme.typography.titleMedium,
+                maxLines = 1,
+            )
+        },
+    )
+}
+
+@Composable
+fun BrightnessSliderContainer(
+    viewModel: BrightnessSliderViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val gamma: Int by viewModel.currentBrightness.map { it.value }.collectAsState(initial = 0)
+    val coroutineScope = rememberCoroutineScope()
+    val restriction by
+        viewModel.policyRestriction.collectAsState(initial = PolicyRestriction.NoRestriction)
+
+    BrightnessSlider(
+        gammaValue = gamma,
+        valueRange = viewModel.minBrightness.value..viewModel.maxBrightness.value,
+        label = viewModel.label,
+        icon = viewModel.icon,
+        restriction = restriction,
+        onRestrictedClick = viewModel::showPolicyRestrictionDialog,
+        onDrag = { coroutineScope.launch { viewModel.onDrag(Drag.Dragging(GammaBrightness(it))) } },
+        onStop = { coroutineScope.launch { viewModel.onDrag(Drag.Stopped(GammaBrightness(it))) } },
+        modifier = modifier.fillMaxWidth(),
+        formatter = viewModel::formatValue,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
new file mode 100644
index 0000000..f0988ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.brightness.ui.viewmodel
+
+import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor
+import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor
+import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
+import com.android.systemui.utils.PolicyRestriction
+import javax.inject.Inject
+
+@SysUISingleton
+class BrightnessSliderViewModel
+@Inject
+constructor(
+    private val screenBrightnessInteractor: ScreenBrightnessInteractor,
+    private val brightnessPolicyEnforcementInteractor: BrightnessPolicyEnforcementInteractor,
+) {
+    val currentBrightness = screenBrightnessInteractor.gammaBrightness
+
+    val maxBrightness = screenBrightnessInteractor.maxGammaBrightness
+    val minBrightness = screenBrightnessInteractor.minGammaBrightness
+
+    val label = Text.Resource(R.string.quick_settings_brightness_dialog_title)
+
+    val icon = Icon.Resource(R.drawable.ic_brightness_full, ContentDescription.Resource(label.res))
+
+    val policyRestriction = brightnessPolicyEnforcementInteractor.brightnessPolicyRestriction
+
+    fun showPolicyRestrictionDialog(restriction: PolicyRestriction.Restricted) {
+        brightnessPolicyEnforcementInteractor.startAdminSupportDetailsDialog(restriction)
+    }
+
+    /**
+     * As a brightness slider is dragged, the corresponding events should be sent using this method.
+     */
+    suspend fun onDrag(drag: Drag) {
+        when (drag) {
+            is Drag.Dragging -> screenBrightnessInteractor.setTemporaryBrightness(drag.brightness)
+            is Drag.Stopped -> screenBrightnessInteractor.setBrightness(drag.brightness)
+        }
+    }
+
+    /**
+     * Format the current value of brightness as a percentage between the minimum and maximum gamma.
+     */
+    fun formatValue(value: Int): String {
+        val min = minBrightness.value
+        val max = maxBrightness.value
+        val coercedValue = value.coerceIn(min, max)
+        val percentage = (coercedValue - min) * 100 / (max - min)
+        // This is not finalized UI so using fixed string
+        return "$percentage%"
+    }
+}
+
+/** Represents a drag event in a brightness slider. */
+sealed interface Drag {
+    val brightness: GammaBrightness
+    @JvmInline value class Dragging(override val brightness: GammaBrightness) : Drag
+    @JvmInline value class Stopped(override val brightness: GammaBrightness) : Drag
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1ed4b50..7d86e06 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.bouncer.data.repository.BouncerRepositoryModule;
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractorModule;
 import com.android.systemui.bouncer.ui.BouncerViewModule;
+import com.android.systemui.brightness.dagger.ScreenBrightnessModule;
 import com.android.systemui.classifier.FalsingModule;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
 import com.android.systemui.common.data.CommonDataLayerModule;
@@ -229,6 +230,7 @@
         RecordIssueModule.class,
         ReferenceModule.class,
         RetailModeModule.class,
+        ScreenBrightnessModule.class,
         ScreenshotModule.class,
         SensorModule.class,
         SecurityRepositoryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 926f7f1..75c50fd 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -19,6 +19,7 @@
 import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING;
 import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING;
 import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION;
+import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -81,6 +82,7 @@
     private final LockPatternUtils mLockPatternUtils;
     private final UserTracker mUserTracker;
     private final float mBouncerZoneScreenPercentage;
+    private final float mMinBouncerZoneScreenPercentage;
 
     private final ScrimManager mScrimManager;
     private ScrimController mCurrentScrimController;
@@ -222,6 +224,7 @@
             @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING)
                     FlingAnimationUtils flingAnimationUtilsClosing,
             @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
+            @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
             UiEventLogger uiEventLogger) {
         mCentralSurfaces = centralSurfaces;
         mScrimManager = scrimManager;
@@ -229,6 +232,7 @@
         mLockPatternUtils = lockPatternUtils;
         mUserTracker = userTracker;
         mBouncerZoneScreenPercentage = swipeRegionPercentage;
+        mMinBouncerZoneScreenPercentage = minRegionPercentage;
         mFlingAnimationUtils = flingAnimationUtils;
         mFlingAnimationUtilsClosing = flingAnimationUtilsClosing;
         mValueAnimatorCreator = valueAnimatorCreator;
@@ -237,24 +241,27 @@
     }
 
     @Override
-    public void getTouchInitiationRegion(Rect bounds, Region region) {
+    public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
         final int width = bounds.width();
         final int height = bounds.height();
+        final float minBouncerHeight = height * mMinBouncerZoneScreenPercentage;
+        final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage));
 
-        if (mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) {
-            region.op(new Rect(0, 0, width,
-                            Math.round(
-                                    height * mBouncerZoneScreenPercentage)),
-                    Region.Op.UNION);
-        } else {
-            region.op(new Rect(0,
-                            Math.round(height * (1 - mBouncerZoneScreenPercentage)),
-                            width,
-                            height),
-                    Region.Op.UNION);
+        final boolean isBouncerShowing =
+                mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false);
+        final Rect normalRegion = isBouncerShowing
+                ? new Rect(0, 0, width, Math.round(height * mBouncerZoneScreenPercentage))
+                : new Rect(0, Math.round(height * (1 - mBouncerZoneScreenPercentage)),
+                        width, height);
+
+        if (!isBouncerShowing && exclusionRect != null) {
+            int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom);
+            normalRegion.top = Math.max(normalRegion.top, lowestBottom);
         }
+        region.union(normalRegion);
     }
 
+
     @Override
     public void onSessionStart(TouchSession session) {
         mVelocityTracker = mVelocityTrackerFactory.obtain();
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
index e5c705f..13588c2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java
@@ -87,7 +87,7 @@
     }
 
     @Override
-    public void getTouchInitiationRegion(Rect bounds, Region region) {
+    public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
         final Rect outBounds = new Rect(bounds);
         outBounds.inset(outBounds.width() - mInitiationWidth, 0, 0, 0);
         region.op(outBounds, Region.Op.UNION);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 55a9c0c..3b22b31 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -18,9 +18,15 @@
 
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import static com.android.systemui.shared.Flags.bouncerAreaExclusion;
+
 import android.graphics.Rect;
 import android.graphics.Region;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.GestureDetector;
+import android.view.ISystemGestureExclusionListener;
+import android.view.IWindowManager;
 import android.view.InputEvent;
 import android.view.MotionEvent;
 
@@ -31,6 +37,8 @@
 import androidx.lifecycle.LifecycleObserver;
 import androidx.lifecycle.LifecycleOwner;
 
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.touch.dagger.InputSessionComponent;
 import com.android.systemui.shared.system.InputChannelCompat;
@@ -58,8 +66,23 @@
 public class DreamOverlayTouchMonitor {
     // This executor is used to protect {@code mActiveTouchSessions} from being modified
     // concurrently. Any operation that adds or removes values should use this executor.
-    private final Executor mExecutor;
+    public String TAG = "DreamOverlayTouchMonitor";
+    private final Executor mMainExecutor;
+    private final Executor mBackgroundExecutor;
     private final Lifecycle mLifecycle;
+    private Rect mExclusionRect = null;
+
+    private ISystemGestureExclusionListener mGestureExclusionListener =
+            new ISystemGestureExclusionListener.Stub() {
+                @Override
+                public void onSystemGestureExclusionChanged(int displayId,
+                        Region systemGestureExclusion,
+                        Region systemGestureExclusionUnrestricted) {
+                    mExclusionRect = systemGestureExclusion.getBounds();
+                }
+            };
+
+
 
     /**
      * Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures.
@@ -67,7 +90,7 @@
     private ListenableFuture<DreamTouchHandler.TouchSession> push(
             TouchSessionImpl touchSessionImpl) {
         return CallbackToFutureAdapter.getFuture(completer -> {
-            mExecutor.execute(() -> {
+            mMainExecutor.execute(() -> {
                 if (!mActiveTouchSessions.remove(touchSessionImpl)) {
                     completer.set(null);
                     return;
@@ -90,7 +113,7 @@
     private ListenableFuture<DreamTouchHandler.TouchSession> pop(
             TouchSessionImpl touchSessionImpl) {
         return CallbackToFutureAdapter.getFuture(completer -> {
-            mExecutor.execute(() -> {
+            mMainExecutor.execute(() -> {
                 if (mActiveTouchSessions.remove(touchSessionImpl)) {
                     touchSessionImpl.onRemoved();
 
@@ -240,6 +263,17 @@
      */
     private void startMonitoring() {
         stopMonitoring(true);
+        if (bouncerAreaExclusion()) {
+            mBackgroundExecutor.execute(() -> {
+                try {
+                    mWindowManagerService.registerSystemGestureExclusionListener(
+                            mGestureExclusionListener, mDisplayId);
+                } catch (RemoteException e) {
+                    // Handle the exception
+                    Log.e(TAG, "Failed to register gesture exclusion listener", e);
+                }
+            });
+        }
         mCurrentInputSession = mInputSessionFactory.create(
                 "dreamOverlay",
                 mInputEventListener,
@@ -252,6 +286,18 @@
      * Destroys any active {@link InputSession}.
      */
     private void stopMonitoring(boolean force) {
+        mExclusionRect = null;
+        if (bouncerAreaExclusion()) {
+            mBackgroundExecutor.execute(() -> {
+                try {
+                    mWindowManagerService.unregisterSystemGestureExclusionListener(
+                            mGestureExclusionListener, mDisplayId);
+                } catch (RemoteException e) {
+                    // Handle the exception
+                    Log.e(TAG, "unregisterSystemGestureExclusionListener: failed", e);
+                }
+            });
+        }
         if (mCurrentInputSession == null) {
             return;
         }
@@ -263,7 +309,7 @@
 
         // When we stop monitoring touches, we must ensure that all active touch sessions and
         // descendants informed of the removal so any cleanup for active tracking can proceed.
-        mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
+        mMainExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
             while (touchSession != null) {
                 touchSession.onRemoved();
                 touchSession = touchSession.getPredecessor();
@@ -295,11 +341,15 @@
                             if (!handler.isEnabled()) {
                                 continue;
                             }
-                    final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(),
-                            TYPE_APPLICATION_OVERLAY);
-
-                    final Region initiationRegion = Region.obtain();
-                    handler.getTouchInitiationRegion(maxBounds, initiationRegion);
+                            final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(),
+                                    TYPE_APPLICATION_OVERLAY);
+                            final Region initiationRegion = Region.obtain();
+                            Rect exclusionRect = null;
+                            if (bouncerAreaExclusion()) {
+                                exclusionRect = getCurrentExclusionRect();
+                            }
+                            handler.getTouchInitiationRegion(
+                                            maxBounds, initiationRegion, exclusionRect);
 
                     if (!initiationRegion.isEmpty()) {
                         // Initiation regions require a motion event to determine pointer location
@@ -335,6 +385,9 @@
                     .flatMap(Collection::stream)
                     .forEach(inputEventListener -> inputEventListener.onInputEvent(ev));
         }
+                    private Rect getCurrentExclusionRect() {
+                        return mExclusionRect;
+                    }
     };
 
     /**
@@ -416,6 +469,9 @@
 
     private InputSessionComponent.Factory mInputSessionFactory;
     private InputSession mCurrentInputSession;
+    private final int mDisplayId;
+    private final IWindowManager mWindowManagerService;
+
 
     /**
      * Designated constructor for {@link DreamOverlayTouchMonitor}
@@ -432,15 +488,21 @@
     @Inject
     public DreamOverlayTouchMonitor(
             @Main Executor executor,
+            @Background Executor backgroundExecutor,
             Lifecycle lifecycle,
             InputSessionComponent.Factory inputSessionFactory,
             DisplayHelper displayHelper,
-            Set<DreamTouchHandler> handlers) {
+            Set<DreamTouchHandler> handlers,
+            IWindowManager windowManagerService,
+            @DisplayId int displayId) {
+        mDisplayId = displayId;
         mHandlers = handlers;
         mInputSessionFactory = inputSessionFactory;
-        mExecutor = executor;
+        mMainExecutor = executor;
+        mBackgroundExecutor = backgroundExecutor;
         mLifecycle = lifecycle;
         mDisplayHelper = displayHelper;
+        mWindowManagerService = windowManagerService;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
index 72ad45d..1ec0008 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java
@@ -104,7 +104,7 @@
      * indicating the entire screen should be considered.
      * @param region A {@link Region} that is passed in to the target entry touch region.
      */
-    default void getTouchInitiationRegion(Rect bounds, Region region) {
+    default void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
index 6f05e83..e0bf52e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java
@@ -82,7 +82,7 @@
     }
 
     @Override
-    public void getTouchInitiationRegion(Rect bounds, Region region) {
+    public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) {
         final Rect outBounds = new Rect(bounds);
         outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight);
         region.op(outBounds, Region.Op.UNION);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
index 8cf11a9..a5db2ff 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -21,10 +21,10 @@
 import android.util.TypedValue;
 import android.view.VelocityTracker;
 
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
 import com.android.systemui.dreams.touch.DreamTouchHandler;
+import com.android.systemui.res.R;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 
@@ -46,6 +46,9 @@
      */
     public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region";
 
+    public static final String MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE =
+            "min_bouncer_zone_screen_percentage";
+
     /**
      * The {@link android.view.animation.AnimationUtils} for animating the bouncer closing.
      */
@@ -110,6 +113,18 @@
     }
 
     /**
+     * Provides the minimum region to start wipe gestures from.
+     */
+    @Provides
+    @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE)
+    public static float providesMinBouncerZoneScreenPercentage(@Main Resources resources) {
+        TypedValue typedValue = new TypedValue();
+        resources.getValue(R.dimen.dream_overlay_bouncer_min_region_screen_percentage,
+                typedValue, true);
+        return typedValue.getFloat();
+    }
+
+    /**
      * Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply
      * a wrapper around {@link ValueAnimator}.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fb0d225..654610e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2153,13 +2153,6 @@
         mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_TRANSITION_FROM_AOD);
 
         synchronized (KeyguardViewMediator.this) {
-            if (mHiding && isOccluded) {
-                // We're in the process of going away but WindowManager wants to show a
-                // SHOW_WHEN_LOCKED activity instead.
-                // TODO(bc-unlock): Migrate to remote animation.
-                startKeyguardExitAnimation(0, 0);
-            }
-
             mPowerGestureIntercepted =
                     isOccluded && mUpdateMonitor.isSecureCameraLaunchedOverKeyguard();
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
index e017129..bf1f074 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -26,6 +26,9 @@
 import com.android.systemui.keyguard.data.repository.BiometricType
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.Scenes
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -33,6 +36,7 @@
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.launch
@@ -48,6 +52,8 @@
     @Application private val applicationScope: CoroutineScope,
     @Application private val context: Context,
     deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val sceneContainerFlags: SceneContainerFlags,
+    private val sceneInteractor: SceneInteractor,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
     alternateBouncerInteractor: AlternateBouncerInteractor,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -65,16 +71,35 @@
         }
     }
 
+    private val isSideFpsIndicatorOnPrimaryBouncerEnabled: Boolean
+        get() = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
+
+    private val isBouncerSceneActive: Flow<Boolean> =
+        if (sceneContainerFlags.isEnabled()) {
+            sceneInteractor.currentScene.map { it == Scenes.Bouncer }.distinctUntilChanged()
+        } else {
+            flowOf(false)
+        }
+
     private val showIndicatorForPrimaryBouncer: Flow<Boolean> =
         merge(
+                // Legacy bouncer visibility changes.
                 primaryBouncerInteractor.isShowing,
                 primaryBouncerInteractor.startingToHide,
                 primaryBouncerInteractor.startingDisappearAnimation.filterNotNull(),
+                // Bouncer scene visibility changes.
+                isBouncerSceneActive,
                 deviceEntryFingerprintAuthRepository.shouldUpdateIndicatorVisibility.filter { it }
             )
-            .map { shouldShowIndicatorForPrimaryBouncer() }
+            .map {
+                isBouncerActive() &&
+                    isSideFpsIndicatorOnPrimaryBouncerEnabled &&
+                    keyguardUpdateMonitor.isFingerprintDetectionRunning &&
+                    keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+            }
 
     private val showIndicatorForAlternateBouncer: Flow<Boolean> =
+        // Note: this interactor internally verifies that SideFPS is enabled and running.
         alternateBouncerInteractor.isVisible
 
     /**
@@ -89,16 +114,11 @@
             }
             .distinctUntilChanged()
 
-    private fun shouldShowIndicatorForPrimaryBouncer(): Boolean {
-        val sfpsEnabled: Boolean =
-            context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
-        val sfpsDetectionRunning = keyguardUpdateMonitor.isFingerprintDetectionRunning
-        val isUnlockingWithFpAllowed = keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
-
+    private fun isBouncerActive(): Boolean {
+        if (sceneContainerFlags.isEnabled()) {
+            return sceneInteractor.currentScene.value == Scenes.Bouncer
+        }
         return primaryBouncerInteractor.isBouncerShowing() &&
-            sfpsEnabled &&
-            sfpsDetectionRunning &&
-            isUnlockingWithFpAllowed &&
             !primaryBouncerInteractor.isAnimatingAway()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index da9e00d..e861ddf 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.mediaprojection.permission;
 
+import static android.Manifest.permission.LOG_COMPAT_CHANGE;
+import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG;
 import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT;
 import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT;
+import static android.media.projection.MediaProjectionManager.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION;
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL;
 import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -26,11 +29,13 @@
 import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP;
 
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.ActivityOptions.LaunchCookie;
 import android.app.AlertDialog;
 import android.app.StatusBarManager;
+import android.app.compat.CompatChanges;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -108,6 +113,7 @@
     }
 
     @Override
+    @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE})
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
@@ -235,6 +241,10 @@
         // the correct screen width when in split screen.
         Context dialogContext = getApplicationContext();
         if (isPartialScreenSharingEnabled()) {
+            final boolean overrideDisableSingleAppOption =
+                    CompatChanges.isChangeEnabled(
+                            OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION,
+                            mPackageName, getHostUserHandle());
             MediaProjectionPermissionDialogDelegate delegate =
                     new MediaProjectionPermissionDialogDelegate(
                             dialogContext,
@@ -246,6 +256,7 @@
                             },
                             () -> finish(RECORD_CANCEL, /* projection= */ null),
                             appName,
+                            overrideDisableSingleAppOption,
                             mUid,
                             mMediaProjectionMetricsLogger);
             mDialog =
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
index 0f54e93..8858041a 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt
@@ -30,11 +30,12 @@
     private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>,
     private val onCancelClicked: Runnable,
     private val appName: String?,
+    private val forceShowPartialScreenshare: Boolean,
     hostUid: Int,
     mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
 ) :
     BaseMediaProjectionPermissionDialogDelegate<AlertDialog>(
-        createOptionList(context, appName, mediaProjectionConfig),
+        createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare),
         appName,
         hostUid,
         mediaProjectionMetricsLogger
@@ -65,7 +66,8 @@
         private fun createOptionList(
             context: Context,
             appName: String?,
-            mediaProjectionConfig: MediaProjectionConfig?
+            mediaProjectionConfig: MediaProjectionConfig?,
+            overrideDisableSingleAppOption: Boolean = false,
         ): List<ScreenShareOption> {
             val singleAppWarningText =
                 if (appName == null) {
@@ -80,8 +82,13 @@
                     R.string.media_projection_entry_app_permission_dialog_warning_entire_screen
                 }
 
+            // The single app option should only be disabled if there is an app name provided,
+            // the client has setup a MediaProjection with
+            // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by
+            // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
             val singleAppOptionDisabled =
                 appName != null &&
+                    !overrideDisableSingleAppOption &&
                     mediaProjectionConfig?.regionToCapture ==
                         MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt b/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt
new file mode 100644
index 0000000..8af5665
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/flags/NewQsUI.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.flags
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification avalanche suppression flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NewQsUI {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_QS_UI_REFACTOR
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.qsUiRefactor()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
new file mode 100644
index 0000000..a3c2cbb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class QuickSettingsContainerViewModel
+@Inject
+constructor(
+    val brightnessSliderViewModel: BrightnessSliderViewModel,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
new file mode 100644
index 0000000..0344b32
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the heads-up cycling flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationHeadsUpCycling {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_HEADS_UP_CYCLING
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the heads-up cycling animation enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationContentAlphaOptimization()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index afd2415..5f26702 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -662,7 +662,12 @@
          * show if any subsequent events are to be handled.
          */
         if (beginShowingBouncer(event)) {
-            mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
+            if (SceneContainerFlag.isEnabled()) {
+                mSceneInteractorLazy.get().changeScene(
+                        Scenes.Bouncer, "StatusBarKeyguardViewManager.onPanelExpansionChanged");
+            } else {
+                mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
+            }
         }
 
         if (!primaryBouncerIsOrWillBeShowing()) {
@@ -716,7 +721,12 @@
             // The keyguard might be showing (already). So we need to hide it.
             if (!primaryBouncerIsShowing()) {
                 mCentralSurfaces.hideKeyguard();
-                mPrimaryBouncerInteractor.show(true);
+                if (SceneContainerFlag.isEnabled()) {
+                    mSceneInteractorLazy.get().changeScene(
+                            Scenes.Bouncer, "StatusBarKeyguardViewManager.showBouncerOrKeyguard");
+                } else {
+                    mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+                }
             } else {
                 Log.e(TAG, "Attempted to show the sim bouncer when it is already showing.");
             }
@@ -778,7 +788,12 @@
     public void showPrimaryBouncer(boolean scrimmed) {
         hideAlternateBouncer(false);
         if (mKeyguardStateController.isShowing() && !isBouncerShowing()) {
-            mPrimaryBouncerInteractor.show(scrimmed);
+            if (SceneContainerFlag.isEnabled()) {
+                mSceneInteractorLazy.get().changeScene(
+                        Scenes.Bouncer, "StatusBarKeyguardViewManager.showPrimaryBouncer");
+            } else {
+                mPrimaryBouncerInteractor.show(scrimmed);
+            }
         }
         updateStates();
     }
@@ -873,13 +888,23 @@
                 if (afterKeyguardGone) {
                     // we'll handle the dismiss action after keyguard is gone, so just show the
                     // bouncer
-                    mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
+                    if (SceneContainerFlag.isEnabled()) {
+                        mSceneInteractorLazy.get().changeScene(
+                                Scenes.Bouncer, "StatusBarKeyguardViewManager.dismissWithAction");
+                    } else {
+                        mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+                    }
                 } else {
                     // after authentication success, run dismiss action with the option to defer
                     // hiding the keyguard based on the return value of the OnDismissAction
                     mPrimaryBouncerInteractor.setDismissAction(
                             mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
-                    mPrimaryBouncerInteractor.show(/* isScrimmed= */true);
+                    if (SceneContainerFlag.isEnabled()) {
+                        mSceneInteractorLazy.get().changeScene(
+                                Scenes.Bouncer, "StatusBarKeyguardViewManager.dismissWithAction");
+                    } else {
+                        mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+                    }
                     // bouncer will handle the dismiss action, so we no longer need to track it here
                     mAfterKeyguardGoneAction = null;
                     mKeyguardGoneCancelAction = null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index 55a0f59..9cdecef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -17,9 +17,12 @@
 
 import android.util.Log
 import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
 import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry
+import java.io.PrintWriter
 import javax.inject.Inject
 
 /*
@@ -27,7 +30,9 @@
  * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager
  */
 @SysUISingleton
-class AvalancheController @Inject constructor() {
+class AvalancheController @Inject constructor(
+    dumpManager: DumpManager,
+) : Dumpable {
 
     private val tag = "AvalancheController"
     private val debug = false
@@ -54,22 +59,26 @@
     // For debugging only
     @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
 
-    /**
-     * Run or delay Runnable for given HeadsUpEntry
-     */
-    fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) {
+    init {
+        dumpManager.registerNormalDumpable(tag, /* module */ this)
+    }
+
+    /** Run or delay Runnable for given HeadsUpEntry */
+    fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) {
         if (!NotificationThrottleHun.isEnabled) {
             runnable.run()
             return
         }
         val fn = "[$label] => AvalancheController.update ${getKey(entry)}"
-
+        if (entry == null) {
+            log { "Entry is NULL, stop update." }
+            return;
+        }
         if (debug) {
             debugRunnableLabelMap[runnable] = label
         }
-
         if (isShowing(entry)) {
-            log {"$fn => [update showing]" }
+            log { "$fn => [update showing]" }
             runnable.run()
         } else if (entry in nextMap) {
             log { "$fn => [update next]" }
@@ -164,9 +173,7 @@
         }
     }
 
-    /**
-     * Return true if entry is waiting to show.
-     */
+    /** Return true if entry is waiting to show. */
     fun isWaiting(key: String): Boolean {
         if (!NotificationThrottleHun.isEnabled) {
             return false
@@ -179,9 +186,7 @@
         return false
     }
 
-    /**
-     * Return list of keys for huns waiting
-     */
+    /** Return list of keys for huns waiting */
     fun getWaitingKeys(): MutableList<String> {
         if (!NotificationThrottleHun.isEnabled) {
             return mutableListOf()
@@ -254,12 +259,15 @@
         }
     }
 
-    // TODO(b/315362456) expose as dumpable for bugreports
+    private fun getStateStr(): String {
+        return "SHOWING: ${getKey(headsUpEntryShowing)}" +
+            "\tNEXT LIST: $nextListStr\tMAP: $nextMapStr" +
+            "\tDROP: $dropSetStr"
+    }
+
     private fun logState(reason: String) {
-        log { "state $reason" }
-        log { "showing: " + getKey(headsUpEntryShowing) }
-        log { "next list: $nextListStr map: $nextMapStr" }
-        log { "drop: $dropSetStr" }
+        log { "REASON $reason" }
+        log { getStateStr() }
     }
 
     private val dropSetStr: String
@@ -298,4 +306,8 @@
         }
         return entry.mEntry!!.key
     }
+
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("AvalancheController: ${getStateStr()}")
+    }
 }
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 7a57027..988564a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt
@@ -14,6 +14,7 @@
 
 package com.android.systemui.statusbar.policy
 
+import android.os.UserManager
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -47,6 +48,7 @@
 import com.android.systemui.qs.tiles.impl.work.domain.model.WorkModeTileModel
 import com.android.systemui.qs.tiles.impl.work.ui.WorkModeTileMapper
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy
 import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.res.R
@@ -120,6 +122,13 @@
                         labelRes = R.string.quick_settings_location_label,
                     ),
                 instanceId = uiEventLogger.getNewInstanceId(),
+                policy =
+                    QSTilePolicy.Restricted(
+                        listOf(
+                            UserManager.DISALLOW_SHARE_LOCATION,
+                            UserManager.DISALLOW_CONFIG_LOCATION
+                        )
+                    )
             )
 
         /** Inject LocationTile into tileViewModelMap in QSModule */
diff --git a/packages/SystemUI/src/com/android/systemui/utils/PolicyRestriction.kt b/packages/SystemUI/src/com/android/systemui/utils/PolicyRestriction.kt
new file mode 100644
index 0000000..38c6d7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/utils/PolicyRestriction.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.utils
+
+import com.android.settingslib.RestrictedLockUtils
+
+/**
+ * Models a possible policy restriction.
+ *
+ * @see RestrictedLockUtils.checkIfRestrictionEnforced
+ */
+sealed interface PolicyRestriction {
+    data object NoRestriction : PolicyRestriction
+
+    data class Restricted(val admin: RestrictedLockUtils.EnforcedAdmin) : PolicyRestriction
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
index b0c8a4a..dc73344 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt
@@ -23,7 +23,7 @@
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
 import com.android.settingslib.volume.data.repository.stateChanges
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index ea4c082..eebb6fb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.animation.DialogTransitionAnimator
 import com.android.systemui.animation.Expandable
 import com.android.systemui.media.dialog.MediaOutputDialogManager
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import javax.inject.Inject
 
@@ -33,10 +33,10 @@
     private val mediaOutputDialogManager: MediaOutputDialogManager,
 ) {
 
-    fun onBarClick(session: MediaDeviceSession, isPlaybackActive: Boolean, expandable: Expandable) {
-        if (isPlaybackActive) {
+    fun onBarClick(sessionWithPlayback: SessionWithPlayback?, expandable: Expandable) {
+        if (sessionWithPlayback?.playback?.isActive == true) {
             mediaOutputDialogManager.createAndShowWithController(
-                session.packageName,
+                sessionWithPlayback.session.packageName,
                 false,
                 expandable.dialogController()
             )
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index e60139e..41ad035 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -25,8 +25,8 @@
 import com.android.settingslib.volume.data.repository.MediaControllerRepository
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt
index ddc0784..22c160d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSessions.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.volume.panel.component.mediaoutput.domain.model
 
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+
 /** Models a pair of local and remote [MediaDeviceSession]s. */
 data class MediaDeviceSessions(
     val local: MediaDeviceSession?,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/MediaDeviceSession.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
rename to packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/MediaDeviceSession.kt
index 2a2ce79..eca3315 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaDeviceSession.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/MediaDeviceSession.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.volume.panel.component.mediaoutput.domain.model
+package com.android.systemui.volume.panel.component.mediaoutput.shared.model
 
 import android.media.session.MediaSession
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/SessionWithPlayback.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/SessionWithPlayback.kt
new file mode 100644
index 0000000..c4476fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/shared/model/SessionWithPlayback.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume.panel.component.mediaoutput.shared.model
+
+import android.media.session.PlaybackState
+
+data class SessionWithPlayback(
+    val session: MediaDeviceSession,
+    val playback: PlaybackState,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 2530a3a..fc9602e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel
 
 import android.content.Context
-import android.media.session.PlaybackState
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Color
 import com.android.systemui.common.shared.model.Icon
@@ -25,9 +24,8 @@
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlayback
 import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
-import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -47,7 +45,6 @@
 constructor(
     private val context: Context,
     @VolumePanelScope private val coroutineScope: CoroutineScope,
-    private val volumePanelViewModel: VolumePanelViewModel,
     private val actionsInteractor: MediaOutputActionsInteractor,
     private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
     interactor: MediaOutputInteractor,
@@ -129,14 +126,6 @@
             )
 
     fun onBarClick(expandable: Expandable) {
-        sessionWithPlayback.value?.let {
-            actionsInteractor.onBarClick(it.session, it.playback.isActive, expandable)
-        }
-        volumePanelViewModel.dismissPanel()
+        actionsInteractor.onBarClick(sessionWithPlayback.value, expandable)
     }
-
-    private data class SessionWithPlayback(
-        val session: MediaDeviceSession,
-        val playback: PlaybackState,
-    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
index 73c8bbf..8d8fa17 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/CastVolumeSliderViewModel.kt
@@ -21,7 +21,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.res.R
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
 import com.android.systemui.volume.panel.component.volume.domain.interactor.VolumeSliderInteractor
 import dagger.assisted.Assisted
 import dagger.assisted.AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 4e9a456..09e56c1 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -20,8 +20,8 @@
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSession
-import com.android.systemui.volume.panel.component.mediaoutput.domain.model.isTheSameSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.isTheSameSession
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.AudioStreamSliderViewModel
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.CastVolumeSliderViewModel
 import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index d4a0c8f..30c5e6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -77,6 +77,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.phone.dozeServiceHost
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -85,7 +87,6 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import java.util.Optional
@@ -238,6 +239,8 @@
                 testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
+                kosmos.fakeSceneContainerFlags,
+                kosmos.sceneInteractor,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 keyguardUpdateMonitor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index ae20c70..1acb203 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -75,6 +75,8 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.phone.dozeServiceHost
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -233,6 +235,8 @@
                 testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
+                kosmos.fakeSceneContainerFlags,
+                kosmos.sceneInteractor,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 keyguardUpdateMonitor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 1c6f251..a127631 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -32,6 +32,7 @@
 import android.testing.AndroidTestingRunner;
 import android.util.Pair;
 import android.view.GestureDetector;
+import android.view.IWindowManager;
 import android.view.InputEvent;
 import android.view.MotionEvent;
 
@@ -83,11 +84,14 @@
         private final GestureDetector.OnGestureListener mGestureListener;
         private final DisplayHelper mDisplayHelper;
         private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+        private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
         private final Rect mDisplayBounds = Mockito.mock(Rect.class);
+        private final IWindowManager mIWindowManager;
 
         Environment(Set<DreamTouchHandler> handlers) {
             mLifecycle = Mockito.mock(Lifecycle.class);
             mLifecycleOwner = Mockito.mock(LifecycleOwner.class);
+            mIWindowManager = Mockito.mock(IWindowManager.class);
 
             mInputFactory = Mockito.mock(InputSessionComponent.Factory.class);
             final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class);
@@ -100,8 +104,8 @@
             mDisplayHelper = Mockito.mock(DisplayHelper.class);
             when(mDisplayHelper.getMaxBounds(anyInt(), anyInt()))
                     .thenReturn(mDisplayBounds);
-            mMonitor = new DreamOverlayTouchMonitor(mExecutor, mLifecycle, mInputFactory,
-                    mDisplayHelper, handlers);
+            mMonitor = new DreamOverlayTouchMonitor(mExecutor, mBackgroundExecutor,
+                    mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0);
             mMonitor.init();
 
             final ArgumentCaptor<LifecycleObserver> lifecycleObserverCaptor =
@@ -163,7 +167,8 @@
         environment.publishInputEvent(initialEvent);
 
         // Verify display bounds passed into TouchHandler#getTouchInitiationRegion
-        verify(touchHandler).getTouchInitiationRegion(eq(environment.getDisplayBounds()), any());
+        verify(touchHandler).getTouchInitiationRegion(
+                eq(environment.getDisplayBounds()), any(), any());
         final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
                 ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
         verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
@@ -182,7 +187,7 @@
             final Region region = (Region) invocation.getArguments()[1];
             region.set(touchArea);
             return null;
-        }).when(touchHandler).getTouchInitiationRegion(any(), any());
+        }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());
 
         final Environment environment = new Environment(Stream.of(touchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -211,7 +216,7 @@
             final Region region = (Region) invocation.getArguments()[1];
             region.set(touchArea);
             return null;
-        }).when(touchHandler).getTouchInitiationRegion(any(), any());
+        }).when(touchHandler).getTouchInitiationRegion(any(), any(), any());
 
         final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler)
                 .collect(Collectors.toCollection(HashSet::new)));
@@ -264,7 +269,7 @@
 
         // Make sure there is no active session.
         verify(touchHandler, never()).onSessionStart(any());
-        verify(touchHandler, never()).getTouchInitiationRegion(any(), any());
+        verify(touchHandler, never()).getTouchInitiationRegion(any(), any(), any());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 3b4f683..9266af4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -36,16 +36,20 @@
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -69,6 +73,8 @@
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
 
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
     private val bouncerRepository = FakeKeyguardBouncerRepository()
     private val biometricSettingsRepository = FakeBiometricSettingsRepository()
     private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
@@ -78,11 +84,11 @@
 
     private lateinit var underTest: DeviceEntrySideFpsOverlayInteractor
 
-    private val testScope = TestScope(StandardTestDispatcher())
-
     @Before
     fun setup() {
         mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
+        kosmos.fakeSceneContainerFlags.enabled = false
+
         primaryBouncerInteractor =
             PrimaryBouncerInteractor(
                 bouncerRepository,
@@ -100,6 +106,7 @@
                 mSelectedUserInteractor,
                 faceAuthInteractor
             )
+
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
                 mock(StatusBarStateController::class.java),
@@ -114,11 +121,14 @@
                 { mock(KeyguardTransitionInteractor::class.java) },
                 testScope.backgroundScope,
             )
+
         underTest =
             DeviceEntrySideFpsOverlayInteractor(
                 testScope.backgroundScope,
                 mContext,
                 deviceEntryFingerprintAuthRepository,
+                kosmos.fakeSceneContainerFlags,
+                kosmos.sceneInteractor,
                 primaryBouncerInteractor,
                 alternateBouncerInteractor,
                 keyguardUpdateMonitor
@@ -138,7 +148,7 @@
                 fpsDetectionRunning = true,
                 isUnlockingWithFpAllowed = true
             )
-            assertThat(showIndicatorForDeviceEntry).isEqualTo(true)
+            assertThat(showIndicatorForDeviceEntry).isTrue()
         }
 
     @Test
@@ -154,7 +164,63 @@
                 fpsDetectionRunning = true,
                 isUnlockingWithFpAllowed = true
             )
-            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+            assertThat(showIndicatorForDeviceEntry).isFalse()
+        }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_onBouncerSceneActive() =
+        testScope.runTest {
+            kosmos.fakeSceneContainerFlags.enabled = true
+            underTest =
+                DeviceEntrySideFpsOverlayInteractor(
+                    testScope.backgroundScope,
+                    mContext,
+                    deviceEntryFingerprintAuthRepository,
+                    kosmos.fakeSceneContainerFlags,
+                    kosmos.sceneInteractor,
+                    primaryBouncerInteractor,
+                    alternateBouncerInteractor,
+                    keyguardUpdateMonitor
+                )
+
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            updateBouncerScene(
+                isActive = true,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            assertThat(showIndicatorForDeviceEntry).isTrue()
+        }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_onBouncerSceneInactive() =
+        testScope.runTest {
+            kosmos.fakeSceneContainerFlags.enabled = true
+            underTest =
+                DeviceEntrySideFpsOverlayInteractor(
+                    testScope.backgroundScope,
+                    mContext,
+                    deviceEntryFingerprintAuthRepository,
+                    kosmos.fakeSceneContainerFlags,
+                    kosmos.sceneInteractor,
+                    primaryBouncerInteractor,
+                    alternateBouncerInteractor,
+                    keyguardUpdateMonitor
+                )
+
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            updateBouncerScene(
+                isActive = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            assertThat(showIndicatorForDeviceEntry).isFalse()
         }
 
     @Test
@@ -170,7 +236,7 @@
                 fpsDetectionRunning = false,
                 isUnlockingWithFpAllowed = true
             )
-            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+            assertThat(showIndicatorForDeviceEntry).isFalse()
         }
     }
 
@@ -187,7 +253,39 @@
                 fpsDetectionRunning = true,
                 isUnlockingWithFpAllowed = false
             )
-            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+            assertThat(showIndicatorForDeviceEntry).isFalse()
+        }
+    }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_fromBouncerScene_whenFpsDetectionNotRunning() {
+        testScope.runTest {
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            updateBouncerScene(
+                isActive = true,
+                fpsDetectionRunning = false,
+                isUnlockingWithFpAllowed = true
+            )
+            assertThat(showIndicatorForDeviceEntry).isFalse()
+        }
+    }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_fromBouncerScene_onUnlockingWithFpDisallowed() {
+        testScope.runTest {
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            updateBouncerScene(
+                isActive = true,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = false
+            )
+            assertThat(showIndicatorForDeviceEntry).isFalse()
         }
     }
 
@@ -204,7 +302,7 @@
                 fpsDetectionRunning = true,
                 isUnlockingWithFpAllowed = true
             )
-            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+            assertThat(showIndicatorForDeviceEntry).isFalse()
         }
     }
 
@@ -216,10 +314,10 @@
             runCurrent()
 
             bouncerRepository.setAlternateVisible(true)
-            assertThat(showIndicatorForDeviceEntry).isEqualTo(true)
+            assertThat(showIndicatorForDeviceEntry).isTrue()
 
             bouncerRepository.setAlternateVisible(false)
-            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+            assertThat(showIndicatorForDeviceEntry).isFalse()
         }
 
     @Test
@@ -266,4 +364,25 @@
             true
         )
     }
+
+    private fun TestScope.updateBouncerScene(
+        isActive: Boolean,
+        fpsDetectionRunning: Boolean,
+        isUnlockingWithFpAllowed: Boolean,
+    ) {
+        kosmos.sceneInteractor.changeScene(
+            if (isActive) Scenes.Bouncer else Scenes.Lockscreen,
+            "reason"
+        )
+
+        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+            .thenReturn(fpsDetectionRunning)
+        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+            .thenReturn(isUnlockingWithFpAllowed)
+        mContext.orCreateTestableResources.addOverride(
+            R.bool.config_show_sidefps_hint_on_bouncer,
+            true
+        )
+        runCurrent()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
new file mode 100644
index 0000000..e044eec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.mediaprojection.permission
+
+import android.app.AlertDialog
+import android.media.projection.MediaProjectionConfig
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.WindowManager
+import android.widget.Spinner
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.AlertDialogWithDelegate
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.mock
+import junit.framework.Assert.assertEquals
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() {
+
+    private lateinit var dialog: AlertDialog
+
+    private val flags = mock<FeatureFlagsClassic>()
+    private val onStartRecordingClicked = mock<Runnable>()
+    private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+
+    private val mediaProjectionConfig: MediaProjectionConfig =
+        MediaProjectionConfig.createConfigForDefaultDisplay()
+    private val appName: String = "testApp"
+    private val hostUid: Int = 12345
+
+    private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app
+    private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen
+    private val resIdSingleAppDisabled =
+        R.string.media_projection_entry_app_permission_dialog_single_app_disabled
+
+    @Before
+    fun setUp() {
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+    }
+
+    @After
+    fun teardown() {
+        if (::dialog.isInitialized) {
+            dialog.dismiss()
+        }
+    }
+
+    @Test
+    fun showDialog_forceShowPartialScreenShareFalse() {
+        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+        // overrideDisableSingleAppOption = false
+        val overrideDisableSingleAppOption = false
+        setUpAndShowDialog(overrideDisableSingleAppOption)
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        val secondOptionText =
+            spinner.adapter
+                .getDropDownView(1, null, spinner)
+                .findViewById<TextView>(android.R.id.text2)
+                ?.text
+
+        // check that the first option is full screen and enabled
+        assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+        // check that the second option is single app and disabled
+        assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText)
+    }
+
+    @Test
+    fun showDialog_forceShowPartialScreenShareTrue() {
+        // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and
+        // overrideDisableSingleAppOption = true
+        val overrideDisableSingleAppOption = true
+        setUpAndShowDialog(overrideDisableSingleAppOption)
+
+        val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
+        val secondOptionText =
+            spinner.adapter
+                .getDropDownView(1, null, spinner)
+                .findViewById<TextView>(android.R.id.text1)
+                ?.text
+
+        // check that the first option is single app and enabled
+        assertEquals(context.getString(resIdSingleApp), spinner.selectedItem)
+
+        // check that the second option is full screen and enabled
+        assertEquals(context.getString(resIdFullScreen), secondOptionText)
+    }
+
+    private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) {
+        val delegate =
+            MediaProjectionPermissionDialogDelegate(
+                context,
+                mediaProjectionConfig,
+                {},
+                onStartRecordingClicked,
+                appName,
+                overrideDisableSingleAppOption,
+                hostUid,
+                mediaProjectionMetricsLogger
+            )
+
+        dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
+        SystemUIDialog.applyFlags(dialog)
+        SystemUIDialog.setDialogSize(dialog)
+
+        dialog.window?.addSystemFlags(
+            WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+        )
+
+        delegate.onCreate(dialog, savedInstanceState = null)
+        dialog.show()
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryKosmos.kt
new file mode 100644
index 0000000..2e5ddc7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeBrightnessPolicyRepository by Kosmos.Fixture { FakeBrightnessPolicyRepository() }
+
+var Kosmos.brightnessPolicyRepository: BrightnessPolicyRepository by
+    Kosmos.Fixture { fakeBrightnessPolicyRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
new file mode 100644
index 0000000..d3ceb15
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.brightness.data.repository
+
+import com.android.settingslib.RestrictedLockUtils
+import com.android.systemui.utils.PolicyRestriction
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeBrightnessPolicyRepository : BrightnessPolicyRepository {
+    private val _restrictionPolicy: MutableStateFlow<PolicyRestriction> =
+        MutableStateFlow(PolicyRestriction.NoRestriction)
+    override val restrictionPolicy = _restrictionPolicy.asStateFlow()
+
+    fun setCurrentUserUnrestricted() {
+        _restrictionPolicy.value = PolicyRestriction.NoRestriction
+    }
+
+    fun setCurrentUserRestricted() {
+        _restrictionPolicy.value =
+            PolicyRestriction.Restricted(
+                RestrictedLockUtils.EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(
+                    BrightnessPolicyRepository.RESTRICTION
+                )
+            )
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
new file mode 100644
index 0000000..a05b5e6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.brightness.data.repository
+
+import android.hardware.display.BrightnessInfo
+import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE
+import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
+import com.android.systemui.brightness.data.model.LinearBrightness
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+
+class FakeScreenBrightnessRepository(
+    initialBrightnessInfo: BrightnessInfo =
+        BrightnessInfo(0f, 0f, 1f, HIGH_BRIGHTNESS_MODE_OFF, 1f, BRIGHTNESS_MAX_REASON_NONE)
+) : ScreenBrightnessRepository {
+
+    private val brightnessInfo = MutableStateFlow(initialBrightnessInfo)
+    private val _temporaryBrightness =
+        MutableStateFlow(LinearBrightness(initialBrightnessInfo.brightness))
+    val temporaryBrightness = _temporaryBrightness.asStateFlow()
+    override val linearBrightness = brightnessInfo.map { LinearBrightness(it.brightness) }
+    override val minLinearBrightness = brightnessInfo.map { LinearBrightness(it.brightnessMinimum) }
+    override val maxLinearBrightness = brightnessInfo.map { LinearBrightness(it.brightnessMaximum) }
+
+    override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
+        return minMaxLinearBrightness()
+    }
+
+    private fun minMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
+        return with(brightnessInfo.value) {
+            LinearBrightness(brightnessMinimum) to LinearBrightness(brightnessMaximum)
+        }
+    }
+
+    override fun setTemporaryBrightness(value: LinearBrightness) {
+        val bounds = minMaxLinearBrightness()
+        val clampedValue = value.clamp(bounds.first, bounds.second)
+        _temporaryBrightness.value = clampedValue
+    }
+
+    override fun setBrightness(value: LinearBrightness) {
+        val bounds = minMaxLinearBrightness()
+        val clampedValue = value.clamp(bounds.first, bounds.second)
+        _temporaryBrightness.value = clampedValue
+        brightnessInfo.value =
+            with(brightnessInfo.value) {
+                BrightnessInfo(
+                    clampedValue.floatValue,
+                    brightnessMinimum,
+                    brightnessMaximum,
+                    highBrightnessMode,
+                    highBrightnessTransitionPoint,
+                    brightnessMaxReason,
+                )
+            }
+    }
+
+    fun setMinMaxBrightness(min: LinearBrightness, max: LinearBrightness) {
+        check(min.floatValue <= max.floatValue)
+        val clampedBrightness = LinearBrightness(brightnessInfo.value.brightness).clamp(min, max)
+        _temporaryBrightness.value = clampedBrightness
+        brightnessInfo.value =
+            with(brightnessInfo.value) {
+                BrightnessInfo(
+                    clampedBrightness.floatValue,
+                    min.floatValue,
+                    max.floatValue,
+                    highBrightnessMode,
+                    highBrightnessTransitionPoint,
+                    brightnessMaxReason
+                )
+            }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepositoryKosmos.kt
new file mode 100644
index 0000000..c5b0eb2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepositoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeScreenBrightnessRepository: FakeScreenBrightnessRepository by
+    Kosmos.Fixture { FakeScreenBrightnessRepository() }
+
+var Kosmos.screenBrightnessRepository: ScreenBrightnessRepository by
+    Kosmos.Fixture { fakeScreenBrightnessRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorKosmos.kt
new file mode 100644
index 0000000..9d3c8d2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.domain.interactor
+
+import com.android.systemui.brightness.data.repository.brightnessPolicyRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.activityStarter
+
+val Kosmos.brightnessPolicyEnforcementInteractor by
+    Kosmos.Fixture {
+        BrightnessPolicyEnforcementInteractor(brightnessPolicyRepository, activityStarter)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
new file mode 100644
index 0000000..22784e4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.brightness.domain.interactor
+
+import com.android.systemui.brightness.data.repository.screenBrightnessRepository
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.screenBrightnessInteractor by
+    Kosmos.Fixture { ScreenBrightnessInteractor(screenBrightnessRepository) }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 8ac1eb9..4dae6d5 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -103,10 +103,10 @@
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall;
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
-import com.android.server.companion.presence.ObservableUuid;
-import com.android.server.companion.presence.ObservableUuidStore;
+import com.android.server.companion.devicepresence.CompanionAppBinder;
+import com.android.server.companion.devicepresence.DevicePresenceProcessor;
+import com.android.server.companion.devicepresence.ObservableUuid;
+import com.android.server.companion.devicepresence.ObservableUuidStore;
 import com.android.server.companion.transport.CompanionTransportManager;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.ActivityTaskManagerInternal;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index daa8fdb..9cfb535 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -36,8 +36,8 @@
 import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
 import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
-import com.android.server.companion.presence.DevicePresenceProcessor;
-import com.android.server.companion.presence.ObservableUuid;
+import com.android.server.companion.devicepresence.DevicePresenceProcessor;
+import com.android.server.companion.devicepresence.ObservableUuid;
 import com.android.server.companion.transport.CompanionTransportManager;
 
 import java.io.PrintWriter;
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index acf683d..8c1116b 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -37,8 +37,8 @@
 import android.util.Slog;
 
 import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
-import com.android.server.companion.presence.CompanionAppBinder;
-import com.android.server.companion.presence.DevicePresenceProcessor;
+import com.android.server.companion.devicepresence.CompanionAppBinder;
+import com.android.server.companion.devicepresence.DevicePresenceProcessor;
 import com.android.server.companion.transport.CompanionTransportManager;
 
 /**
diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java
similarity index 61%
rename from services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
rename to services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java
index 407b9da..6cdc02e 100644
--- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java
@@ -15,27 +15,15 @@
  */
 
 
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
 
 import static android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED;
 import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED;
-import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE;
-import static android.bluetooth.BluetoothAdapter.EXTRA_STATE;
-import static android.bluetooth.BluetoothAdapter.nameForState;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES;
-import static android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY;
 import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES;
 import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
 import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST;
 import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER;
 
-import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
-import static com.android.server.companion.utils.Utils.btDeviceToString;
-
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.MainThread;
@@ -56,21 +44,19 @@
 import android.content.IntentFilter;
 import android.os.Handler;
 import android.os.Looper;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.server.companion.association.AssociationStore;
 import com.android.server.companion.association.AssociationStore.ChangeType;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 @SuppressLint("LongLogTag")
-class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener {
-    private static final String TAG = "CDM_BleCompanionDeviceScanner";
+class BleDeviceProcessor implements AssociationStore.OnChangeListener {
+    private static final String TAG = "CDM_BleDeviceProcessor";
 
     interface Callback {
         void onBleCompanionDeviceFound(int associationId, int userId);
@@ -78,26 +64,27 @@
         void onBleCompanionDeviceLost(int associationId, int userId);
     }
 
-    private final @NonNull AssociationStore mAssociationStore;
-    private final @NonNull Callback mCallback;
+    @NonNull
+    private final AssociationStore mAssociationStore;
+    @NonNull
+    private final Callback mCallback;
 
     // Non-null after init().
-    private @Nullable BluetoothAdapter mBtAdapter;
+    @Nullable
+    private BluetoothAdapter mBtAdapter;
     // Non-null after init() and when BLE is available. Otherwise - null.
-    private @Nullable BluetoothLeScanner mBleScanner;
+    @Nullable
+    private BluetoothLeScanner mBleScanner;
     // Only accessed from the Main thread.
     private boolean mScanning = false;
 
-    BleCompanionDeviceScanner(
-            @NonNull AssociationStore associationStore, @NonNull Callback callback) {
+    BleDeviceProcessor(@NonNull AssociationStore associationStore, @NonNull Callback callback) {
         mAssociationStore = associationStore;
         mCallback = callback;
     }
 
     @MainThread
     void init(@NonNull Context context, @NonNull BluetoothAdapter btAdapter) {
-        if (DEBUG) Log.i(TAG, "init()");
-
         if (mBtAdapter != null) {
             throw new IllegalStateException(getClass().getSimpleName() + " is already initialized");
         }
@@ -113,9 +100,7 @@
     final void restartScan() {
         enforceInitialized();
 
-        if (DEBUG) Log.i(TAG , "restartScan()");
         if (mBleScanner == null) {
-            if (DEBUG) Log.d(TAG, "  > BLE is not available");
             return;
         }
 
@@ -138,12 +123,8 @@
         enforceInitialized();
 
         final boolean bleAvailable = mBtAdapter.isLeEnabled();
-        if (DEBUG) {
-            Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable);
-        }
         if ((bleAvailable && mBleScanner != null) || (!bleAvailable && mBleScanner == null)) {
             // Nothing changed.
-            if (DEBUG) Log.i(TAG, "  > BLE status did not change");
             return;
         }
 
@@ -153,12 +134,9 @@
                 // Oops, that's a race condition. Can return.
                 return;
             }
-            if (DEBUG) Log.i(TAG, "  > BLE is now available");
 
             startScan();
         } else {
-            if (DEBUG) Log.i(TAG, "  > BLE is now unavailable");
-
             stopScanIfNeeded();
             mBleScanner = null;
         }
@@ -194,13 +172,7 @@
             }
         }
         if (macAddresses.isEmpty()) {
-            if (DEBUG) Log.i(TAG, "  > there are no (associated) devices to Scan for.");
             return;
-        } else {
-            if (DEBUG) {
-                Log.d(TAG, "  > addresses=(n=" + macAddresses.size() + ")"
-                        + "[" + String.join(", ", macAddresses) + "]");
-            }
         }
 
         final List<ScanFilter> filters = new ArrayList<>(macAddresses.size());
@@ -230,7 +202,6 @@
 
         Slog.i(TAG, "stopBleScan()");
         if (!mScanning) {
-            if (DEBUG) Log.d(TAG, "  > not scanning.");
             return;
         }
         // mScanCallback is non-null here - it cannot be null when mScanning is true.
@@ -252,26 +223,16 @@
 
     @MainThread
     private void notifyDeviceFound(@NonNull BluetoothDevice device) {
-        if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device));
-
-        final List<AssociationInfo> associations =
-                mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
-        if (DEBUG) Log.d(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
-
-        for (AssociationInfo association : associations) {
+        for (AssociationInfo association : mAssociationStore.getActiveAssociationsByAddress(
+                device.getAddress())) {
             mCallback.onBleCompanionDeviceFound(association.getId(), association.getUserId());
         }
     }
 
     @MainThread
     private void notifyDeviceLost(@NonNull BluetoothDevice device) {
-        if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device));
-
-        final List<AssociationInfo> associations =
-                mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
-        if (DEBUG) Log.d(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
-
-        for (AssociationInfo association : associations) {
+        for (AssociationInfo association : mAssociationStore.getActiveAssociationsByAddress(
+                device.getAddress())) {
             mCallback.onBleCompanionDeviceLost(association.getId(), association.getUserId());
         }
     }
@@ -280,17 +241,6 @@
         final BroadcastReceiver receiver = new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                final int prevState = intent.getIntExtra(EXTRA_PREVIOUS_STATE, -1);
-                final int state = intent.getIntExtra(EXTRA_STATE, -1);
-
-                if (DEBUG) {
-                    // The action is either STATE_CHANGED or BLE_STATE_CHANGED.
-                    final String action =
-                            intent.getAction().replace("android.bluetooth.adapter.", "bt.");
-                    Log.d(TAG, "on(Broadcast)Receive() " + action + ": "
-                            + nameForBtState(prevState) + "->" + nameForBtState(state));
-                }
-
                 checkBleState();
             }
         };
@@ -313,16 +263,6 @@
         public void onScanResult(int callbackType, ScanResult result) {
             final BluetoothDevice device = result.getDevice();
 
-            if (DEBUG) {
-                Log.d(TAG, "onScanResult() " + nameForBleScanCallbackType(callbackType)
-                        + " device=" + btDeviceToString(device));
-                Log.v(TAG, "  > scanResult=" + result);
-
-                final List<AssociationInfo> associations =
-                        mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
-                Log.v(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
-            }
-
             switch (callbackType) {
                 case CALLBACK_TYPE_FIRST_MATCH:
                     notifyDeviceFound(device);
@@ -342,60 +282,20 @@
         @MainThread
         @Override
         public void onScanFailed(int errorCode) {
-            if (DEBUG) Log.w(TAG, "onScanFailed() " + nameForBleScanErrorCode(errorCode));
             mScanning = false;
         }
     };
 
-    private static String nameForBtState(int state) {
-        return nameForState(state) + "(" + state + ")";
-    }
-
     private static String nameForBleScanCallbackType(int callbackType) {
-        final String name;
-        switch (callbackType) {
-            case CALLBACK_TYPE_ALL_MATCHES:
-                name = "ALL_MATCHES";
-                break;
-            case CALLBACK_TYPE_FIRST_MATCH:
-                name = "FIRST_MATCH";
-                break;
-            case CALLBACK_TYPE_MATCH_LOST:
-                name = "MATCH_LOST";
-                break;
-            default:
-                name = "Unknown";
-        }
+        final String name = switch (callbackType) {
+            case CALLBACK_TYPE_ALL_MATCHES -> "ALL_MATCHES";
+            case CALLBACK_TYPE_FIRST_MATCH -> "FIRST_MATCH";
+            case CALLBACK_TYPE_MATCH_LOST -> "MATCH_LOST";
+            default -> "Unknown";
+        };
         return name + "(" + callbackType + ")";
     }
 
-    private static String nameForBleScanErrorCode(int errorCode) {
-        final String name;
-        switch (errorCode) {
-            case SCAN_FAILED_ALREADY_STARTED:
-                name = "ALREADY_STARTED";
-                break;
-            case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED:
-                name = "APPLICATION_REGISTRATION_FAILED";
-                break;
-            case SCAN_FAILED_INTERNAL_ERROR:
-                name = "INTERNAL_ERROR";
-                break;
-            case SCAN_FAILED_FEATURE_UNSUPPORTED:
-                name = "FEATURE_UNSUPPORTED";
-                break;
-            case SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES:
-                name = "OUT_OF_HARDWARE_RESOURCES";
-                break;
-            case SCAN_FAILED_SCANNING_TOO_FREQUENTLY:
-                name = "SCANNING_TOO_FREQUENTLY";
-                break;
-            default:
-                name = "Unknown";
-        }
-        return name + "(" + errorCode + ")";
-    }
-
     private static final ScanSettings SCAN_SETTINGS = new ScanSettings.Builder()
             .setCallbackType(CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST)
             .setScanMode(SCAN_MODE_LOW_POWER)
diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java
similarity index 62%
rename from services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
rename to services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java
index e1a8db4..612c156 100644
--- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java
@@ -14,14 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
 
 import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED;
 import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED;
 
-import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG;
-import static com.android.server.companion.utils.Utils.btDeviceToString;
-
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
 import android.bluetooth.BluetoothAdapter;
@@ -32,8 +29,6 @@
 import android.os.HandlerExecutor;
 import android.os.ParcelUuid;
 import android.os.UserHandle;
-import android.os.UserManager;
-import android.util.Log;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.companion.association.AssociationStore;
@@ -45,10 +40,10 @@
 import java.util.Map;
 
 @SuppressLint("LongLogTag")
-public class BluetoothCompanionDeviceConnectionListener
+public class BluetoothDeviceProcessor
         extends BluetoothAdapter.BluetoothConnectionCallback
         implements AssociationStore.OnChangeListener {
-    private static final String TAG = "CDM_BluetoothCompanionDeviceConnectionListener";
+    private static final String TAG = "CDM_BluetoothDeviceProcessor";
 
     interface Callback {
         void onBluetoothCompanionDeviceConnected(int associationId, int userId);
@@ -58,24 +53,25 @@
         void onDevicePresenceEventByUuid(ObservableUuid uuid, int event);
     }
 
-    private final @NonNull AssociationStore mAssociationStore;
-    private final @NonNull Callback mCallback;
+    @NonNull
+    private final AssociationStore mAssociationStore;
+    @NonNull
+    private final ObservableUuidStore mObservableUuidStore;
+    @NonNull
+    private final Callback mCallback;
+
     /** A set of ALL connected BT device (not only companion.) */
-    private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
+    @NonNull
+    private final Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>();
 
-    private final @NonNull ObservableUuidStore mObservableUuidStore;
-
-    BluetoothCompanionDeviceConnectionListener(UserManager userManager,
-            @NonNull AssociationStore associationStore,
+    BluetoothDeviceProcessor(@NonNull AssociationStore associationStore,
             @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) {
         mAssociationStore = associationStore;
         mObservableUuidStore = observableUuidStore;
         mCallback = callback;
     }
 
-    public void init(@NonNull BluetoothAdapter btAdapter) {
-        if (DEBUG) Log.i(TAG, "init()");
-
+    void init(@NonNull BluetoothAdapter btAdapter) {
         btAdapter.registerBluetoothConnectionCallback(
                 new HandlerExecutor(Handler.getMain()), /* callback */this);
         mAssociationStore.registerLocalListener(this);
@@ -87,13 +83,9 @@
      */
     @Override
     public void onDeviceConnected(@NonNull BluetoothDevice device) {
-        if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device));
-
         final MacAddress macAddress = MacAddress.fromString(device.getAddress());
-        final int userId = UserHandle.myUserId();
 
         if (mAllConnectedDevices.put(macAddress, device) != null) {
-            if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected.");
             return;
         }
 
@@ -108,18 +100,9 @@
     @Override
     public void onDeviceDisconnected(@NonNull BluetoothDevice device,
             int reason) {
-        if (DEBUG) {
-            Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device));
-            Log.d(TAG, "  reason=" + disconnectReasonToString(reason));
-        }
-
         final MacAddress macAddress = MacAddress.fromString(device.getAddress());
-        final int userId = UserHandle.myUserId();
 
         if (mAllConnectedDevices.remove(macAddress) == null) {
-            if (DEBUG) {
-                Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device));
-            }
             return;
         }
 
@@ -130,22 +113,6 @@
         int userId = UserHandle.myUserId();
         final List<AssociationInfo> associations =
                 mAssociationStore.getActiveAssociationsByAddress(device.getAddress());
-        final List<ObservableUuid> observableUuids =
-                mObservableUuidStore.getObservableUuidsForUser(userId);
-        final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
-
-        final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
-                ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
-
-        if (DEBUG) {
-            Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device)
-                    + " connected=" + connected);
-            if (associations.isEmpty()) {
-                Log.d(TAG, "  > No CDM associations");
-            } else {
-                Log.d(TAG, "  > associations=" + Arrays.toString(associations.toArray()));
-            }
-        }
 
         for (AssociationInfo association : associations) {
             if (!association.isNotifyOnDeviceNearby()) continue;
@@ -157,44 +124,25 @@
             }
         }
 
+        final List<ObservableUuid> observableUuids =
+                mObservableUuidStore.getObservableUuidsForUser(userId);
+        final ParcelUuid[] bluetoothDeviceUuids = device.getUuids();
+        final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids)
+                ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids);
+
         for (ObservableUuid uuid : observableUuids) {
             if (deviceUuids.contains(uuid.getUuid())) {
                 mCallback.onDevicePresenceEventByUuid(uuid, connected ? EVENT_BT_CONNECTED
-                                : EVENT_BT_DISCONNECTED);
+                        : EVENT_BT_DISCONNECTED);
             }
         }
     }
 
     @Override
     public void onAssociationAdded(AssociationInfo association) {
-        if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association);
-
         if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) {
             mCallback.onBluetoothCompanionDeviceConnected(
                     association.getId(), association.getUserId());
         }
     }
-
-    @Override
-    public void onAssociationRemoved(AssociationInfo association) {
-        // Intentionally do nothing: CompanionDevicePresenceMonitor will do all the bookkeeping
-        // required.
-    }
-
-    @Override
-    public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) {
-        if (DEBUG) {
-            Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged
-                    + " " + association);
-        }
-
-        if (!addressChanged) {
-            // Don't need to do anything.
-            return;
-        }
-
-        // At the moment CDM does allow changing association addresses, so we will never come here.
-        // This will be implemented when CDM support updating addresses.
-        throw new IllegalArgumentException("Address changes are not supported.");
-    }
 }
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
similarity index 99%
rename from services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
rename to services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
index b6348ea..60f4688 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java
similarity index 99%
rename from services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
rename to services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java
index c01c319..5923e70c 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
 
 import static android.content.Context.BIND_ALMOST_PERCEPTIBLE;
 import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
similarity index 96%
rename from services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
rename to services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index 092886c..cfb7f337 100644
--- a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
 
 import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
 import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED;
@@ -24,6 +24,7 @@
 import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED;
 import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED;
 import static android.companion.DevicePresenceEvent.NO_ASSOCIATION;
+import static android.content.Context.BLUETOOTH_SERVICE;
 import static android.os.Process.ROOT_UID;
 import static android.os.Process.SHELL_UID;
 
@@ -35,6 +36,7 @@
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
 import android.companion.AssociationInfo;
 import android.companion.DeviceNotAssociatedException;
 import android.companion.DevicePresenceEvent;
@@ -49,7 +51,6 @@
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
 import android.os.UserManager;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -80,8 +81,7 @@
  */
 @SuppressLint("LongLogTag")
 public class DevicePresenceProcessor implements AssociationStore.OnChangeListener,
-        BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback {
-    static final boolean DEBUG = false;
+        BluetoothDeviceProcessor.Callback, BleDeviceProcessor.Callback {
     private static final String TAG = "CDM_DevicePresenceProcessor";
 
     @NonNull
@@ -93,9 +93,9 @@
     @NonNull
     private final ObservableUuidStore mObservableUuidStore;
     @NonNull
-    private final BluetoothCompanionDeviceConnectionListener mBtConnectionListener;
+    private final BluetoothDeviceProcessor mBluetoothDeviceProcessor;
     @NonNull
-    private final BleCompanionDeviceScanner mBleScanner;
+    private final BleDeviceProcessor mBleDeviceProcessor;
     @NonNull
     private final PowerManagerInternal mPowerManagerInternal;
     @NonNull
@@ -142,7 +142,7 @@
 
     public DevicePresenceProcessor(@NonNull Context context,
             @NonNull CompanionAppBinder companionAppBinder,
-            UserManager userManager,
+            @NonNull UserManager userManager,
             @NonNull AssociationStore associationStore,
             @NonNull ObservableUuidStore observableUuidStore,
             @NonNull PowerManagerInternal powerManagerInternal) {
@@ -151,25 +151,27 @@
         mAssociationStore = associationStore;
         mObservableUuidStore = observableUuidStore;
         mUserManager = userManager;
-        mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager,
-                associationStore, mObservableUuidStore,
-                /* BluetoothCompanionDeviceConnectionListener.Callback */ this);
-        mBleScanner = new BleCompanionDeviceScanner(associationStore,
-                /* BleCompanionDeviceScanner.Callback */ this);
+        mBluetoothDeviceProcessor = new BluetoothDeviceProcessor(associationStore,
+                mObservableUuidStore, this);
+        mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this);
         mPowerManagerInternal = powerManagerInternal;
     }
 
     /** Initialize {@link DevicePresenceProcessor} */
     public void init(Context context) {
-        if (DEBUG) Slog.i(TAG, "init()");
-
-        final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
-        if (btAdapter != null) {
-            mBtConnectionListener.init(btAdapter);
-            mBleScanner.init(context, btAdapter);
-        } else {
-            Slog.w(TAG, "BluetoothAdapter is NOT available.");
+        BluetoothManager bm = (BluetoothManager) context.getSystemService(BLUETOOTH_SERVICE);
+        if (bm == null) {
+            Slog.w(TAG, "BluetoothManager is not available.");
+            return;
         }
+        final BluetoothAdapter btAdapter = bm.getAdapter();
+        if (btAdapter == null) {
+            Slog.w(TAG, "BluetoothAdapter is NOT available.");
+            return;
+        }
+
+        mBluetoothDeviceProcessor.init(btAdapter);
+        mBleDeviceProcessor.init(context, btAdapter);
 
         mAssociationStore.registerLocalListener(this);
     }
@@ -280,7 +282,7 @@
      * For legacy device presence below Android V.
      *
      * @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String,
-     *             int)}
+     * int)}
      */
     @Deprecated
     public void startObservingDevicePresence(int userId, String packageName, String deviceAddress)
@@ -310,7 +312,7 @@
      * For legacy device presence below Android V.
      *
      * @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String,
-     *             int)}
+     * int)}
      */
     @Deprecated
     public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress)
@@ -496,7 +498,7 @@
 
             // Stop the BLE scan if all devices report BT connected status and BLE was present.
             if (canStopBleScan()) {
-                mBleScanner.stopScanIfNeeded();
+                mBleDeviceProcessor.stopScanIfNeeded();
             }
 
         }
@@ -513,7 +515,7 @@
         }
 
         // Start BLE scanning when the device is disconnected.
-        mBleScanner.startScan();
+        mBleDeviceProcessor.startScan();
 
         onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED);
         // If current device is BLE present but BT is disconnected , means it will be
@@ -724,7 +726,7 @@
         final ParcelUuid parcelUuid = uuid.getUuid();
         final int userId = uuid.getUserId();
         if (!mUserManager.isUserUnlockingOrUnlocked(userId)) {
-            onDeviceLocked(/* associationId */ -1, userId, eventType, parcelUuid);
+            onDeviceLocked(NO_ASSOCIATION, userId, eventType, parcelUuid);
             return;
         }
 
@@ -930,10 +932,6 @@
     @Override
     public void onAssociationRemoved(@NonNull AssociationInfo association) {
         final int id = association.getId();
-        if (DEBUG) {
-            Log.i(TAG, "onAssociationRemoved() id=" + id);
-            Log.d(TAG, "  > association=" + association);
-        }
 
         mConnectedBtDevices.remove(id);
         mNearbyBleDevices.remove(id);
@@ -1004,8 +1002,8 @@
                     if (deviceEvents != null) {
                         deviceEvents.removeIf(deviceEvent ->
                                 deviceEvent.getEvent() == EVENT_BLE_APPEARED
-                                && Objects.equals(deviceEvent.getUuid(), uuid)
-                                && deviceEvent.getAssociationId() == associationId);
+                                        && Objects.equals(deviceEvent.getUuid(), uuid)
+                                        && deviceEvent.getAssociationId() == associationId);
                     }
                 }
             }
@@ -1018,8 +1016,8 @@
                     if (deviceEvents != null) {
                         deviceEvents.removeIf(deviceEvent ->
                                 deviceEvent.getEvent() == EVENT_BT_CONNECTED
-                                && Objects.equals(deviceEvent.getUuid(), uuid)
-                                && deviceEvent.getAssociationId() == associationId);
+                                        && Objects.equals(deviceEvent.getUuid(), uuid)
+                                        && deviceEvent.getAssociationId() == associationId);
                     }
                 }
             }
@@ -1054,7 +1052,7 @@
                     return;
                 }
 
-                switch(event) {
+                switch (event) {
                     case EVENT_BLE_APPEARED:
                         onBleCompanionDeviceFound(
                                 associationInfo.getId(), associationInfo.getUserId());
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuid.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java
similarity index 96%
rename from services/companion/java/com/android/server/companion/presence/ObservableUuid.java
rename to services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java
index 9cfa270..c9f60ca 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuid.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
similarity index 99%
rename from services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
rename to services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
index fa0f6bd..4678a16 100644
--- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.companion.presence;
+package com.android.server.companion.devicepresence;
 
 import static com.android.internal.util.XmlUtils.readIntAttribute;
 import static com.android.internal.util.XmlUtils.readLongAttribute;
diff --git a/services/core/java/com/android/server/devicestate/OWNERS b/services/core/java/com/android/server/devicestate/OWNERS
index ae79fc0..43f3f0c 100644
--- a/services/core/java/com/android/server/devicestate/OWNERS
+++ b/services/core/java/com/android/server/devicestate/OWNERS
@@ -1,3 +1,4 @@
 ogunwale@google.com
 akulian@google.com
-darryljohnson@google.com
+lihongyu@google.com
+kennethford@google.com
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 4aab9d2..06dc7f5 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -55,6 +55,7 @@
 import com.android.server.EventLogTags;
 import com.android.server.display.brightness.BrightnessEvent;
 import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.config.HysteresisLevels;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -673,14 +674,10 @@
         }
 
         pw.println();
-        pw.println("  mAmbientBrightnessThresholds=");
-        mAmbientBrightnessThresholds.dump(pw);
-        pw.println("  mScreenBrightnessThresholds=");
-        mScreenBrightnessThresholds.dump(pw);
-        pw.println("  mScreenBrightnessThresholdsIdle=");
-        mScreenBrightnessThresholdsIdle.dump(pw);
-        pw.println("  mAmbientBrightnessThresholdsIdle=");
-        mAmbientBrightnessThresholdsIdle.dump(pw);
+        pw.println("  mAmbientBrightnessThresholds=" + mAmbientBrightnessThresholds);
+        pw.println("  mAmbientBrightnessThresholdsIdle=" + mAmbientBrightnessThresholdsIdle);
+        pw.println("  mScreenBrightnessThresholds=" + mScreenBrightnessThresholds);
+        pw.println("  mScreenBrightnessThresholdsIdle=" + mScreenBrightnessThresholdsIdle);
     }
 
     public float[] getLastSensorValues() {
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
index 09094ff..4bbddae 100644
--- a/services/core/java/com/android/server/display/DisplayDevice.java
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -162,8 +162,8 @@
         DisplayDeviceInfo displayDeviceInfo = getDisplayDeviceInfoLocked();
         var width = displayDeviceInfo.width;
         var height = displayDeviceInfo.height;
-        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.yDpi > 0
-                    && displayDeviceInfo.xDpi > 0) {
+        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.type == Display.TYPE_EXTERNAL
+                    && displayDeviceInfo.yDpi > 0 && displayDeviceInfo.xDpi > 0) {
             if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * MAX_ANISOTROPY) {
                 height = (int) (height * displayDeviceInfo.xDpi / displayDeviceInfo.yDpi + 0.5);
             } else if (displayDeviceInfo.xDpi * MAX_ANISOTROPY < displayDeviceInfo.yDpi) {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 70668cb..85a231f 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -33,7 +33,6 @@
 import android.os.PowerManager;
 import android.text.TextUtils;
 import android.util.MathUtils;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Spline;
@@ -46,7 +45,6 @@
 import com.android.server.display.config.AutoBrightness;
 import com.android.server.display.config.BlockingZoneConfig;
 import com.android.server.display.config.BrightnessLimitMap;
-import com.android.server.display.config.BrightnessThresholds;
 import com.android.server.display.config.BrightnessThrottlingMap;
 import com.android.server.display.config.BrightnessThrottlingPoint;
 import com.android.server.display.config.Density;
@@ -58,6 +56,7 @@
 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.HysteresisLevels;
 import com.android.server.display.config.IdleScreenRefreshRateTimeout;
 import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
 import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds;
@@ -80,7 +79,6 @@
 import com.android.server.display.config.SensorData;
 import com.android.server.display.config.ThermalStatus;
 import com.android.server.display.config.ThermalThrottling;
-import com.android.server.display.config.ThresholdPoint;
 import com.android.server.display.config.UsiVersion;
 import com.android.server.display.config.XmlParser;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -625,13 +623,6 @@
     private static final int DEFAULT_HIGH_REFRESH_RATE = 0;
     private static final float[] DEFAULT_BRIGHTNESS_THRESHOLDS = new float[]{};
 
-    private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
-    private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
-    private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
-    private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
-    private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
-    private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
-
     private static final int INTERPOLATION_DEFAULT = 0;
     private static final int INTERPOLATION_LINEAR = 1;
 
@@ -713,38 +704,16 @@
     private long mBrightnessRampIncreaseMaxIdleMillis = 0;
     private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS;
     private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS;
-    private float mScreenBrighteningMinThreshold = 0.0f;     // Retain behaviour as though there is
-    private float mScreenBrighteningMinThresholdIdle = 0.0f; // no minimum threshold for change in
-    private float mScreenDarkeningMinThreshold = 0.0f;       // screen brightness or ambient
-    private float mScreenDarkeningMinThresholdIdle = 0.0f;   // brightness.
-    private float mAmbientLuxBrighteningMinThreshold = 0.0f;
-    private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f;
-    private float mAmbientLuxDarkeningMinThreshold = 0.0f;
-    private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f;
 
-    // Screen brightness thresholds levels & percentages
-    private float[] mScreenBrighteningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
-    private float[] mScreenBrighteningPercentages = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
-    private float[] mScreenDarkeningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS;
-    private float[] mScreenDarkeningPercentages = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
-
-    // Screen brightness thresholds levels & percentages for idle mode
-    private float[] mScreenBrighteningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
-    private float[] mScreenBrighteningPercentagesIdle = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS;
-    private float[] mScreenDarkeningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS;
-    private float[] mScreenDarkeningPercentagesIdle = DEFAULT_SCREEN_DARKENING_THRESHOLDS;
-
-    // Ambient brightness thresholds levels & percentages
-    private float[] mAmbientBrighteningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
-    private float[] mAmbientBrighteningPercentages = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
-    private float[] mAmbientDarkeningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
-    private float[] mAmbientDarkeningPercentages = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
-
-    // Ambient brightness thresholds levels & percentages for idle mode
-    private float[] mAmbientBrighteningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
-    private float[] mAmbientBrighteningPercentagesIdle = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS;
-    private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS;
-    private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS;
+    // Hysteresis levels for screen/ambient brightness for normal/idle modes
+    private HysteresisLevels mScreenBrightnessHysteresis =
+            HysteresisLevels.loadDisplayBrightnessConfig(null, null);
+    private HysteresisLevels mScreenBrightnessIdleHysteresis =
+            HysteresisLevels.loadDisplayBrightnessIdleConfig(null, null);
+    private HysteresisLevels mAmbientBrightnessHysteresis =
+            HysteresisLevels.loadAmbientBrightnessConfig(null, null);
+    private HysteresisLevels mAmbientBrightnessIdleHysteresis =
+            HysteresisLevels.loadAmbientBrightnessIdleConfig(null, null);
 
     // A mapping between screen off sensor values and lux values
     private int[] mScreenOffBrightnessSensorValueToLux;
@@ -1302,324 +1271,20 @@
         return mAmbientHorizonShort;
     }
 
-    /**
-     * The minimum value for the screen brightness increase to actually occur.
-     * @return float value in brightness scale of 0 - 1.
-     */
-    public float getScreenBrighteningMinThreshold() {
-        return mScreenBrighteningMinThreshold;
+    public HysteresisLevels getAmbientBrightnessHysteresis() {
+        return mAmbientBrightnessHysteresis;
     }
 
-    /**
-     * The minimum value for the screen brightness decrease to actually occur.
-     * @return float value in brightness scale of 0 - 1.
-     */
-    public float getScreenDarkeningMinThreshold() {
-        return mScreenDarkeningMinThreshold;
+    public HysteresisLevels getAmbientBrightnessIdleHysteresis() {
+        return mAmbientBrightnessIdleHysteresis;
     }
 
-    /**
-     * The minimum value for the screen brightness increase to actually occur while in idle screen
-     * brightness mode.
-     * @return float value in brightness scale of 0 - 1.
-     */
-    public float getScreenBrighteningMinThresholdIdle() {
-        return mScreenBrighteningMinThresholdIdle;
+    public HysteresisLevels getScreenBrightnessHysteresis() {
+        return mScreenBrightnessHysteresis;
     }
 
-    /**
-     * The minimum value for the screen brightness decrease to actually occur while in idle screen
-     * brightness mode.
-     * @return float value in brightness scale of 0 - 1.
-     */
-    public float getScreenDarkeningMinThresholdIdle() {
-        return mScreenDarkeningMinThresholdIdle;
-    }
-
-    /**
-     * The minimum value for the ambient lux increase for a screen brightness change to actually
-     * occur.
-     * @return float value in lux.
-     */
-    public float getAmbientLuxBrighteningMinThreshold() {
-        return mAmbientLuxBrighteningMinThreshold;
-    }
-
-    /**
-     * The minimum value for the ambient lux decrease for a screen brightness change to actually
-     * occur.
-     * @return float value in lux.
-     */
-    public float getAmbientLuxDarkeningMinThreshold() {
-        return mAmbientLuxDarkeningMinThreshold;
-    }
-
-    /**
-     * The minimum value for the ambient lux increase for a screen brightness change to actually
-     * occur while in idle screen brightness mode.
-     * @return float value in lux.
-     */
-    public float getAmbientLuxBrighteningMinThresholdIdle() {
-        return mAmbientLuxBrighteningMinThresholdIdle;
-    }
-
-    /**
-     * The minimum value for the ambient lux decrease for a screen brightness change to actually
-     * occur while in idle screen brightness mode.
-     * @return float value in lux.
-     */
-    public float getAmbientLuxDarkeningMinThresholdIdle() {
-        return mAmbientLuxDarkeningMinThresholdIdle;
-    }
-
-    /**
-     * The array that describes the range of screen brightness that each threshold percentage
-     * applies within.
-     *
-     * The (zero-based) index is calculated as follows
-     * value = current screen brightness value
-     * level = mScreenBrighteningLevels
-     *
-     * condition                       return
-     * value < level[0]                = 0.0f
-     * level[n] <= value < level[n+1]  = mScreenBrighteningPercentages[n]
-     * level[MAX] <= value             = mScreenBrighteningPercentages[MAX]
-     *
-     * @return the screen brightness levels between 0.0 and 1.0 for which each
-     * mScreenBrighteningPercentages applies
-     */
-    public float[] getScreenBrighteningLevels() {
-        return mScreenBrighteningLevels;
-    }
-
-    /**
-     * The array that describes the screen brightening threshold percentage change at each screen
-     * brightness level described in mScreenBrighteningLevels.
-     *
-     * @return the percentages between 0 and 100 of brightness increase required in order for the
-     * screen brightness to change
-     */
-    public float[] getScreenBrighteningPercentages() {
-        return mScreenBrighteningPercentages;
-    }
-
-    /**
-     * The array that describes the range of screen brightness that each threshold percentage
-     * applies within.
-     *
-     * The (zero-based) index is calculated as follows
-     * value = current screen brightness value
-     * level = mScreenDarkeningLevels
-     *
-     * condition                       return
-     * value < level[0]                = 0.0f
-     * level[n] <= value < level[n+1]  = mScreenDarkeningPercentages[n]
-     * level[MAX] <= value             = mScreenDarkeningPercentages[MAX]
-     *
-     * @return the screen brightness levels between 0.0 and 1.0 for which each
-     * mScreenDarkeningPercentages applies
-     */
-    public float[] getScreenDarkeningLevels() {
-        return mScreenDarkeningLevels;
-    }
-
-    /**
-     * The array that describes the screen darkening threshold percentage change at each screen
-     * brightness level described in mScreenDarkeningLevels.
-     *
-     * @return the percentages between 0 and 100 of brightness decrease required in order for the
-     * screen brightness to change
-     */
-    public float[] getScreenDarkeningPercentages() {
-        return mScreenDarkeningPercentages;
-    }
-
-    /**
-     * The array that describes the range of ambient brightness that each threshold
-     * percentage applies within.
-     *
-     * The (zero-based) index is calculated as follows
-     * value = current ambient brightness value
-     * level = mAmbientBrighteningLevels
-     *
-     * condition                       return
-     * value < level[0]                = 0.0f
-     * level[n] <= value < level[n+1]  = mAmbientBrighteningPercentages[n]
-     * level[MAX] <= value             = mAmbientBrighteningPercentages[MAX]
-     *
-     * @return the ambient brightness levels from 0 lux upwards for which each
-     * mAmbientBrighteningPercentages applies
-     */
-    public float[] getAmbientBrighteningLevels() {
-        return mAmbientBrighteningLevels;
-    }
-
-    /**
-     * The array that describes the ambient brightening threshold percentage change at each ambient
-     * brightness level described in mAmbientBrighteningLevels.
-     *
-     * @return the percentages between 0 and 100 of brightness increase required in order for the
-     * screen brightness to change
-     */
-    public float[] getAmbientBrighteningPercentages() {
-        return mAmbientBrighteningPercentages;
-    }
-
-    /**
-     * The array that describes the range of ambient brightness that each threshold percentage
-     * applies within.
-     *
-     * The (zero-based) index is calculated as follows
-     * value = current ambient brightness value
-     * level = mAmbientDarkeningLevels
-     *
-     * condition                       return
-     * value < level[0]                = 0.0f
-     * level[n] <= value < level[n+1]  = mAmbientDarkeningPercentages[n]
-     * level[MAX] <= value             = mAmbientDarkeningPercentages[MAX]
-     *
-     * @return the ambient brightness levels from 0 lux upwards for which each
-     * mAmbientDarkeningPercentages applies
-     */
-    public float[] getAmbientDarkeningLevels() {
-        return mAmbientDarkeningLevels;
-    }
-
-    /**
-     * The array that describes the ambient darkening threshold percentage change at each ambient
-     * brightness level described in mAmbientDarkeningLevels.
-     *
-     * @return the percentages between 0 and 100 of brightness decrease required in order for the
-     * screen brightness to change
-     */
-    public float[] getAmbientDarkeningPercentages() {
-        return mAmbientDarkeningPercentages;
-    }
-
-    /**
-     * The array that describes the range of screen brightness that each threshold percentage
-     * applies within whilst in idle screen brightness mode.
-     *
-     * The (zero-based) index is calculated as follows
-     * value = current screen brightness value
-     * level = mScreenBrighteningLevelsIdle
-     *
-     * condition                       return
-     * value < level[0]                = 0.0f
-     * level[n] <= value < level[n+1]  = mScreenBrighteningPercentagesIdle[n]
-     * level[MAX] <= value             = mScreenBrighteningPercentagesIdle[MAX]
-     *
-     * @return the screen brightness levels between 0.0 and 1.0 for which each
-     * mScreenBrighteningPercentagesIdle applies
-     */
-    public float[] getScreenBrighteningLevelsIdle() {
-        return mScreenBrighteningLevelsIdle;
-    }
-
-    /**
-     * The array that describes the screen brightening threshold percentage change at each screen
-     * brightness level described in mScreenBrighteningLevelsIdle.
-     *
-     * @return the percentages between 0 and 100 of brightness increase required in order for the
-     * screen brightness to change while in idle mode.
-     */
-    public float[] getScreenBrighteningPercentagesIdle() {
-        return mScreenBrighteningPercentagesIdle;
-    }
-
-    /**
-     * The array that describes the range of screen brightness that each threshold percentage
-     * applies within whilst in idle screen brightness mode.
-     *
-     * The (zero-based) index is calculated as follows
-     * value = current screen brightness value
-     * level = mScreenDarkeningLevelsIdle
-     *
-     * condition                       return
-     * value < level[0]                = 0.0f
-     * level[n] <= value < level[n+1]  = mScreenDarkeningPercentagesIdle[n]
-     * level[MAX] <= value             = mScreenDarkeningPercentagesIdle[MAX]
-     *
-     * @return the screen brightness levels between 0.0 and 1.0 for which each
-     * mScreenDarkeningPercentagesIdle applies
-     */
-    public float[] getScreenDarkeningLevelsIdle() {
-        return mScreenDarkeningLevelsIdle;
-    }
-
-    /**
-     * The array that describes the screen darkening threshold percentage change at each screen
-     * brightness level described in mScreenDarkeningLevelsIdle.
-     *
-     * @return the percentages between 0 and 100 of brightness decrease required in order for the
-     * screen brightness to change while in idle mode.
-     */
-    public float[] getScreenDarkeningPercentagesIdle() {
-        return mScreenDarkeningPercentagesIdle;
-    }
-
-    /**
-     * The array that describes the range of ambient brightness that each threshold percentage
-     * applies within whilst in idle screen brightness mode.
-     *
-     * The (zero-based) index is calculated as follows
-     * value = current ambient brightness value
-     * level = mAmbientBrighteningLevelsIdle
-     *
-     * condition                       return
-     * value < level[0]                = 0.0f
-     * level[n] <= value < level[n+1]  = mAmbientBrighteningPercentagesIdle[n]
-     * level[MAX] <= value             = mAmbientBrighteningPercentagesIdle[MAX]
-     *
-     * @return the ambient brightness levels from 0 lux upwards for which each
-     * mAmbientBrighteningPercentagesIdle applies
-     */
-    public float[] getAmbientBrighteningLevelsIdle() {
-        return mAmbientBrighteningLevelsIdle;
-    }
-
-    /**
-     * The array that describes the ambient brightness threshold percentage change whilst in
-     * idle screen brightness mode at each ambient brightness level described in
-     * mAmbientBrighteningLevelsIdle.
-     *
-     * @return the percentages between 0 and 100 of ambient brightness increase required in order
-     * for the screen brightness to change
-     */
-    public float[] getAmbientBrighteningPercentagesIdle() {
-        return mAmbientBrighteningPercentagesIdle;
-    }
-
-    /**
-     * The array that describes the range of ambient brightness that each threshold percentage
-     * applies within whilst in idle screen brightness mode.
-     *
-     * The (zero-based) index is calculated as follows
-     * value = current ambient brightness value
-     * level = mAmbientDarkeningLevelsIdle
-     *
-     * condition                       return
-     * value < level[0]                = 0.0f
-     * level[n] <= value < level[n+1]  = mAmbientDarkeningPercentagesIdle[n]
-     * level[MAX] <= value             = mAmbientDarkeningPercentagesIdle[MAX]
-     *
-     * @return the ambient brightness levels from 0 lux upwards for which each
-     * mAmbientDarkeningPercentagesIdle applies
-     */
-    public float[] getAmbientDarkeningLevelsIdle() {
-        return mAmbientDarkeningLevelsIdle;
-    }
-
-    /**
-     * The array that describes the ambient brightness threshold percentage change whilst in
-     * idle screen brightness mode at each ambient brightness level described in
-     * mAmbientDarkeningLevelsIdle.
-     *
-     * @return the percentages between 0 and 100 of ambient brightness decrease required in order
-     * for the screen brightness to change
-     */
-    public float[] getAmbientDarkeningPercentagesIdle() {
-        return mAmbientDarkeningPercentagesIdle;
+    public HysteresisLevels getScreenBrightnessIdleHysteresis() {
+        return mScreenBrightnessIdleHysteresis;
     }
 
     public SensorData getAmbientLightSensor() {
@@ -1985,49 +1650,13 @@
                 + "mAmbientHorizonLong=" + mAmbientHorizonLong
                 + ", mAmbientHorizonShort=" + mAmbientHorizonShort
                 + "\n"
-                + "mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
-                + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
-                + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
-                + ", mScreenBrighteningMinThresholdIdle=" + mScreenBrighteningMinThresholdIdle
-                + ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold
-                + ", mAmbientLuxDarkeningMinThresholdIdle=" + mAmbientLuxDarkeningMinThresholdIdle
-                + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold
-                + ", mAmbientLuxBrighteningMinThresholdIdle="
-                + mAmbientLuxBrighteningMinThresholdIdle
+                + "mAmbientBrightnessHysteresis=" + mAmbientBrightnessHysteresis
                 + "\n"
-                + "mScreenBrighteningLevels=" + Arrays.toString(
-                mScreenBrighteningLevels)
-                + ", mScreenBrighteningPercentages=" + Arrays.toString(
-                mScreenBrighteningPercentages)
-                + ", mScreenDarkeningLevels=" + Arrays.toString(
-                mScreenDarkeningLevels)
-                + ", mScreenDarkeningPercentages=" + Arrays.toString(
-                mScreenDarkeningPercentages)
-                + ", mAmbientBrighteningLevels=" + Arrays.toString(
-                mAmbientBrighteningLevels)
-                + ", mAmbientBrighteningPercentages=" + Arrays.toString(
-                mAmbientBrighteningPercentages)
-                + ", mAmbientDarkeningLevels=" + Arrays.toString(
-                mAmbientDarkeningLevels)
-                + ", mAmbientDarkeningPercentages=" + Arrays.toString(
-                mAmbientDarkeningPercentages)
+                + "mAmbientIdleHysteresis=" + mAmbientBrightnessIdleHysteresis
                 + "\n"
-                + "mAmbientBrighteningLevelsIdle=" + Arrays.toString(
-                mAmbientBrighteningLevelsIdle)
-                + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString(
-                mAmbientBrighteningPercentagesIdle)
-                + ", mAmbientDarkeningLevelsIdle=" + Arrays.toString(
-                mAmbientDarkeningLevelsIdle)
-                + ", mAmbientDarkeningPercentagesIdle=" + Arrays.toString(
-                mAmbientDarkeningPercentagesIdle)
-                + ", mScreenBrighteningLevelsIdle=" + Arrays.toString(
-                mScreenBrighteningLevelsIdle)
-                + ", mScreenBrighteningPercentagesIdle=" + Arrays.toString(
-                mScreenBrighteningPercentagesIdle)
-                + ", mScreenDarkeningLevelsIdle=" + Arrays.toString(
-                mScreenDarkeningLevelsIdle)
-                + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString(
-                mScreenDarkeningPercentagesIdle)
+                + "mScreenBrightnessHysteresis=" + mScreenBrightnessHysteresis
+                + "\n"
+                + "mScreenBrightnessIdleHysteresis=" + mScreenBrightnessIdleHysteresis
                 + "\n"
                 + "mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mScreenOffBrightnessSensor=" + mScreenOffBrightnessSensor
@@ -3137,281 +2766,15 @@
     }
 
     private void loadBrightnessChangeThresholds(DisplayConfiguration config) {
-        loadDisplayBrightnessThresholds(config);
-        loadAmbientBrightnessThresholds(config);
-        loadDisplayBrightnessThresholdsIdle(config);
-        loadAmbientBrightnessThresholdsIdle(config);
-    }
-
-    private void loadDisplayBrightnessThresholds(DisplayConfiguration config) {
-        BrightnessThresholds brighteningScreen = null;
-        BrightnessThresholds darkeningScreen = null;
-        if (config != null && config.getDisplayBrightnessChangeThresholds() != null) {
-            brighteningScreen =
-                    config.getDisplayBrightnessChangeThresholds().getBrighteningThresholds();
-            darkeningScreen =
-                    config.getDisplayBrightnessChangeThresholds().getDarkeningThresholds();
-
-        }
-
-        // Screen bright/darkening threshold levels for active mode
-        Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
-                brighteningScreen,
-                com.android.internal.R.array.config_screenThresholdLevels,
-                com.android.internal.R.array.config_screenBrighteningThresholds,
-                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
-                /* potentialOldBrightnessScale= */ true);
-
-        mScreenBrighteningLevels = screenBrighteningPair.first;
-        mScreenBrighteningPercentages = screenBrighteningPair.second;
-
-        Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
-                darkeningScreen,
-                com.android.internal.R.array.config_screenThresholdLevels,
-                com.android.internal.R.array.config_screenDarkeningThresholds,
-                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
-                /* potentialOldBrightnessScale= */ true);
-        mScreenDarkeningLevels = screenDarkeningPair.first;
-        mScreenDarkeningPercentages = screenDarkeningPair.second;
-
-        // Screen bright/darkening threshold minimums for active mode
-        if (brighteningScreen != null && brighteningScreen.getMinimum() != null) {
-            mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue();
-        }
-        if (darkeningScreen != null && darkeningScreen.getMinimum() != null) {
-            mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue();
-        }
-    }
-
-    private void loadAmbientBrightnessThresholds(DisplayConfiguration config) {
-        // Ambient Brightness Threshold Levels
-        BrightnessThresholds brighteningAmbientLux = null;
-        BrightnessThresholds darkeningAmbientLux = null;
-        if (config != null && config.getAmbientBrightnessChangeThresholds() != null) {
-            brighteningAmbientLux =
-                    config.getAmbientBrightnessChangeThresholds().getBrighteningThresholds();
-            darkeningAmbientLux =
-                    config.getAmbientBrightnessChangeThresholds().getDarkeningThresholds();
-        }
-
-        // Ambient bright/darkening threshold levels for active mode
-        Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
-                brighteningAmbientLux,
-                com.android.internal.R.array.config_ambientThresholdLevels,
-                com.android.internal.R.array.config_ambientBrighteningThresholds,
-                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
-        mAmbientBrighteningLevels = ambientBrighteningPair.first;
-        mAmbientBrighteningPercentages = ambientBrighteningPair.second;
-
-        Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
-                darkeningAmbientLux,
-                com.android.internal.R.array.config_ambientThresholdLevels,
-                com.android.internal.R.array.config_ambientDarkeningThresholds,
-                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
-        mAmbientDarkeningLevels = ambientDarkeningPair.first;
-        mAmbientDarkeningPercentages = ambientDarkeningPair.second;
-
-        // Ambient bright/darkening threshold minimums for active/idle mode
-        if (brighteningAmbientLux != null && brighteningAmbientLux.getMinimum() != null) {
-            mAmbientLuxBrighteningMinThreshold =
-                    brighteningAmbientLux.getMinimum().floatValue();
-        }
-
-        if (darkeningAmbientLux != null && darkeningAmbientLux.getMinimum() != null) {
-            mAmbientLuxDarkeningMinThreshold = darkeningAmbientLux.getMinimum().floatValue();
-        }
-    }
-
-    private void loadDisplayBrightnessThresholdsIdle(DisplayConfiguration config) {
-        BrightnessThresholds brighteningScreenIdle = null;
-        BrightnessThresholds darkeningScreenIdle = null;
-        if (config != null && config.getDisplayBrightnessChangeThresholdsIdle() != null) {
-            brighteningScreenIdle =
-                    config.getDisplayBrightnessChangeThresholdsIdle().getBrighteningThresholds();
-            darkeningScreenIdle =
-                    config.getDisplayBrightnessChangeThresholdsIdle().getDarkeningThresholds();
-        }
-
-        Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage(
-                brighteningScreenIdle,
-                com.android.internal.R.array.config_screenThresholdLevels,
-                com.android.internal.R.array.config_screenBrighteningThresholds,
-                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
-                /* potentialOldBrightnessScale= */ true);
-        mScreenBrighteningLevelsIdle = screenBrighteningPair.first;
-        mScreenBrighteningPercentagesIdle = screenBrighteningPair.second;
-
-        Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage(
-                darkeningScreenIdle,
-                com.android.internal.R.array.config_screenThresholdLevels,
-                com.android.internal.R.array.config_screenDarkeningThresholds,
-                DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS,
-                /* potentialOldBrightnessScale= */ true);
-        mScreenDarkeningLevelsIdle = screenDarkeningPair.first;
-        mScreenDarkeningPercentagesIdle = screenDarkeningPair.second;
-
-        if (brighteningScreenIdle != null
-                && brighteningScreenIdle.getMinimum() != null) {
-            mScreenBrighteningMinThresholdIdle =
-                    brighteningScreenIdle.getMinimum().floatValue();
-        }
-        if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) {
-            mScreenDarkeningMinThresholdIdle =
-                    darkeningScreenIdle.getMinimum().floatValue();
-        }
-    }
-
-    private void loadAmbientBrightnessThresholdsIdle(DisplayConfiguration config) {
-        BrightnessThresholds brighteningAmbientLuxIdle = null;
-        BrightnessThresholds darkeningAmbientLuxIdle = null;
-        if (config != null && config.getAmbientBrightnessChangeThresholdsIdle() != null) {
-            brighteningAmbientLuxIdle =
-                    config.getAmbientBrightnessChangeThresholdsIdle().getBrighteningThresholds();
-            darkeningAmbientLuxIdle =
-                    config.getAmbientBrightnessChangeThresholdsIdle().getDarkeningThresholds();
-        }
-
-        Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage(
-                brighteningAmbientLuxIdle,
-                com.android.internal.R.array.config_ambientThresholdLevels,
-                com.android.internal.R.array.config_ambientBrighteningThresholds,
-                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS);
-        mAmbientBrighteningLevelsIdle = ambientBrighteningPair.first;
-        mAmbientBrighteningPercentagesIdle = ambientBrighteningPair.second;
-
-        Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage(
-                darkeningAmbientLuxIdle,
-                com.android.internal.R.array.config_ambientThresholdLevels,
-                com.android.internal.R.array.config_ambientDarkeningThresholds,
-                DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS);
-        mAmbientDarkeningLevelsIdle = ambientDarkeningPair.first;
-        mAmbientDarkeningPercentagesIdle = ambientDarkeningPair.second;
-
-        if (brighteningAmbientLuxIdle != null
-                && brighteningAmbientLuxIdle.getMinimum() != null) {
-            mAmbientLuxBrighteningMinThresholdIdle =
-                    brighteningAmbientLuxIdle.getMinimum().floatValue();
-        }
-
-        if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) {
-            mAmbientLuxDarkeningMinThresholdIdle =
-                    darkeningAmbientLuxIdle.getMinimum().floatValue();
-        }
-    }
-
-    private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
-            int configFallbackThreshold, int configFallbackPercentage, float[] defaultLevels,
-            float[] defaultPercentage) {
-        return getBrightnessLevelAndPercentage(thresholds, configFallbackThreshold,
-                configFallbackPercentage, defaultLevels, defaultPercentage, false);
-    }
-
-    // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
-    // percentages for brightness levels at or above the lux value.
-    // Historically, config.xml would have an array for brightness levels that was 1 shorter than
-    // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
-    // rest of the framework. Values were also defined in different units (permille vs percent).
-    private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds,
-            int configFallbackThreshold, int configFallbackPermille,
-            float[] defaultLevels, float[] defaultPercentage,
-            boolean potentialOldBrightnessScale) {
-        if (thresholds != null
-                && thresholds.getBrightnessThresholdPoints() != null
-                && thresholds.getBrightnessThresholdPoints()
-                        .getBrightnessThresholdPoint().size() != 0) {
-
-            // The level and percentages arrays are equal length in the ddc (new system)
-            List<ThresholdPoint> points =
-                    thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
-            final int size = points.size();
-
-            float[] thresholdLevels = new float[size];
-            float[] thresholdPercentages = new float[size];
-
-            int i = 0;
-            for (ThresholdPoint point : points) {
-                thresholdLevels[i] = point.getThreshold().floatValue();
-                thresholdPercentages[i] = point.getPercentage().floatValue();
-                i++;
-            }
-            return new Pair<>(thresholdLevels, thresholdPercentages);
-        } else {
-            // The level and percentages arrays are unequal length in config.xml (old system)
-            // We prefix the array with a 0 value to ensure they can be handled consistently
-            // with the new system.
-
-            // Load levels array
-            int[] configThresholdArray = mContext.getResources().getIntArray(
-                    configFallbackThreshold);
-            int configThresholdsSize;
-            if (configThresholdArray == null || configThresholdArray.length == 0) {
-                configThresholdsSize = 1;
-            } else {
-                configThresholdsSize = configThresholdArray.length + 1;
-            }
-
-
-            // Load percentage array
-            int[] configPermille = mContext.getResources().getIntArray(
-                    configFallbackPermille);
-
-            // Ensure lengths match up
-            boolean emptyArray = configPermille == null || configPermille.length == 0;
-            if (emptyArray && configThresholdsSize == 1) {
-                return new Pair<>(defaultLevels, defaultPercentage);
-            }
-            if (emptyArray || configPermille.length != configThresholdsSize) {
-                throw new IllegalArgumentException(
-                        "Brightness threshold arrays do not align in length");
-            }
-
-            // Calculate levels array
-            float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
-            // Start at 1, so that 0 index value is 0.0f (default)
-            for (int i = 1; i < configThresholdsSize; i++) {
-                configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
-            }
-            if (potentialOldBrightnessScale) {
-                configThresholdWithZeroPrefixed =
-                        constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
-            }
-
-            // Calculate percentages array
-            float[] configPercentage = new float[configThresholdsSize];
-            for (int i = 0; i < configPermille.length; i++) {
-                configPercentage[i] = configPermille[i] / 10.0f;
-            }            return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
-        }
-    }
-
-    /**
-     * This check is due to historical reasons, where screen thresholdLevels used to be
-     * integer values in the range of [0-255], but then was changed to be float values from [0,1].
-     * To accommodate both the possibilities, we first check if all the thresholdLevels are in
-     * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
-     */
-    private float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
-        if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
-                /* maxValueInclusive= */ 1.0f)) {
-            return thresholdLevels;
-        }
-
-        Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
-        float[] thresholdLevelsScaled = new float[thresholdLevels.length];
-        for (int index = 0; thresholdLevels.length > index; ++index) {
-            thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
-        }
-        return thresholdLevelsScaled;
-    }
-
-    private boolean isAllInRange(float[] configArray, float minValueInclusive,
-            float maxValueInclusive) {
-        for (float v : configArray) {
-            if (v < minValueInclusive || v > maxValueInclusive) {
-                return false;
-            }
-        }
-        return true;
+        Resources res = mContext.getResources();
+        mScreenBrightnessHysteresis =
+                HysteresisLevels.loadDisplayBrightnessConfig(config, res);
+        mScreenBrightnessIdleHysteresis =
+                HysteresisLevels.loadDisplayBrightnessIdleConfig(config, res);
+        mAmbientBrightnessHysteresis =
+                HysteresisLevels.loadAmbientBrightnessConfig(config, res);
+        mAmbientBrightnessIdleHysteresis =
+                HysteresisLevels.loadAmbientBrightnessIdleConfig(config, res);
     }
 
     private boolean thermalStatusIsValid(ThermalStatus value) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index b21a89a..404c3ad 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -86,6 +86,7 @@
 import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
 import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
 import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
+import com.android.server.display.config.HysteresisLevels;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.layout.Layout;
 import com.android.server.display.state.DisplayStateController;
@@ -1050,76 +1051,20 @@
 
         if (defaultModeBrightnessMapper != null) {
             // Ambient Lux - Active Mode Brightness Thresholds
-            float[] ambientBrighteningThresholds =
-                    mDisplayDeviceConfig.getAmbientBrighteningPercentages();
-            float[] ambientDarkeningThresholds =
-                    mDisplayDeviceConfig.getAmbientDarkeningPercentages();
-            float[] ambientBrighteningLevels =
-                    mDisplayDeviceConfig.getAmbientBrighteningLevels();
-            float[] ambientDarkeningLevels =
-                    mDisplayDeviceConfig.getAmbientDarkeningLevels();
-            float ambientDarkeningMinThreshold =
-                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold();
-            float ambientBrighteningMinThreshold =
-                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold();
-            HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels(
-                    ambientBrighteningThresholds, ambientDarkeningThresholds,
-                    ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold,
-                    ambientBrighteningMinThreshold);
+            HysteresisLevels ambientBrightnessThresholds =
+                    mDisplayDeviceConfig.getAmbientBrightnessHysteresis();
 
             // Display - Active Mode Brightness Thresholds
-            float[] screenBrighteningThresholds =
-                    mDisplayDeviceConfig.getScreenBrighteningPercentages();
-            float[] screenDarkeningThresholds =
-                    mDisplayDeviceConfig.getScreenDarkeningPercentages();
-            float[] screenBrighteningLevels =
-                    mDisplayDeviceConfig.getScreenBrighteningLevels();
-            float[] screenDarkeningLevels =
-                    mDisplayDeviceConfig.getScreenDarkeningLevels();
-            float screenDarkeningMinThreshold =
-                    mDisplayDeviceConfig.getScreenDarkeningMinThreshold();
-            float screenBrighteningMinThreshold =
-                    mDisplayDeviceConfig.getScreenBrighteningMinThreshold();
-            HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels(
-                    screenBrighteningThresholds, screenDarkeningThresholds,
-                    screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold,
-                    screenBrighteningMinThreshold, true);
+            HysteresisLevels screenBrightnessThresholds =
+                    mDisplayDeviceConfig.getScreenBrightnessHysteresis();
 
             // Ambient Lux - Idle Screen Brightness Thresholds
-            float ambientDarkeningMinThresholdIdle =
-                    mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle();
-            float ambientBrighteningMinThresholdIdle =
-                    mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle();
-            float[] ambientBrighteningThresholdsIdle =
-                    mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle();
-            float[] ambientDarkeningThresholdsIdle =
-                    mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle();
-            float[] ambientBrighteningLevelsIdle =
-                    mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle();
-            float[] ambientDarkeningLevelsIdle =
-                    mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle();
-            HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
-                    ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle,
-                    ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle,
-                    ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle);
+            HysteresisLevels ambientBrightnessThresholdsIdle =
+                    mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis();
 
             // Display - Idle Screen Brightness Thresholds
-            float screenDarkeningMinThresholdIdle =
-                    mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle();
-            float screenBrighteningMinThresholdIdle =
-                    mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle();
-            float[] screenBrighteningThresholdsIdle =
-                    mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle();
-            float[] screenDarkeningThresholdsIdle =
-                    mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle();
-            float[] screenBrighteningLevelsIdle =
-                    mDisplayDeviceConfig.getScreenBrighteningLevelsIdle();
-            float[] screenDarkeningLevelsIdle =
-                    mDisplayDeviceConfig.getScreenDarkeningLevelsIdle();
-            HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels(
-                    screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle,
-                    screenBrighteningLevelsIdle, screenDarkeningLevelsIdle,
-                    screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle);
+            HysteresisLevels screenBrightnessThresholdsIdle =
+                    mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis();
 
             long brighteningLightDebounce = mDisplayDeviceConfig
                     .getAutoBrightnessBrighteningLightDebounce();
@@ -3208,25 +3153,6 @@
                     AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
         }
 
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold) {
-            return new HysteresisLevels(brighteningThresholdsPercentages,
-                    darkeningThresholdsPercentages, brighteningThresholdLevels,
-                    darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold);
-        }
-
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
-            return new HysteresisLevels(brighteningThresholdsPercentages,
-                    darkeningThresholdsPercentages, brighteningThresholdLevels,
-                    darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold,
-                    potentialOldBrightnessRange);
-        }
-
         ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
                 SensorManager sensorManager,
                 Sensor lightSensor,
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
deleted file mode 100644
index 0521b8a..0000000
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.display;
-
-import android.util.Slog;
-
-import com.android.server.display.utils.DebugUtils;
-
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-/**
- * A helper class for handling access to illuminance hysteresis level values.
- */
-public class HysteresisLevels {
-    private static final String TAG = "HysteresisLevels";
-
-    // To enable these logs, run:
-    // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot'
-    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
-
-    private final float[] mBrighteningThresholdsPercentages;
-    private final float[] mDarkeningThresholdsPercentages;
-    private final float[] mBrighteningThresholdLevels;
-    private final float[] mDarkeningThresholdLevels;
-    private final float mMinDarkening;
-    private final float mMinBrightening;
-
-    /**
-     * Creates a {@code HysteresisLevels} object with the given equal-length
-     * float arrays.
-     * @param brighteningThresholdsPercentages 0-100 of thresholds
-     * @param darkeningThresholdsPercentages 0-100 of thresholds
-     * @param brighteningThresholdLevels float array of brightness values in the relevant units
-     * @param minBrighteningThreshold the minimum value for which the brightening value needs to
-     *                                return.
-     * @param minDarkeningThreshold the minimum value for which the darkening value needs to return.
-     * @param potentialOldBrightnessRange whether or not the values used could be from the old
-     *                                    screen brightness range ie, between 1-255.
-    */
-    HysteresisLevels(float[] brighteningThresholdsPercentages,
-            float[] darkeningThresholdsPercentages,
-            float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
-            float minDarkeningThreshold, float minBrighteningThreshold,
-            boolean potentialOldBrightnessRange) {
-        if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
-                || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
-            throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
-        }
-        mBrighteningThresholdsPercentages =
-                setArrayFormat(brighteningThresholdsPercentages, 100.0f);
-        mDarkeningThresholdsPercentages =
-                setArrayFormat(darkeningThresholdsPercentages, 100.0f);
-        mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
-        mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
-        mMinDarkening = minDarkeningThreshold;
-        mMinBrightening = minBrighteningThreshold;
-    }
-
-    HysteresisLevels(float[] brighteningThresholdsPercentages,
-            float[] darkeningThresholdsPercentages,
-            float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
-            float minDarkeningThreshold, float minBrighteningThreshold) {
-        this(brighteningThresholdsPercentages, darkeningThresholdsPercentages,
-                brighteningThresholdLevels, darkeningThresholdLevels, minDarkeningThreshold,
-                minBrighteningThreshold, false);
-    }
-
-    /**
-     * Return the brightening hysteresis threshold for the given value level.
-     */
-    public float getBrighteningThreshold(float value) {
-        final float brightConstant = getReferenceLevel(value,
-                mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
-
-        float brightThreshold = value * (1.0f + brightConstant);
-        if (DEBUG) {
-            Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
-                    + brightThreshold + ", value=" + value);
-        }
-
-        brightThreshold = Math.max(brightThreshold, value + mMinBrightening);
-        return brightThreshold;
-    }
-
-    /**
-     * Return the darkening hysteresis threshold for the given value level.
-     */
-    public float getDarkeningThreshold(float value) {
-        final float darkConstant = getReferenceLevel(value,
-                mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
-        float darkThreshold = value * (1.0f - darkConstant);
-        if (DEBUG) {
-            Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
-                    + darkThreshold + ", value=" + value);
-        }
-        darkThreshold = Math.min(darkThreshold, value - mMinDarkening);
-        return Math.max(darkThreshold, 0.0f);
-    }
-
-    /**
-     * Return the hysteresis constant for the closest threshold value from the given array.
-     */
-    private float getReferenceLevel(float value, float[] thresholdLevels,
-            float[] thresholdPercentages) {
-        if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
-            return 0.0f;
-        }
-        int index = 0;
-        while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
-            index++;
-        }
-        return thresholdPercentages[index];
-    }
-
-    /**
-     * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
-     */
-    private float[] setArrayFormat(float[] configArray, float divideFactor) {
-        float[] levelArray = new float[configArray.length];
-        for (int index = 0; levelArray.length > index; ++index) {
-            levelArray[index] = configArray[index] / divideFactor;
-        }
-        return levelArray;
-    }
-
-    void dump(PrintWriter pw) {
-        pw.println("HysteresisLevels");
-        pw.println("  mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels));
-        pw.println("  mBrighteningThresholdsPercentages="
-                + Arrays.toString(mBrighteningThresholdsPercentages));
-        pw.println("  mMinBrightening=" + mMinBrightening);
-        pw.println("  mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels));
-        pw.println("  mDarkeningThresholdsPercentages="
-                + Arrays.toString(mDarkeningThresholdsPercentages));
-        pw.println("  mMinDarkening=" + mMinDarkening);
-    }
-}
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 22e4bc5..189e366 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -482,7 +482,8 @@
             int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
             int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
 
-            if (mIsAnisotropyCorrectionEnabled && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
+            if (mIsAnisotropyCorrectionEnabled && deviceInfo.type == Display.TYPE_EXTERNAL
+                        && deviceInfo.xDpi > 0 && deviceInfo.yDpi > 0) {
                 if (deviceInfo.xDpi > deviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
                     maskedHeight = (int) (maskedHeight * deviceInfo.xDpi / deviceInfo.yDpi + 0.5);
                 } else if (deviceInfo.xDpi * DisplayDevice.MAX_ANISOTROPY < deviceInfo.yDpi) {
@@ -711,8 +712,8 @@
         var displayLogicalWidth = displayInfo.logicalWidth;
         var displayLogicalHeight = displayInfo.logicalHeight;
 
-        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.xDpi > 0
-                    && displayDeviceInfo.yDpi > 0) {
+        if (mIsAnisotropyCorrectionEnabled && displayDeviceInfo.type == Display.TYPE_EXTERNAL
+                    && displayDeviceInfo.xDpi > 0 && displayDeviceInfo.yDpi > 0) {
             if (displayDeviceInfo.xDpi > displayDeviceInfo.yDpi * DisplayDevice.MAX_ANISOTROPY) {
                 var scalingFactor = displayDeviceInfo.yDpi / displayDeviceInfo.xDpi;
                 if (rotated) {
diff --git a/services/core/java/com/android/server/display/config/HysteresisLevels.java b/services/core/java/com/android/server/display/config/HysteresisLevels.java
new file mode 100644
index 0000000..e659d88
--- /dev/null
+++ b/services/core/java/com/android/server/display/config/HysteresisLevels.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2016 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.ArrayRes;
+import android.annotation.Nullable;
+import android.content.res.Resources;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.utils.DebugUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A helper class for handling access to illuminance hysteresis level values.
+ */
+public class HysteresisLevels {
+    private static final String TAG = "HysteresisLevels";
+
+    private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f};
+    private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f};
+    private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f};
+    private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f};
+    private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f};
+    private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f};
+
+    // To enable these logs, run:
+    // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot'
+    private static final boolean DEBUG = DebugUtils.isDebuggable(TAG);
+
+    /**
+     * The array that describes the brightness threshold percentage change
+     * at each brightness level described in mBrighteningThresholdLevels.
+     */
+    private final float[] mBrighteningThresholdsPercentages;
+
+    /**
+     * The array that describes the brightness threshold percentage change
+     * at each brightness level described in mDarkeningThresholdLevels.
+     */
+    private final float[] mDarkeningThresholdsPercentages;
+
+    /**
+     * The array that describes the range of brightness that each threshold percentage applies to
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current brightness value
+     * level = mBrighteningThresholdLevels
+     *
+     * condition                       return
+     * value < mBrighteningThresholdLevels[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mBrighteningThresholdsPercentages[n]
+     * level[MAX] <= value             = mBrighteningThresholdsPercentages[MAX]
+     */
+    private final float[] mBrighteningThresholdLevels;
+
+    /**
+     * The array that describes the range of brightness that each threshold percentage applies to
+     *
+     * The (zero-based) index is calculated as follows
+     * value = current brightness value
+     * level = mDarkeningThresholdLevels
+     *
+     * condition                       return
+     * value < level[0]                = 0.0f
+     * level[n] <= value < level[n+1]  = mDarkeningThresholdsPercentages[n]
+     * level[MAX] <= value             = mDarkeningThresholdsPercentages[MAX]
+     */
+    private final float[] mDarkeningThresholdLevels;
+
+    /**
+     * The minimum value decrease for darkening event
+     */
+    private final float mMinDarkening;
+
+    /**
+     * The minimum value increase for brightening event.
+     */
+    private final float mMinBrightening;
+
+    /**
+     * Creates a {@code HysteresisLevels} object with the given equal-length
+     * float arrays.
+     *
+     * @param brighteningThresholdsPercentages 0-100 of thresholds
+     * @param darkeningThresholdsPercentages   0-100 of thresholds
+     * @param brighteningThresholdLevels       float array of brightness values in the relevant
+     *                                         units
+     * @param minBrighteningThreshold          the minimum value for which the brightening value
+     *                                         needs to
+     *                                         return.
+     * @param minDarkeningThreshold            the minimum value for which the darkening value needs
+     *                                         to return.
+     */
+    @VisibleForTesting
+    public HysteresisLevels(float[] brighteningThresholdsPercentages,
+            float[] darkeningThresholdsPercentages,
+            float[] brighteningThresholdLevels, float[] darkeningThresholdLevels,
+            float minDarkeningThreshold, float minBrighteningThreshold) {
+        if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length
+                || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) {
+            throw new IllegalArgumentException("Mismatch between hysteresis array lengths.");
+        }
+        mBrighteningThresholdsPercentages =
+                setArrayFormat(brighteningThresholdsPercentages, 100.0f);
+        mDarkeningThresholdsPercentages =
+                setArrayFormat(darkeningThresholdsPercentages, 100.0f);
+        mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f);
+        mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f);
+        mMinDarkening = minDarkeningThreshold;
+        mMinBrightening = minBrighteningThreshold;
+    }
+
+    /**
+     * Return the brightening hysteresis threshold for the given value level.
+     */
+    public float getBrighteningThreshold(float value) {
+        final float brightConstant = getReferenceLevel(value,
+                mBrighteningThresholdLevels, mBrighteningThresholdsPercentages);
+
+        float brightThreshold = value * (1.0f + brightConstant);
+        if (DEBUG) {
+            Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
+                    + brightThreshold + ", value=" + value);
+        }
+
+        brightThreshold = Math.max(brightThreshold, value + mMinBrightening);
+        return brightThreshold;
+    }
+
+    /**
+     * Return the darkening hysteresis threshold for the given value level.
+     */
+    public float getDarkeningThreshold(float value) {
+        final float darkConstant = getReferenceLevel(value,
+                mDarkeningThresholdLevels, mDarkeningThresholdsPercentages);
+        float darkThreshold = value * (1.0f - darkConstant);
+        if (DEBUG) {
+            Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
+                    + darkThreshold + ", value=" + value);
+        }
+        darkThreshold = Math.min(darkThreshold, value - mMinDarkening);
+        return Math.max(darkThreshold, 0.0f);
+    }
+
+    @VisibleForTesting
+    public float[] getBrighteningThresholdsPercentages() {
+        return mBrighteningThresholdsPercentages;
+    }
+
+    @VisibleForTesting
+    public float[] getDarkeningThresholdsPercentages() {
+        return mDarkeningThresholdsPercentages;
+    }
+
+    @VisibleForTesting
+    public float[] getBrighteningThresholdLevels() {
+        return mBrighteningThresholdLevels;
+    }
+
+    @VisibleForTesting
+    public float[] getDarkeningThresholdLevels() {
+        return mDarkeningThresholdLevels;
+    }
+
+    @VisibleForTesting
+    public float getMinDarkening() {
+        return mMinDarkening;
+    }
+
+    @VisibleForTesting
+    public float getMinBrightening() {
+        return mMinBrightening;
+    }
+
+    /**
+     * Return the hysteresis constant for the closest threshold value from the given array.
+     */
+    private float getReferenceLevel(float value, float[] thresholdLevels,
+            float[] thresholdPercentages) {
+        if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) {
+            return 0.0f;
+        }
+        int index = 0;
+        while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) {
+            index++;
+        }
+        return thresholdPercentages[index];
+    }
+
+    /**
+     * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}.
+     */
+    private float[] setArrayFormat(float[] configArray, float divideFactor) {
+        float[] levelArray = new float[configArray.length];
+        for (int index = 0; levelArray.length > index; ++index) {
+            levelArray[index] = configArray[index] / divideFactor;
+        }
+        return levelArray;
+    }
+
+    @Override
+    public String toString() {
+        return "HysteresisLevels {"
+                + "\n"
+                + "    mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels)
+                + ",\n"
+                + "    mBrighteningThresholdsPercentages="
+                + Arrays.toString(mBrighteningThresholdsPercentages)
+                + ",\n"
+                + "    mMinBrightening=" + mMinBrightening
+                + ",\n"
+                + "    mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels)
+                + ",\n"
+                + "    mDarkeningThresholdsPercentages="
+                + Arrays.toString(mDarkeningThresholdsPercentages)
+                + ",\n"
+                + "    mMinDarkening=" + mMinDarkening
+                + "\n"
+                + "}";
+    }
+
+    /**
+     * Creates hysteresis levels for Active Ambient Lux
+     */
+    public static HysteresisLevels loadAmbientBrightnessConfig(
+            @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+        return createHysteresisLevels(
+                config == null ? null : config.getAmbientBrightnessChangeThresholds(),
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientBrighteningThresholds,
+                com.android.internal.R.array.config_ambientDarkeningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS,
+                DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS,
+                DEFAULT_AMBIENT_DARKENING_THRESHOLDS,
+                resources, /* potentialOldBrightnessScale= */ false);
+    }
+
+    /**
+     * Creates hysteresis levels for Active Screen Brightness
+     */
+    public static HysteresisLevels loadDisplayBrightnessConfig(
+            @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+        return createHysteresisLevels(
+                config == null ? null : config.getDisplayBrightnessChangeThresholds(),
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenBrighteningThresholds,
+                com.android.internal.R.array.config_screenDarkeningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS,
+                DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+                DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+                resources, /* potentialOldBrightnessScale= */ true);
+    }
+
+    /**
+     * Creates hysteresis levels for Idle Ambient Lux
+     */
+    public static HysteresisLevels loadAmbientBrightnessIdleConfig(
+            @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+        return createHysteresisLevels(
+                config == null ? null : config.getAmbientBrightnessChangeThresholdsIdle(),
+                com.android.internal.R.array.config_ambientThresholdLevels,
+                com.android.internal.R.array.config_ambientBrighteningThresholds,
+                com.android.internal.R.array.config_ambientDarkeningThresholds,
+                DEFAULT_AMBIENT_THRESHOLD_LEVELS,
+                DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS,
+                DEFAULT_AMBIENT_DARKENING_THRESHOLDS,
+                resources, /* potentialOldBrightnessScale= */ false);
+    }
+
+    /**
+     * Creates hysteresis levels for Idle Screen Brightness
+     */
+    public static HysteresisLevels loadDisplayBrightnessIdleConfig(
+            @Nullable DisplayConfiguration config, @Nullable Resources resources) {
+        return createHysteresisLevels(
+                config == null ? null : config.getDisplayBrightnessChangeThresholdsIdle(),
+                com.android.internal.R.array.config_screenThresholdLevels,
+                com.android.internal.R.array.config_screenBrighteningThresholds,
+                com.android.internal.R.array.config_screenDarkeningThresholds,
+                DEFAULT_SCREEN_THRESHOLD_LEVELS,
+                DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS,
+                DEFAULT_SCREEN_DARKENING_THRESHOLDS,
+                resources, /* potentialOldBrightnessScale= */ true);
+    }
+
+
+    private static HysteresisLevels createHysteresisLevels(
+            @Nullable Thresholds thresholds,
+            @ArrayRes int configLevels,
+            @ArrayRes int configBrighteningThresholds,
+            @ArrayRes int configDarkeningThresholds,
+            float[] defaultLevels,
+            float[] defaultBrighteningThresholds,
+            float[] defaultDarkeningThresholds,
+            @Nullable Resources resources,
+            boolean potentialOldBrightnessScale
+    ) {
+        BrightnessThresholds brighteningThresholds =
+                thresholds == null ? null : thresholds.getBrighteningThresholds();
+        BrightnessThresholds darkeningThresholds =
+                thresholds == null ? null : thresholds.getDarkeningThresholds();
+
+        Pair<float[], float[]> brighteningPair = getBrightnessLevelAndPercentage(
+                brighteningThresholds,
+                configLevels, configBrighteningThresholds,
+                defaultLevels, defaultBrighteningThresholds,
+                potentialOldBrightnessScale, resources);
+
+        Pair<float[], float[]> darkeningPair = getBrightnessLevelAndPercentage(
+                darkeningThresholds,
+                configLevels, configDarkeningThresholds,
+                defaultLevels, defaultDarkeningThresholds,
+                potentialOldBrightnessScale, resources);
+
+        float brighteningMinThreshold =
+                brighteningThresholds != null && brighteningThresholds.getMinimum() != null
+                        ? brighteningThresholds.getMinimum().floatValue() : 0f;
+        float darkeningMinThreshold =
+                darkeningThresholds != null && darkeningThresholds.getMinimum() != null
+                        ? darkeningThresholds.getMinimum().floatValue() : 0f;
+
+        return new HysteresisLevels(
+                brighteningPair.second,
+                darkeningPair.second,
+                brighteningPair.first,
+                darkeningPair.first,
+                darkeningMinThreshold,
+                brighteningMinThreshold
+        );
+    }
+
+    // Returns two float arrays, one of the brightness levels and one of the corresponding threshold
+    // percentages for brightness levels at or above the lux value.
+    // Historically, config.xml would have an array for brightness levels that was 1 shorter than
+    // the levels array. Now we prepend a 0 to this array so they can be treated the same in the
+    // rest of the framework. Values were also defined in different units (permille vs percent).
+    private static Pair<float[], float[]> getBrightnessLevelAndPercentage(
+            @Nullable BrightnessThresholds thresholds,
+            int configFallbackThreshold, int configFallbackPermille,
+            float[] defaultLevels, float[] defaultPercentage, boolean potentialOldBrightnessScale,
+            @Nullable Resources resources) {
+        if (thresholds != null
+                && thresholds.getBrightnessThresholdPoints() != null
+                && !thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint()
+                .isEmpty()) {
+
+            // The level and percentages arrays are equal length in the ddc (new system)
+            List<ThresholdPoint> points =
+                    thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint();
+            final int size = points.size();
+
+            float[] thresholdLevels = new float[size];
+            float[] thresholdPercentages = new float[size];
+
+            int i = 0;
+            for (ThresholdPoint point : points) {
+                thresholdLevels[i] = point.getThreshold().floatValue();
+                thresholdPercentages[i] = point.getPercentage().floatValue();
+                i++;
+            }
+            return new Pair<>(thresholdLevels, thresholdPercentages);
+        } else if (resources != null) {
+            // The level and percentages arrays are unequal length in config.xml (old system)
+            // We prefix the array with a 0 value to ensure they can be handled consistently
+            // with the new system.
+
+            // Load levels array
+            int[] configThresholdArray = resources.getIntArray(configFallbackThreshold);
+            int configThresholdsSize;
+            // null check is not needed here, however it test we are mocking resources that might
+            // return null
+            if (configThresholdArray == null || configThresholdArray.length == 0) {
+                configThresholdsSize = 1;
+            } else {
+                configThresholdsSize = configThresholdArray.length + 1;
+            }
+
+            // Load percentage array
+            int[] configPermille = resources.getIntArray(configFallbackPermille);
+
+            // Ensure lengths match up
+            // null check is not needed here, however it test we are mocking resources that might
+            // return null
+            boolean emptyArray = configPermille == null || configPermille.length == 0;
+            if (emptyArray && configThresholdsSize == 1) {
+                return new Pair<>(defaultLevels, defaultPercentage);
+            }
+            if (emptyArray || configPermille.length != configThresholdsSize) {
+                throw new IllegalArgumentException(
+                        "Brightness threshold arrays do not align in length");
+            }
+
+            // Calculate levels array
+            float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize];
+            // Start at 1, so that 0 index value is 0.0f (default)
+            for (int i = 1; i < configThresholdsSize; i++) {
+                configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1];
+            }
+            if (potentialOldBrightnessScale) {
+                configThresholdWithZeroPrefixed =
+                        constraintInRangeIfNeeded(configThresholdWithZeroPrefixed);
+            }
+
+            // Calculate percentages array
+            float[] configPercentage = new float[configThresholdsSize];
+            for (int i = 0; i < configPermille.length; i++) {
+                configPercentage[i] = configPermille[i] / 10.0f;
+            }
+            return new Pair<>(configThresholdWithZeroPrefixed, configPercentage);
+        } else {
+            return new Pair<>(defaultLevels, defaultPercentage);
+        }
+    }
+
+    /**
+     * This check is due to historical reasons, where screen thresholdLevels used to be
+     * integer values in the range of [0-255], but then was changed to be float values from [0,1].
+     * To accommodate both the possibilities, we first check if all the thresholdLevels are in
+     * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale.
+     */
+    private static float[] constraintInRangeIfNeeded(float[] thresholdLevels) {
+        if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f,
+                /* maxValueInclusive= */ 1.0f)) {
+            return thresholdLevels;
+        }
+
+        Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale");
+        float[] thresholdLevelsScaled = new float[thresholdLevels.length];
+        for (int index = 0; thresholdLevels.length > index; ++index) {
+            thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f;
+        }
+        return thresholdLevelsScaled;
+    }
+
+    private static boolean isAllInRange(float[] configArray, float minValueInclusive,
+            float maxValueInclusive) {
+        for (float v : configArray) {
+            if (v < minValueInclusive || v > maxValueInclusive) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 7f9c1cf..33b5a70 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -327,7 +327,8 @@
                                 UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)
                         .setProfileApiVisibility(
                                 UserProperties.PROFILE_API_VISIBILITY_HIDDEN)
-                        .setItemsRestrictedOnHomeScreen(true));
+                        .setItemsRestrictedOnHomeScreen(true)
+                        .setUpdateCrossProfileIntentFiltersOnOTA(true));
     }
 
     /**
diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 54de64e..64253e1 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -53,6 +53,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.display.brightness.clamper.BrightnessClamperController;
+import com.android.server.display.config.HysteresisLevels;
 import com.android.server.testutils.OffsettableClock;
 
 import org.junit.After;
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 494a667..b80d44f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -55,6 +55,7 @@
 
 import com.android.internal.R;
 import com.android.server.display.config.HdrBrightnessData;
+import com.android.server.display.config.HysteresisLevels;
 import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint;
 import com.android.server.display.config.ThermalStatus;
 import com.android.server.display.feature.DisplayManagerFlags;
@@ -169,53 +170,57 @@
         assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA);
 
         // Test thresholds
-        assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(),
-                ZERO_DELTA);
-        assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
-                ZERO_DELTA);
-        assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+        HysteresisLevels ambientHysteresis = mDisplayDeviceConfig.getAmbientBrightnessHysteresis();
+        HysteresisLevels ambientIdleHysteresis =
+                mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis();
+        HysteresisLevels screenHysteresis = mDisplayDeviceConfig.getScreenBrightnessHysteresis();
+        HysteresisLevels screenIdleHysteresis =
+                mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis();
+        assertEquals(10, ambientHysteresis.getMinBrightening(), ZERO_DELTA);
+        assertEquals(20, ambientIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+        assertEquals(30, ambientHysteresis.getMinDarkening(), ZERO_DELTA);
+        assertEquals(40, ambientIdleHysteresis.getMinDarkening(), ZERO_DELTA);
 
-        assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
-        assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
-        assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+        assertEquals(0.1f, screenHysteresis.getMinBrightening(), ZERO_DELTA);
+        assertEquals(0.2f, screenIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+        assertEquals(0.3f, screenHysteresis.getMinDarkening(), ZERO_DELTA);
+        assertEquals(0.4f, screenIdleHysteresis.getMinDarkening(), ZERO_DELTA);
 
         assertArrayEquals(new float[]{0, 0.10f, 0.20f},
-                mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{9, 10, 11},
-                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+                screenHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.09f, 0.10f, 0.11f},
+                screenHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
         assertArrayEquals(new float[]{0, 0.11f, 0.21f},
-                mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{11, 12, 13},
-                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+                screenHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.11f, 0.12f, 0.13f},
+                screenHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
 
         assertArrayEquals(new float[]{0, 100, 200},
-                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{13, 14, 15},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+                ambientHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.13f, 0.14f, 0.15f},
+                ambientHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
         assertArrayEquals(new float[]{0, 300, 400},
-                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{15, 16, 17},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+                ambientHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.15f, 0.16f, 0.17f},
+                ambientHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
 
         assertArrayEquals(new float[]{0, 0.12f, 0.22f},
-                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{17, 18, 19},
-                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+                screenIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.17f, 0.18f, 0.19f},
+                screenIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
         assertArrayEquals(new float[]{0, 0.13f, 0.23f},
-                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{19, 20, 21},
-                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+                screenIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.19f, 0.20f, 0.21f},
+                screenIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
 
         assertArrayEquals(new float[]{0, 500, 600},
-                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{21, 22, 23},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+                ambientIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.21f, 0.22f, 0.23f},
+                ambientIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
         assertArrayEquals(new float[]{0, 700, 800},
-                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{23, 24, 25},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+                ambientIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.23f, 0.24f, 0.25f},
+                ambientIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
 
         assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate());
         assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate());
@@ -686,55 +691,60 @@
                 new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
                         brightnessIntToFloat(150)}, SMALL_DELTA);
 
+        HysteresisLevels ambientHysteresis = mDisplayDeviceConfig.getAmbientBrightnessHysteresis();
+        HysteresisLevels ambientIdleHysteresis =
+                mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis();
+        HysteresisLevels screenHysteresis = mDisplayDeviceConfig.getScreenBrightnessHysteresis();
+        HysteresisLevels screenIdleHysteresis =
+                mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis();
         // Test thresholds
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(),
-                ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA);
+        assertEquals(0, ambientHysteresis.getMinBrightening(), ZERO_DELTA);
+        assertEquals(0, ambientIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+        assertEquals(0, ambientHysteresis.getMinDarkening(), ZERO_DELTA);
+        assertEquals(0, ambientIdleHysteresis.getMinDarkening(), ZERO_DELTA);
 
-        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA);
-        assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA);
+        assertEquals(0, screenHysteresis.getMinBrightening(), ZERO_DELTA);
+        assertEquals(0, screenIdleHysteresis.getMinBrightening(), ZERO_DELTA);
+        assertEquals(0, screenHysteresis.getMinDarkening(), ZERO_DELTA);
+        assertEquals(0, screenIdleHysteresis.getMinDarkening(), ZERO_DELTA);
 
         // screen levels will be considered "old screen brightness scale"
         // and therefore will divide by 255
         assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA);
-        assertArrayEquals(new float[]{35, 36, 37},
-                mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA);
+                screenHysteresis.getBrighteningThresholdLevels(), SMALL_DELTA);
+        assertArrayEquals(new float[]{0.35f, 0.36f, 0.37f},
+                screenHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
         assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA);
-        assertArrayEquals(new float[]{37, 38, 39},
-                mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA);
+                screenHysteresis.getDarkeningThresholdLevels(), SMALL_DELTA);
+        assertArrayEquals(new float[]{0.37f, 0.38f, 0.39f},
+                screenHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
 
         assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{27, 28, 29},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA);
+                ambientHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.27f, 0.28f, 0.29f},
+                ambientHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
         assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA);
-        assertArrayEquals(new float[]{29, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA);
+                ambientHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.29f, 0.30f, 0.31f},
+                ambientHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
 
         assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA);
-        assertArrayEquals(new float[]{35, 36, 37},
-                mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA);
+                screenIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.35f, 0.36f, 0.37f},
+                screenIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
         assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f},
-                mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA);
-        assertArrayEquals(new float[]{37, 38, 39},
-                mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA);
+                screenIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.37f, 0.38f, 0.39f},
+                screenIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
 
         assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{27, 28, 29},
-                mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA);
+                ambientIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.27f, 0.28f, 0.29f},
+                ambientIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA);
         assertArrayEquals(new float[]{0, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA);
-        assertArrayEquals(new float[]{29, 30, 31},
-                mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA);
+                ambientIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.29f, 0.30f, 0.31f},
+                ambientIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA);
         assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(),
                 DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
         assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
index 1c71abc..0029674 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceTest.java
@@ -26,6 +26,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.view.Display;
 import android.view.SurfaceControl;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -72,6 +73,7 @@
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_anisotropyCorrection() {
+        mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
         mDisplayDeviceInfo.xDpi = 0.5f;
         mDisplayDeviceInfo.yDpi = 1.0f;
         DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
@@ -81,6 +83,16 @@
     }
 
     @Test
+    public void testGetDisplaySurfaceDefaultSizeLocked_notRotated_noAnisotropyCorrection() {
+        mDisplayDeviceInfo.type = Display.TYPE_INTERNAL;
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+        assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(PORTRAIT_SIZE);
+    }
+
+    @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_notRotated() {
         DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
                 mMockDisplayAdapter);
@@ -97,6 +109,7 @@
 
     @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_anisotropyCorrection() {
+        mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
         mDisplayDeviceInfo.xDpi = 0.5f;
         mDisplayDeviceInfo.yDpi = 1.0f;
         DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
@@ -107,6 +120,17 @@
     }
 
     @Test
+    public void testGetDisplaySurfaceDefaultSizeLocked_rotation90_noAnisotropyCorrection() {
+        mDisplayDeviceInfo.type = Display.TYPE_INTERNAL;
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+        DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
+                mMockDisplayAdapter, /*isAnisotropyCorrectionEnabled=*/ true);
+        displayDevice.setProjectionLocked(mMockTransaction, ROTATION_90, new Rect(), new Rect());
+        assertThat(displayDevice.getDisplaySurfaceDefaultSizeLocked()).isEqualTo(LANDSCAPE_SIZE);
+    }
+
+    @Test
     public void testGetDisplaySurfaceDefaultSizeLocked_rotation90() {
         DisplayDevice displayDevice = new FakeDisplayDevice(mDisplayDeviceInfo,
                 mMockDisplayAdapter);
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 afb87d1..de93914 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -82,6 +82,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.HysteresisLevels;
 import com.android.server.display.config.SensorData;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.feature.flags.Flags;
@@ -1954,6 +1955,14 @@
                 .thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE);
         when(displayDeviceConfigMock.getBrightnessRampDecreaseMaxIdleMillis())
                 .thenReturn(BRIGHTNESS_RAMP_DECREASE_MAX_IDLE);
+
+        final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class);
+        when(displayDeviceConfigMock.getAmbientBrightnessHysteresis()).thenReturn(hysteresisLevels);
+        when(displayDeviceConfigMock.getAmbientBrightnessIdleHysteresis()).thenReturn(
+                hysteresisLevels);
+        when(displayDeviceConfigMock.getScreenBrightnessHysteresis()).thenReturn(hysteresisLevels);
+        when(displayDeviceConfigMock.getScreenBrightnessIdleHysteresis()).thenReturn(
+                hysteresisLevels);
     }
 
     private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
@@ -1995,7 +2004,7 @@
 
         TestInjector injector = spy(new TestInjector(displayPowerState, animator,
                 automaticBrightnessController, wakelockController, brightnessMappingStrategy,
-                hysteresisLevels, screenOffBrightnessSensorController,
+                screenOffBrightnessSensorController,
                 hbmController, normalBrightnessModeController, hdrClamper,
                 clamperController, mDisplayManagerFlagsMock));
 
@@ -2005,6 +2014,11 @@
         final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class);
         final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class);
 
+        when(config.getAmbientBrightnessHysteresis()).thenReturn(hysteresisLevels);
+        when(config.getAmbientBrightnessIdleHysteresis()).thenReturn(hysteresisLevels);
+        when(config.getScreenBrightnessHysteresis()).thenReturn(hysteresisLevels);
+        when(config.getScreenBrightnessIdleHysteresis()).thenReturn(hysteresisLevels);
+
         setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
         when(config.isAutoBrightnessAvailable()).thenReturn(isAutoBrightnessAvailable);
 
@@ -2080,7 +2094,6 @@
         private final AutomaticBrightnessController mAutomaticBrightnessController;
         private final WakelockController mWakelockController;
         private final BrightnessMappingStrategy mBrightnessMappingStrategy;
-        private final HysteresisLevels mHysteresisLevels;
         private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
         private final HighBrightnessModeController mHighBrightnessModeController;
 
@@ -2096,7 +2109,6 @@
                 AutomaticBrightnessController automaticBrightnessController,
                 WakelockController wakelockController,
                 BrightnessMappingStrategy brightnessMappingStrategy,
-                HysteresisLevels hysteresisLevels,
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController highBrightnessModeController,
                 NormalBrightnessModeController normalBrightnessModeController,
@@ -2108,7 +2120,6 @@
             mAutomaticBrightnessController = automaticBrightnessController;
             mWakelockController = wakelockController;
             mBrightnessMappingStrategy = brightnessMappingStrategy;
-            mHysteresisLevels = hysteresisLevels;
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
             mNormalBrightnessModeController = normalBrightnessModeController;
@@ -2186,22 +2197,6 @@
         }
 
         @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold) {
-            return mHysteresisLevels;
-        }
-
-        @Override
-        HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
-                float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels,
-                float[] darkeningThresholdLevels, float minDarkeningThreshold,
-                float minBrighteningThreshold, boolean potentialOldBrightnessRange) {
-            return mHysteresisLevels;
-        }
-
-        @Override
         ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController(
                 SensorManager sensorManager, Sensor lightSensor, Handler handler,
                 ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index e798aa2..779445e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -142,8 +142,39 @@
         assertEquals(new Point(0, DISPLAY_HEIGHT / 4), mLogicalDisplay.getDisplayPosition());
     }
 
+
+    @Test
+    public void testNoLetterbox_noAnisotropyCorrectionForInternalDisplay() {
+        mDisplayDeviceInfo.type = Display.TYPE_INTERNAL;
+        mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
+                /*isAnisotropyCorrectionEnabled=*/ true,
+                /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
+
+        // In case of Anisotropy of pixels, then the content should be rescaled so it would adjust
+        // to using the whole screen. This is because display will rescale it back to fill the
+        // screen (in case the display menu setting is set to stretch the pixels across the display)
+        mDisplayDeviceInfo.xDpi = 0.5f;
+        mDisplayDeviceInfo.yDpi = 1.0f;
+
+        mLogicalDisplay.updateLocked(mDeviceRepo);
+        var originalDisplayInfo = mLogicalDisplay.getDisplayInfoLocked();
+        // Content width not scaled
+        assertEquals(DISPLAY_WIDTH, originalDisplayInfo.logicalWidth);
+        assertEquals(DISPLAY_HEIGHT, originalDisplayInfo.logicalHeight);
+
+        SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+
+        // Applications need to think that they are shown on a display with square pixels.
+        // as applications can be displayed on multiple displays simultaneously (mirrored).
+        // Content is too wide, should have become letterboxed - but it won't because of anisotropy
+        // correction
+        assertEquals(new Point(0, 0), mLogicalDisplay.getDisplayPosition());
+    }
+
     @Test
     public void testNoLetterbox_anisotropyCorrection() {
+        mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
                 /*isAnisotropyCorrectionEnabled=*/ true,
                 /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
@@ -172,6 +203,7 @@
 
     @Test
     public void testLetterbox_anisotropyCorrectionYDpi() {
+        mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
                 /*isAnisotropyCorrectionEnabled=*/ true,
                 /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
@@ -229,6 +261,7 @@
 
     @Test
     public void testPillarbox_anisotropyCorrection() {
+        mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
                 /*isAnisotropyCorrectionEnabled=*/ true,
                 /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
@@ -257,6 +290,7 @@
 
     @Test
     public void testNoPillarbox_anisotropyCorrectionYDpi() {
+        mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
                 /*isAnisotropyCorrectionEnabled=*/ true,
                 /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
@@ -318,6 +352,7 @@
 
     @Test
     public void testGetDisplayPositionAlwaysRotateDisplayEnabled() {
+        mDisplayDeviceInfo.type = Display.TYPE_EXTERNAL;
         mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice,
                 /*isAnisotropyCorrectionEnabled=*/ true,
                 /*isAlwaysRotateDisplayDeviceEnabled=*/ true);
diff --git a/services/tests/selinux/Android.bp b/services/tests/selinux/Android.bp
index 3f2aec7..f387238 100644
--- a/services/tests/selinux/Android.bp
+++ b/services/tests/selinux/Android.bp
@@ -34,7 +34,7 @@
 }
 
 android_test {
-    name: "SelinuxFrameworkTests",
+    name: "SelinuxFrameworksTests",
     srcs: [
         "src/**/*.java",
     ],
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
index dae8f93..0946229 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING
@@ -1,23 +1,6 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.contentcapture"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
-    }
-  ],
-  "postsubmit": [
-    {
-      // b/331020193, Move to presubmit early april 2024
       "name": "FrameworksServicesTests_contentcapture"
     }
   ]
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
index 32729a8..1ad7baa 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING
@@ -1,23 +1,6 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.contentprotection"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
-    }
-  ],
-  "postsubmit": [
-    {
-      // b/331020193, Move to presubmit early april 2024
       "name": "FrameworksServicesTests_contentprotection"
     }
   ]
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
index dc8f934..58f5bb3 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING
@@ -1,29 +1,11 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.location.contexthub."
-        },
-        {
-          "include-annotation": "android.platform.test.annotations.Presubmit"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "org.junit.Ignore"
-        }
-      ]
+      "name": "FrameworksServicesTests_contexthub_presubmit"
     }
   ],
   "postsubmit": [
     {
-      // b/331020193, Move to presubmit early april 2024
-      "name": "FrameworksServicesTests_contexthub_presubmit"
-    },
-    {
       "name": "FrameworksServicesTests",
       "options": [
         {
diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
index 41c4383..944c1df 100644
--- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING
@@ -1,12 +1,7 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.om."
-        }
-      ]
+      "name": "FrameworksServicesTests_om"
     },
     {
       "name": "PackageManagerServiceHostTests",
@@ -16,11 +11,5 @@
         }
       ]
     }
-  ],
-  "postsubmit": [
-    {
-      // b/331020193, Move to presubmit early april 2024
-      "name": "FrameworksServicesTests_om"
-    }
   ]
 }
diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
index 06e7002..2138da9 100644
--- a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING
@@ -1,17 +1,6 @@
 {
   "presubmit": [
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {
-          "include-filter": "com.android.server.os."
-        }
-      ]
-    }
-  ],
-  "postsubmit": [
-    {
-      // b/331020193, Move to presubmit early april 2024
       "name": "FrameworksServicesTests_os"
     }
   ]
diff --git a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
index f4e724f..861562d 100644
--- a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
@@ -21,7 +21,7 @@
   "postsubmit": [
     {
       // Presubmit is intentional here while testing with SLO checker.
-      // b/331020193, Move to presubmit early april 2024
+      // Tests are flaky, waiting to bypass.
       "name": "FrameworksServicesTests_pm_presubmit"
     },
     {
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
index 7e7393c..eb7453d 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING
@@ -1,21 +1,7 @@
 {
-    "presubmit": [
-        {
-            "name": "FrameworksServicesTests",
-            "options": [
-                {
-                    "include-filter": "com.android.server.recoverysystem."
-                },
-                {
-                    "exclude-annotation": "androidx.test.filters.FlakyTest"
-                }
-            ]
-        }
-    ],
-    "postsubmit": [
-      {
-        // b/331020193, Move to presubmit early april 2024
-        "name": "FrameworksServicesTests_recoverysystem"
-      }
-    ]
-}
\ No newline at end of file
+  "presubmit": [
+    {
+      "name": "FrameworksServicesTests_recoverysystem"
+    }
+  ]
+}
diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index a63db88..7b5b07c 100644
--- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -16,6 +16,8 @@
 package com.android.internal.telephony.util;
 
 import static android.telephony.Annotation.DataState;
+import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
+import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -37,6 +39,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyFrameworkInitializer;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.telephony.ITelephony;
@@ -48,6 +51,8 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * This class provides various util functions
@@ -342,4 +347,31 @@
         return false;
 
     }
+
+    /**
+     * @param plmn target plmn for validation.
+     * @return {@code true} if the target plmn is valid {@code false} otherwise.
+     */
+    public static boolean isValidPlmn(@Nullable String plmn) {
+        if (TextUtils.isEmpty(plmn)) {
+            return false;
+        }
+        Pattern pattern = Pattern.compile("^(?:[0-9]{3})(?:[0-9]{2}|[0-9]{3})$");
+        Matcher matcher = pattern.matcher(plmn);
+        if (!matcher.matches()) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @param serviceType target serviceType for validation.
+     * @return {@code true} if the target serviceType is valid {@code false} otherwise.
+     */
+    public static boolean isValidService(int serviceType) {
+        if (serviceType < FIRST_SERVICE_TYPE || serviceType > LAST_SERVICE_TYPE) {
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
index a62103e..7558332 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java
@@ -16,10 +16,16 @@
 
 package com.android.internal.telephony.tests;
 
+import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE;
+import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
@@ -72,6 +78,22 @@
         // getSubscriptionUserHandle should be called if subID is active.
         verify(mSubscriptionManager, times(1)).getSubscriptionUserHandle(eq(activeSubId));
     }
+
+    @Test
+    public void testIsValidPlmn() {
+        assertTrue(TelephonyUtils.isValidPlmn("310260"));
+        assertTrue(TelephonyUtils.isValidPlmn("45006"));
+        assertFalse(TelephonyUtils.isValidPlmn("1234567"));
+        assertFalse(TelephonyUtils.isValidPlmn("1234"));
+    }
+
+    @Test
+    public void testIsValidService() {
+        assertTrue(TelephonyUtils.isValidService(FIRST_SERVICE_TYPE));
+        assertTrue(TelephonyUtils.isValidService(LAST_SERVICE_TYPE));
+        assertFalse(TelephonyUtils.isValidService(FIRST_SERVICE_TYPE - 1));
+        assertFalse(TelephonyUtils.isValidService(LAST_SERVICE_TYPE + 1));
+    }
 }