Max brightness fix in normal brightness mode and HDR content shown

Fixed inconsistency in HBMController and BrightnessRangeControlller current maxBrightness selection
Before: BrightnessRangeController was taking into account NBC only if HBMController mode was HBM_OFF,
However HBMController was selecting transitionPoint as maxBrightness aslo in case e.g. HDR was shown, or low power mode.
Now: BrightnessRangeController takes into account NBC in sync with HBMController

Bug: b/326872826
Test: atest BrightnessRangeControllerTest, manually tested brightness slider behavior
Change-Id: I9071b27769b6a357ca9b2b7f6aa609ab0672467e
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java
index 40b2f5a..10030b3 100644
--- a/services/core/java/com/android/server/display/BrightnessRangeController.java
+++ b/services/core/java/com/android/server/display/BrightnessRangeController.java
@@ -21,6 +21,7 @@
 import android.os.IBinder;
 import android.os.PowerManager;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.brightness.clamper.HdrClamper;
 import com.android.server.display.feature.DisplayManagerFlags;
 
@@ -30,8 +31,7 @@
 class BrightnessRangeController {
 
     private final HighBrightnessModeController mHbmController;
-    private final NormalBrightnessModeController mNormalBrightnessModeController =
-            new NormalBrightnessModeController();
+    private final NormalBrightnessModeController mNormalBrightnessModeController;
 
     private final HdrClamper mHdrClamper;
 
@@ -45,17 +45,21 @@
             Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler,
             DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
         this(hbmController, modeChangeCallback, displayDeviceConfig,
+                new NormalBrightnessModeController(),
                 new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), flags,
                 displayToken, info);
     }
 
