diff options
6 files changed, 336 insertions, 9 deletions
diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java index 4bfc09075448..64d231437559 100644 --- a/services/core/java/com/android/server/display/BrightnessRangeController.java +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -17,9 +17,11 @@ package com.android.server.display; import android.hardware.display.BrightnessInfo; +import android.os.Handler; import android.os.IBinder; import android.provider.DeviceConfigInterface; +import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.feature.DeviceConfigParameterProvider; import java.io.PrintWriter; @@ -31,23 +33,30 @@ class BrightnessRangeController { private final NormalBrightnessModeController mNormalBrightnessModeController = new NormalBrightnessModeController(); + private final HdrClamper mHdrClamper; + private final Runnable mModeChangeCallback; private final boolean mUseNbmController; + private final boolean mUseHdrClamper; + BrightnessRangeController(HighBrightnessModeController hbmController, - Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig) { + Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, Handler handler) { this(hbmController, modeChangeCallback, displayDeviceConfig, + new HdrClamper(modeChangeCallback::run, new Handler(handler.getLooper())), new DeviceConfigParameterProvider(DeviceConfigInterface.REAL)); } BrightnessRangeController(HighBrightnessModeController hbmController, Runnable modeChangeCallback, DisplayDeviceConfig displayDeviceConfig, - DeviceConfigParameterProvider configParameterProvider) { + HdrClamper hdrClamper, DeviceConfigParameterProvider configParameterProvider) { mHbmController = hbmController; mModeChangeCallback = modeChangeCallback; mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled(); + mUseHdrClamper = false; mNormalBrightnessModeController.resetNbmData(displayDeviceConfig.getLuxThrottlingData()); + mHdrClamper = hdrClamper; } void dump(PrintWriter pw) { @@ -63,6 +72,9 @@ class BrightnessRangeController { () -> mNormalBrightnessModeController.onAmbientLuxChange(ambientLux), () -> mHbmController.onAmbientLuxChange(ambientLux) ); + if (mUseHdrClamper) { + mHdrClamper.onAmbientLuxChange(ambientLux); + } } float getNormalBrightnessMax() { @@ -118,7 +130,8 @@ class BrightnessRangeController { } float getHdrBrightnessValue() { - return mHbmController.getHdrBrightnessValue(); + float hdrBrightness = mHbmController.getHdrBrightnessValue(); + return Math.min(hdrBrightness, mHdrClamper.getMaxBrightness()); } float getTransitionPoint() { @@ -138,4 +151,8 @@ class BrightnessRangeController { hbmChangesFunc.run(); } } + + public float getHdrTransitionRate() { + return mHdrClamper.getTransitionRate(); + } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 40dbabf29807..b7b46eab426b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -677,7 +677,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call HighBrightnessModeController hbmController = createHbmControllerLocked(modeChangeCallback); mBrightnessRangeController = new BrightnessRangeController(hbmController, - modeChangeCallback, mDisplayDeviceConfig); + modeChangeCallback, mDisplayDeviceConfig, mHandler); mBrightnessThrottler = createBrightnessThrottlerLocked(); diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 460c351c4027..7021eede32a6 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -539,8 +539,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal modeChangeCallback); mBrightnessThrottler = createBrightnessThrottlerLocked(); - mBrightnessRangeController = new BrightnessRangeController(hbmController, - modeChangeCallback, mDisplayDeviceConfig); + mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController, + modeChangeCallback, mDisplayDeviceConfig, mHandler); mDisplayBrightnessController = new DisplayBrightnessController(context, null, @@ -1497,6 +1497,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // allowed range. float animateValue = clampScreenBrightness(brightnessState); + // custom transition duration + float customTransitionRate = -1f; + // If there are any HDR layers on the screen, we have a special brightness value that we // use instead. We still preserve the calculated brightness for Standard Dynamic Range // (SDR) layers, but the main brightness value will be the one for HDR. @@ -1511,6 +1514,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal // We want to scale HDR brightness level with the SDR level, we also need to restore // SDR brightness immediately when entering dim or low power mode. animateValue = mBrightnessRangeController.getHdrBrightnessValue(); + customTransitionRate = mBrightnessRangeController.getHdrTransitionRate(); } final float currentBrightness = mPowerState.getScreenBrightness(); @@ -1523,6 +1527,9 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal || !isDisplayContentVisible || brightnessIsTemporary) { animateScreenBrightness(animateValue, sdrAnimateValue, SCREEN_ANIMATION_RATE_MINIMUM); + } else if (customTransitionRate > 0) { + animateScreenBrightness(animateValue, sdrAnimateValue, + customTransitionRate); } else { boolean isIncreasing = animateValue > currentBrightness; final float rampSpeed; @@ -2968,6 +2975,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal hbmChangeCallback, hbmMetadata, context); } + BrightnessRangeController getBrightnessRangeController( + HighBrightnessModeController hbmController, Runnable modeChangeCallback, + DisplayDeviceConfig displayDeviceConfig, Handler handler) { + return new BrightnessRangeController(hbmController, + modeChangeCallback, displayDeviceConfig, handler); + } + DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, SensorManager sensorManager, Resources resources) { return DisplayWhiteBalanceFactory.create(handler, diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java new file mode 100644 index 000000000000..079a196234a8 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import android.os.Handler; +import android.os.PowerManager; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.HashMap; +import java.util.Map; + +public class HdrClamper { + + private final Configuration mConfiguration = new Configuration(); + + private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener; + + private final Handler mHandler; + + private final Runnable mDebouncer; + + private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX; + + // brightness change speed, in units per seconds, + private float mTransitionRate = -1f; + + private float mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX; + + private float mDesiredTransitionDuration = -1; // in seconds + + public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener, + Handler handler) { + mClamperChangeListener = clamperChangeListener; + mHandler = handler; + mDebouncer = () -> { + mTransitionRate = Math.abs((mMaxBrightness - mDesiredMaxBrightness) + / mDesiredTransitionDuration); + mMaxBrightness = mDesiredMaxBrightness; + mClamperChangeListener.onChanged(); + }; + } + + // Called in same looper: mHandler.getLooper() + public float getMaxBrightness() { + return mMaxBrightness; + } + + // Called in same looper: mHandler.getLooper() + public float getTransitionRate() { + return mTransitionRate; + } + + + /** + * Updates brightness cap in response to ambient lux change. + * Called by ABC in same looper: mHandler.getLooper() + */ + public void onAmbientLuxChange(float ambientLux) { + float expectedMaxBrightness = findBrightnessLimit(ambientLux); + if (mMaxBrightness == expectedMaxBrightness) { + mDesiredMaxBrightness = mMaxBrightness; + mDesiredTransitionDuration = -1; + mTransitionRate = -1f; + mHandler.removeCallbacks(mDebouncer); + } else if (mDesiredMaxBrightness != expectedMaxBrightness) { + mDesiredMaxBrightness = expectedMaxBrightness; + long debounceTime; + if (mDesiredMaxBrightness > mMaxBrightness) { + debounceTime = mConfiguration.mIncreaseConfig.mDebounceTimeMillis; + mDesiredTransitionDuration = + (float) mConfiguration.mIncreaseConfig.mTransitionTimeMillis / 1000; + } else { + debounceTime = mConfiguration.mDecreaseConfig.mDebounceTimeMillis; + mDesiredTransitionDuration = + (float) mConfiguration.mDecreaseConfig.mTransitionTimeMillis / 1000; + } + + mHandler.removeCallbacks(mDebouncer); + mHandler.postDelayed(mDebouncer, debounceTime); + } + } + + @VisibleForTesting + Configuration getConfiguration() { + return mConfiguration; + } + + private float findBrightnessLimit(float ambientLux) { + float foundAmbientBoundary = Float.MAX_VALUE; + float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX; + for (Map.Entry<Float, Float> brightnessPoint : + mConfiguration.mMaxBrightnessLimits.entrySet()) { + float ambientBoundary = brightnessPoint.getKey(); + // find ambient lux upper boundary closest to current ambient lux + if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) { + foundMaxBrightness = brightnessPoint.getValue(); + foundAmbientBoundary = ambientBoundary; + } + } + return foundMaxBrightness; + } + + @VisibleForTesting + static class Configuration { + final Map<Float, Float> mMaxBrightnessLimits = new HashMap<>(); + final TransitionConfiguration mIncreaseConfig = new TransitionConfiguration(); + + final TransitionConfiguration mDecreaseConfig = new TransitionConfiguration(); + } + + @VisibleForTesting + static class TransitionConfiguration { + long mDebounceTimeMillis; + + long mTransitionTimeMillis; + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java index e7dc48e529eb..11ff42be7fc8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java @@ -43,6 +43,7 @@ import android.content.res.Resources; import android.hardware.Sensor; import android.hardware.SensorEventListener; import android.hardware.SensorManager; +import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManagerInternal.DisplayPowerCallbacks; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; import android.os.Handler; @@ -67,7 +68,9 @@ import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; import com.android.server.display.brightness.BrightnessEvent; +import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.color.ColorDisplayService; +import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.layout.Layout; import com.android.server.display.whitebalance.DisplayWhiteBalanceController; import com.android.server.policy.WindowManagerPolicy; @@ -1175,6 +1178,26 @@ public final class DisplayPowerController2Test { verify(mHolder.displayPowerState, times(1)).stop(); } + @Test + public void testRampRateForHdrContent() { + float clampedBrightness = 0.6f; + float transitionRate = 35.5f; + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); + when(mHolder.hbmController.getHighBrightnessMode()).thenReturn( + BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR); + when(mHolder.hbmController.getHdrBrightnessValue()).thenReturn(PowerManager.BRIGHTNESS_MAX); + when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(clampedBrightness); + when(mHolder.hdrClamper.getTransitionRate()).thenReturn(transitionRate); + + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(mHolder.animator, atLeastOnce()).animateTo(eq(clampedBrightness), anyFloat(), + eq(transitionRate)); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -1270,12 +1293,16 @@ public final class DisplayPowerController2Test { final ScreenOffBrightnessSensorController screenOffBrightnessSensorController = mock(ScreenOffBrightnessSensorController.class); final HighBrightnessModeController hbmController = mock(HighBrightnessModeController.class); + final HdrClamper hdrClamper = mock(HdrClamper.class); + final DeviceConfigParameterProvider deviceConfigParameterProvider = + mock(DeviceConfigParameterProvider.class); when(hbmController.getCurrentBrightnessMax()).thenReturn(PowerManager.BRIGHTNESS_MAX); TestInjector injector = spy(new TestInjector(displayPowerState, animator, automaticBrightnessController, wakelockController, brightnessMappingStrategy, - hysteresisLevels, screenOffBrightnessSensorController, hbmController)); + hysteresisLevels, screenOffBrightnessSensorController, hbmController, hdrClamper, + deviceConfigParameterProvider)); final LogicalDisplay display = mock(LogicalDisplay.class); final DisplayDevice device = mock(DisplayDevice.class); @@ -1293,7 +1320,7 @@ public final class DisplayPowerController2Test { return new DisplayPowerControllerHolder(dpc, display, displayPowerState, brightnessSetting, animator, automaticBrightnessController, wakelockController, - screenOffBrightnessSensorController, hbmController, hbmMetadata, + screenOffBrightnessSensorController, hbmController, hdrClamper, hbmMetadata, brightnessMappingStrategy, injector); } @@ -1311,6 +1338,8 @@ public final class DisplayPowerController2Test { public final WakelockController wakelockController; public final ScreenOffBrightnessSensorController screenOffBrightnessSensorController; public final HighBrightnessModeController hbmController; + + public final HdrClamper hdrClamper; public final HighBrightnessModeMetadata hbmMetadata; public final BrightnessMappingStrategy brightnessMappingStrategy; public final DisplayPowerController2.Injector injector; @@ -1322,6 +1351,7 @@ public final class DisplayPowerController2Test { WakelockController wakelockController, ScreenOffBrightnessSensorController screenOffBrightnessSensorController, HighBrightnessModeController hbmController, + HdrClamper hdrClamper, HighBrightnessModeMetadata hbmMetadata, BrightnessMappingStrategy brightnessMappingStrategy, DisplayPowerController2.Injector injector) { @@ -1334,6 +1364,7 @@ public final class DisplayPowerController2Test { this.wakelockController = wakelockController; this.screenOffBrightnessSensorController = screenOffBrightnessSensorController; this.hbmController = hbmController; + this.hdrClamper = hdrClamper; this.hbmMetadata = hbmMetadata; this.brightnessMappingStrategy = brightnessMappingStrategy; this.injector = injector; @@ -1350,13 +1381,19 @@ public final class DisplayPowerController2Test { private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController; private final HighBrightnessModeController mHighBrightnessModeController; + private final HdrClamper mHdrClamper; + + private final DeviceConfigParameterProvider mDeviceConfigParameterProvider; + TestInjector(DisplayPowerState dps, DualRampAnimator<DisplayPowerState> animator, AutomaticBrightnessController automaticBrightnessController, WakelockController wakelockController, BrightnessMappingStrategy brightnessMappingStrategy, HysteresisLevels hysteresisLevels, ScreenOffBrightnessSensorController screenOffBrightnessSensorController, - HighBrightnessModeController highBrightnessModeController) { + HighBrightnessModeController highBrightnessModeController, + HdrClamper hdrClamper, + DeviceConfigParameterProvider deviceConfigParameterProvider) { mDisplayPowerState = dps; mAnimator = animator; mAutomaticBrightnessController = automaticBrightnessController; @@ -1365,6 +1402,8 @@ public final class DisplayPowerController2Test { mHysteresisLevels = hysteresisLevels; mScreenOffBrightnessSensorController = screenOffBrightnessSensorController; mHighBrightnessModeController = highBrightnessModeController; + mHdrClamper = hdrClamper; + mDeviceConfigParameterProvider = deviceConfigParameterProvider; } @Override @@ -1471,6 +1510,14 @@ public final class DisplayPowerController2Test { } @Override + BrightnessRangeController getBrightnessRangeController( + HighBrightnessModeController hbmController, Runnable modeChangeCallback, + DisplayDeviceConfig displayDeviceConfig, Handler handler) { + return new BrightnessRangeController(hbmController, modeChangeCallback, + displayDeviceConfig, mHdrClamper, mDeviceConfigParameterProvider); + } + + @Override DisplayWhiteBalanceController getDisplayWhiteBalanceController(Handler handler, SensorManager sensorManager, Resources resources) { return mDisplayWhiteBalanceControllerMock; diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java new file mode 100644 index 000000000000..0ebe46ac0c88 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.clamper; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.os.PowerManager; + +import androidx.test.filters.SmallTest; + +import com.android.server.testutils.OffsettableClock; +import com.android.server.testutils.TestHandler; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@SmallTest +public class HdrClamperTest { + + public static final float FLOAT_TOLERANCE = 0.0001f; + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + @Mock + private BrightnessClamperController.ClamperChangeListener mMockListener; + + OffsettableClock mClock = new OffsettableClock.Stopped(); + + private final TestHandler mTestHandler = new TestHandler(null, mClock); + + + private HdrClamper mHdrClamper; + + + @Before + public void setUp() { + mHdrClamper = new HdrClamper(mMockListener, mTestHandler); + configureClamper(); + } + + @Test + public void testClamper_AmbientLuxChangesAboveLimit() { + mHdrClamper.onAmbientLuxChange(500); + + assertFalse(mTestHandler.hasMessagesOrCallbacks()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + } + + @Test + public void testClamper_AmbientLuxChangesBelowLimit() { + mHdrClamper.onAmbientLuxChange(499); + + assertTrue(mTestHandler.hasMessagesOrCallbacks()); + TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek(); + assertEquals(2000, msgInfo.sendTime); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + + mClock.fastForward(2000); + mTestHandler.timeAdvance(); + assertEquals(0.6f, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + } + + @Test + public void testClamper_AmbientLuxChangesBelowLimit_ThenFastAboveLimit() { + mHdrClamper.onAmbientLuxChange(499); + mHdrClamper.onAmbientLuxChange(500); + + assertFalse(mTestHandler.hasMessagesOrCallbacks()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + } + + @Test + public void testClamper_AmbientLuxChangesBelowLimit_ThenSlowlyAboveLimit() { + mHdrClamper.onAmbientLuxChange(499); + mClock.fastForward(2000); + mTestHandler.timeAdvance(); + + mHdrClamper.onAmbientLuxChange(500); + + assertTrue(mTestHandler.hasMessagesOrCallbacks()); + TestHandler.MsgInfo msgInfo = mTestHandler.getPendingMessages().peek(); + assertEquals(3000, msgInfo.sendTime); // 2000 + 1000 + + mClock.fastForward(1000); + mTestHandler.timeAdvance(); + assertEquals(PowerManager.BRIGHTNESS_MAX, mHdrClamper.getMaxBrightness(), FLOAT_TOLERANCE); + } + + private void configureClamper() { + mHdrClamper.getConfiguration().mMaxBrightnessLimits.put(500f, 0.6f); + mHdrClamper.getConfiguration().mIncreaseConfig.mDebounceTimeMillis = 1000; + mHdrClamper.getConfiguration().mIncreaseConfig.mTransitionTimeMillis = 1500; + mHdrClamper.getConfiguration().mDecreaseConfig.mDebounceTimeMillis = 2000; + mHdrClamper.getConfiguration().mDecreaseConfig.mTransitionTimeMillis = 2500; + } +} |