+    @VisibleForTesting
     BrightnessRangeController(HighBrightnessModeController hbmController,
             Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig,
+            NormalBrightnessModeController normalBrightnessModeController,
             HdrClamper hdrClamper, DisplayManagerFlags flags, IBinder displayToken,
             DisplayDeviceInfo info) {
         mHbmController = hbmController;
         mModeChangeCallback = modeChangeCallback;
         mHdrClamper = hdrClamper;
+        mNormalBrightnessModeController = normalBrightnessModeController;
         mUseHdrClamper = flags.isHdrClamperEnabled();
         mUseNbmController = flags.isNbmControllerEnabled();
         if (mUseNbmController) {
@@ -126,8 +130,11 @@
 
 
     float getCurrentBrightnessMax() {
-        if (mUseNbmController && mHbmController.getHighBrightnessMode()
-                == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) {
+        // nbmController might adjust maxBrightness only if device does not support HBM or
+        // hbm is currently not allowed
+        if (mUseNbmController
+                && (!mHbmController.deviceSupportsHbm()
+                || !mHbmController.isHbmCurrentlyAllowed())) {
             return Math.min(mHbmController.getCurrentBrightnessMax(),
                     mNormalBrightnessModeController.getCurrentBrightnessMax());
         }
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index a9f78fd..47176fe 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -168,7 +168,7 @@
     }
 
     float getCurrentBrightnessMax() {
-        if (!deviceSupportsHbm() || isCurrentlyAllowed()) {
+        if (!deviceSupportsHbm() || isHbmCurrentlyAllowed()) {
             // Either the device doesn't support HBM, or HBM range is currently allowed (device
             // it in a high-lux environment). In either case, return the highest brightness
             // level supported by the device.
@@ -356,7 +356,7 @@
         return event.getStartTimeMillis();
     }
 
-    private boolean isCurrentlyAllowed() {
+    boolean isHbmCurrentlyAllowed() {
         // Returns true if HBM is allowed (above the ambient lux threshold) and there's still
         // time within the current window for additional HBM usage. We return false if there is an
         // HDR layer because we don't want the brightness MAX to change for HDR, which has its
@@ -369,7 +369,7 @@
                 && !mIsBlockedByLowPowerMode);
     }
 
-    private boolean deviceSupportsHbm() {
+    boolean deviceSupportsHbm() {
         return mHbmData != null && mHighBrightnessModeMetadata != null;
     }
 
@@ -462,7 +462,7 @@
                     + ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
                     + ", remainingAllowedTime: " + remainingTime
                     + ", isLuxHigh: " + mIsInAllowedAmbientRange
-                    + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
+                    + ", isHBMCurrentlyAllowed: " + isHbmCurrentlyAllowed()
                     + ", isHdrLayerPresent: " + mIsHdrLayerPresent
                     + ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio
                     + ", isAutoBrightnessEnabled: " +  mIsAutoBrightnessEnabled
@@ -575,7 +575,7 @@
             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
         } else if (mIsHdrLayerPresent) {
             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
-        } else if (isCurrentlyAllowed()) {
+        } else if (isHbmCurrentlyAllowed()) {
             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
new file mode 100644
index 0000000..1f3184d
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessRangeControllerTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display
+
+import android.os.IBinder
+import androidx.test.filters.SmallTest
+import com.android.server.display.brightness.clamper.HdrClamper
+import com.android.server.display.feature.DisplayManagerFlags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+private const val MAX_BRIGHTNESS = 1.0f
+private const val TRANSITION_POINT = 0.7f
+private const val NORMAL_BRIGHTNESS_HIGH = 0.8f
+private const val NORMAL_BRIGHTNESS_LOW = 0.6f
+
+@SmallTest
+class BrightnessRangeControllerTest {
+
+    private val mockHbmController = mock<HighBrightnessModeController>()
+    private val mockCallback = mock<Runnable>()
+    private val mockConfig = mock<DisplayDeviceConfig>()
+    private val mockNormalBrightnessController = mock<NormalBrightnessModeController>()
+    private val mockHdrClamper = mock<HdrClamper>()
+    private val mockFlags = mock<DisplayManagerFlags>()
+    private val mockToken = mock<IBinder>()
+
+    @Test
+    fun `returns HBC max brightness if HBM supported and ON`() {
+        val controller = createController()
+        assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+    }
+
+    @Test
+    fun `returns NBC max brightness if device does not support HBM`() {
+        val controller = createController(hbmSupported = false)
+        assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+    }
+
+    @Test
+    fun `returns NBC max brightness if HBM not allowed`() {
+        val controller = createController(hbmAllowed = false)
+        assertThat(controller.currentBrightnessMax).isEqualTo(NORMAL_BRIGHTNESS_LOW)
+    }
+
+    @Test
+    fun `returns HBC max brightness if NBM is disabled`() {
+        val controller = createController(nbmEnabled = false, hbmAllowed = false)
+        assertThat(controller.currentBrightnessMax).isEqualTo(MAX_BRIGHTNESS)
+    }
+
+    @Test
+    fun `returns HBC max brightness if lower than NBC max brightness`() {
+        val controller = createController(
+            hbmAllowed = false,
+            hbmMaxBrightness = TRANSITION_POINT,
+            nbmMaxBrightness = NORMAL_BRIGHTNESS_HIGH
+        )
+        assertThat(controller.currentBrightnessMax).isEqualTo(TRANSITION_POINT)
+    }
+
+    private fun createController(
+        nbmEnabled: Boolean = true,
+        hbmSupported: Boolean = true,
+        hbmAllowed: Boolean = true,
+        hbmMaxBrightness: Float = MAX_BRIGHTNESS,
+        nbmMaxBrightness: Float = NORMAL_BRIGHTNESS_LOW
+    ): BrightnessRangeController {
+        whenever(mockFlags.isNbmControllerEnabled).thenReturn(nbmEnabled)
+        whenever(mockHbmController.deviceSupportsHbm()).thenReturn(hbmSupported)
+        whenever(mockHbmController.isHbmCurrentlyAllowed).thenReturn(hbmAllowed)
+        whenever(mockHbmController.currentBrightnessMax).thenReturn(hbmMaxBrightness)
+        whenever(mockNormalBrightnessController.currentBrightnessMax).thenReturn(nbmMaxBrightness)
+
+        return BrightnessRangeController(mockHbmController, mockCallback, mockConfig,
+            mockNormalBrightnessController, mockHdrClamper, mockFlags, mockToken,
+            DisplayDeviceInfo())
+    }
+}
\ No newline at end of file
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 76b7780..fb23213 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1927,6 +1927,8 @@
                 mock(ScreenOffBrightnessSensorController.class);
         final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class);
         final HdrClamper hdrClamper = mock(HdrClamper.class);
+        final NormalBrightnessModeController normalBrightnessModeController = mock(
+                NormalBrightnessModeController.class);
         BrightnessClamperController clamperController = mock(BrightnessClamperController.class);
 
         when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX);
@@ -1939,7 +1941,8 @@
 
         TestInjector injector = spy(new TestInjector(displayPowerState, animator,
                 automaticBrightnessController, wakelockController, brightnessMappingStrategy,
-                hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper,
+                hysteresisLevels, screenOffBrightnessSensorController,
+                hbmController, normalBrightnessModeController, hdrClamper,
                 clamperController, mDisplayManagerFlagsMock));
 
         final LogicalDisplay display = mock(LogicalDisplay.class);
@@ -2027,6 +2030,8 @@
         private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController;
         private final HighBrightnessModeController mHighBrightnessModeController;
 
+        private final NormalBrightnessModeController mNormalBrightnessModeController;
+
         private final HdrClamper mHdrClamper;
 
         private final BrightnessClamperController mClamperController;
@@ -2040,6 +2045,7 @@
                 HysteresisLevels hysteresisLevels,
                 ScreenOffBrightnessSensorController screenOffBrightnessSensorController,
                 HighBrightnessModeController highBrightnessModeController,
+                NormalBrightnessModeController normalBrightnessModeController,
                 HdrClamper hdrClamper,
                 BrightnessClamperController clamperController,
                 DisplayManagerFlags flags) {
@@ -2051,6 +2057,7 @@
             mHysteresisLevels = hysteresisLevels;
             mScreenOffBrightnessSensorController = screenOffBrightnessSensorController;
             mHighBrightnessModeController = highBrightnessModeController;
+            mNormalBrightnessModeController = normalBrightnessModeController;
             mHdrClamper = hdrClamper;
             mClamperController = clamperController;
             mFlags = flags;
@@ -2163,7 +2170,8 @@
                 DisplayDeviceConfig displayDeviceConfig, Handler handler,
                 DisplayManagerFlags flags, IBinder displayToken, DisplayDeviceInfo info) {
             return new BrightnessRangeController(hbmController, modeChangeCallback,
-                    displayDeviceConfig, mHdrClamper, mFlags, displayToken, info);
+                    displayDeviceConfig, mNormalBrightnessModeController, mHdrClamper,
+                    mFlags, displayToken, info);
         }
 
         @Override