diff options
7 files changed, 630 insertions, 27 deletions
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 7a055d1d7e5c..76b4263c4b89 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -1192,6 +1192,18 @@ public class DisplayDeviceConfig { */ public float getHdrBrightnessFromSdr(float brightness, float maxDesiredHdrSdrRatio) { Spline sdrToHdrSpline = mHbmData != null ? mHbmData.sdrToHdrRatioSpline : null; + return getHdrBrightnessFromSdr(brightness, maxDesiredHdrSdrRatio, sdrToHdrSpline); + } + + /** + * Calculate the HDR brightness for the specified SDR brightenss, restricted by the + * maxDesiredHdrSdrRatio (the ratio between the HDR luminance and SDR luminance) and specific + * sdrToHdrSpline + * + * @return the HDR brightness or BRIGHTNESS_INVALID when no mapping exists. + */ + public float getHdrBrightnessFromSdr(float brightness, float maxDesiredHdrSdrRatio, + @Nullable Spline sdrToHdrSpline) { if (sdrToHdrSpline == null) { return PowerManager.BRIGHTNESS_INVALID; } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 6992580e4df8..1177be212222 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -505,14 +505,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mClock = mInjector.getClock(); mLogicalDisplay = logicalDisplay; mDisplayId = mLogicalDisplay.getDisplayIdLocked(); + mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + IBinder displayToken = mDisplayDevice.getDisplayTokenLocked(); + DisplayDeviceInfo displayDeviceInfo = mDisplayDevice.getDisplayDeviceInfoLocked(); mSensorManager = sensorManager; mHandler = new DisplayControllerHandler(handler.getLooper()); - mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked() - .getDisplayDeviceConfig(); + mDisplayDeviceConfig = mDisplayDevice.getDisplayDeviceConfig(); mIsEnabled = logicalDisplay.isEnabledLocked(); mIsInTransition = logicalDisplay.isInTransitionLocked(); - mIsDisplayInternal = logicalDisplay.getPrimaryDisplayDeviceLocked() - .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL; + mIsDisplayInternal = displayDeviceInfo.type == Display.TYPE_INTERNAL; mWakelockController = mInjector.getWakelockController(mDisplayId, callbacks); mDisplayPowerProximityStateController = mInjector.getDisplayPowerProximityStateController( mWakelockController, mDisplayDeviceConfig, mHandler.getLooper(), @@ -521,7 +522,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mTag = TAG + "[" + mDisplayId + "]"; mThermalBrightnessThrottlingDataId = logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; - mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); + mUniqueDisplayId = mDisplayDevice.getUniqueId(); mDisplayStatsId = mUniqueDisplayId.hashCode(); mPhysicalDisplayName = mDisplayDevice.getNameLocked(); @@ -569,8 +570,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessRangeController = mInjector.getBrightnessRangeController(hbmController, modeChangeCallback, mDisplayDeviceConfig, mHandler, flags, - mDisplayDevice.getDisplayTokenLocked(), - mDisplayDevice.getDisplayDeviceInfoLocked()); + displayToken, displayDeviceInfo); mDisplayBrightnessController = new DisplayBrightnessController(context, null, @@ -584,8 +584,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mUniqueDisplayId, mThermalBrightnessThrottlingDataId, logicalDisplay.getPowerThrottlingDataIdLocked(), - mDisplayDeviceConfig, - mDisplayId), mContext, flags, mSensorManager); + mDisplayDeviceConfig, displayDeviceInfo.width, displayDeviceInfo.height, + displayToken, mDisplayId), mContext, flags, mSensorManager); // Seed the cached brightness saveBrightnessInfo(getScreenBrightnessSetting()); mAutomaticBrightnessStrategy = @@ -893,7 +893,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessClamperController.onDisplayChanged( new BrightnessClamperController.DisplayDeviceData(uniqueId, thermalBrightnessThrottlingDataId, powerThrottlingDataId, - config, mDisplayId)); + config, info.width, info.height, token, mDisplayId)); if (changed) { updatePowerState(); diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index 88d2c007cf37..afab7438bf16 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -28,13 +28,16 @@ import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManagerInternal; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.IBinder; import android.os.PowerManager; import android.provider.DeviceConfig; import android.provider.DeviceConfigInterface; import android.util.IndentingPrintWriter; import android.util.Slog; +import android.util.Spline; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; @@ -65,6 +68,11 @@ public class BrightnessClamperController { private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers; private final List<BrightnessStateModifier> mModifiers; + + private final List<DisplayDeviceDataListener> mDisplayDeviceDataListeners = new ArrayList<>(); + private final List<StatefulModifier> mStatefulModifiers = new ArrayList<>(); + private ModifiersAggregatedState mModifiersAggregatedState = new ModifiersAggregatedState(); + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener; private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; @@ -110,7 +118,16 @@ public class BrightnessClamperController { mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags, context); mModifiers = injector.getModifiers(flags, context, handler, clamperChangeListener, - data.mDisplayDeviceConfig); + data); + + mModifiers.forEach(m -> { + if (m instanceof DisplayDeviceDataListener l) { + mDisplayDeviceDataListeners.add(l); + } + if (m instanceof StatefulModifier s) { + mStatefulModifiers.add(s); + } + }); mOnPropertiesChangedListener = properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId()); @@ -123,6 +140,7 @@ public class BrightnessClamperController { public void onDisplayChanged(DisplayDeviceData data) { mLightSensorController.configure(data.getAmbientLightSensor(), data.getDisplayId()); mClampers.forEach(clamper -> clamper.onDisplayChanged(data)); + mDisplayDeviceDataListeners.forEach(l -> l.onDisplayChanged(data)); adjustLightSensorSubscription(); } @@ -234,14 +252,27 @@ public class BrightnessClamperController { customAnimationRate = minClamper.getCustomAnimationRate(); } + ModifiersAggregatedState newAggregatedState = new ModifiersAggregatedState(); + mStatefulModifiers.forEach((clamper) -> clamper.applyStateChange(newAggregatedState)); + if (mBrightnessCap != brightnessCap || mClamperType != clamperType - || mCustomAnimationRate != customAnimationRate) { + || mCustomAnimationRate != customAnimationRate + || needToNotifyExternalListener(mModifiersAggregatedState, newAggregatedState)) { mBrightnessCap = brightnessCap; mClamperType = clamperType; mCustomAnimationRate = customAnimationRate; mClamperChangeListenerExternal.onChanged(); } + mModifiersAggregatedState = newAggregatedState; + } + + private boolean needToNotifyExternalListener(ModifiersAggregatedState state1, + ModifiersAggregatedState state2) { + return !BrightnessSynchronizer.floatEquals(state1.mMaxDesiredHdrRatio, + state2.mMaxDesiredHdrRatio) + || state1.mSdrHdrRatioSpline != state2.mSdrHdrRatioSpline + || state1.mHdrHbmEnabled != state2.mHdrHbmEnabled; } private void start() { @@ -295,17 +326,16 @@ public class BrightnessClamperController { List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context, Handler handler, ClamperChangeListener listener, - DisplayDeviceConfig displayDeviceConfig) { + DisplayDeviceData data) { List<BrightnessStateModifier> modifiers = new ArrayList<>(); modifiers.add(new DisplayDimModifier(context)); modifiers.add(new BrightnessLowPowerModeModifier()); - if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null - && displayDeviceConfig.isEvenDimmerAvailable()) { + if (flags.isEvenDimmerEnabled() && data.mDisplayDeviceConfig.isEvenDimmerAvailable()) { modifiers.add(new BrightnessLowLuxModifier(handler, listener, context, - displayDeviceConfig)); + data.mDisplayDeviceConfig)); } if (flags.useNewHdrBrightnessModifier()) { - modifiers.add(new HdrBrightnessModifier()); + modifiers.add(new HdrBrightnessModifier(handler, listener, data)); } return modifiers; } @@ -319,7 +349,14 @@ public class BrightnessClamperController { } /** - * Config Data for clampers + * Modifier should implement this interface in order to receive display change updates + */ + interface DisplayDeviceDataListener { + void onDisplayChanged(DisplayDeviceData displayData); + } + + /** + * Config Data for clampers/modifiers */ public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData, BrightnessPowerClamper.PowerData, @@ -331,23 +368,34 @@ public class BrightnessClamperController { @NonNull private final String mPowerThrottlingDataId; @NonNull - private final DisplayDeviceConfig mDisplayDeviceConfig; + final DisplayDeviceConfig mDisplayDeviceConfig; + + final int mWidth; - private final int mDisplayId; + final int mHeight; + + final IBinder mDisplayToken; + + final int mDisplayId; public DisplayDeviceData(@NonNull String uniqueDisplayId, @NonNull String thermalThrottlingDataId, @NonNull String powerThrottlingDataId, @NonNull DisplayDeviceConfig displayDeviceConfig, + int width, + int height, + IBinder displayToken, int displayId) { mUniqueDisplayId = uniqueDisplayId; mThermalThrottlingDataId = thermalThrottlingDataId; mPowerThrottlingDataId = powerThrottlingDataId; mDisplayDeviceConfig = displayDeviceConfig; + mWidth = width; + mHeight = height; + mDisplayToken = displayToken; mDisplayId = displayId; } - @NonNull @Override public String getUniqueDisplayId() { @@ -406,4 +454,24 @@ public class BrightnessClamperController { return mDisplayId; } } + + /** + * Stateful modifier should implement this interface and modify aggregatedState. + * AggregatedState is used by Controller to determine if updatePowerState call is needed + * to correctly adjust brightness + */ + interface StatefulModifier { + void applyStateChange(ModifiersAggregatedState aggregatedState); + } + + /** + * StatefulModifiers contribute to AggregatedState, that is used to decide if brightness + * adjustement is needed + */ + public static class ModifiersAggregatedState { + float mMaxDesiredHdrRatio = HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO; + @Nullable + Spline mSdrHdrRatioSpline = null; + boolean mHdrHbmEnabled = false; + } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java index a829866ae6fc..2ee70fd88919 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java +++ b/services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java @@ -16,17 +16,85 @@ package com.android.server.display.brightness.clamper; +import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.hardware.display.DisplayManagerInternal; +import android.os.Handler; +import android.os.IBinder; +import android.view.SurfaceControlHdrLayerInfoListener; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.config.HdrBrightnessData; import java.io.PrintWriter; -public class HdrBrightnessModifier implements BrightnessStateModifier { +public class HdrBrightnessModifier implements BrightnessStateModifier, + BrightnessClamperController.DisplayDeviceDataListener, + BrightnessClamperController.StatefulModifier { + + static final float DEFAULT_MAX_HDR_SDR_RATIO = 1.0f; + private static final float DEFAULT_HDR_LAYER_SIZE = -1.0f; + + private final SurfaceControlHdrLayerInfoListener mHdrListener = + new SurfaceControlHdrLayerInfoListener() { + @Override + public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, + int maxH, int flags, float maxDesiredHdrSdrRatio) { + boolean hdrLayerPresent = numberOfHdrLayers > 0; + mHandler.post(() -> HdrBrightnessModifier.this.onHdrInfoChanged( + hdrLayerPresent ? (float) (maxW * maxH) : DEFAULT_HDR_LAYER_SIZE, + hdrLayerPresent ? maxDesiredHdrSdrRatio : DEFAULT_MAX_HDR_SDR_RATIO)); + } + }; + + private final Handler mHandler; + private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener; + private final Injector mInjector; + + private IBinder mRegisteredDisplayToken; + + private float mScreenSize; + private float mHdrLayerSize = DEFAULT_HDR_LAYER_SIZE; + private HdrBrightnessData mHdrBrightnessData; + private DisplayDeviceConfig mDisplayDeviceConfig; + private float mMaxDesiredHdrRatio = DEFAULT_MAX_HDR_SDR_RATIO; + private Mode mMode = Mode.NO_HDR; + + HdrBrightnessModifier(Handler handler, + BrightnessClamperController.ClamperChangeListener clamperChangeListener, + BrightnessClamperController.DisplayDeviceData displayData) { + this(handler, clamperChangeListener, new Injector(), displayData); + } + + @VisibleForTesting + HdrBrightnessModifier(Handler handler, + BrightnessClamperController.ClamperChangeListener clamperChangeListener, + Injector injector, + BrightnessClamperController.DisplayDeviceData displayData) { + mHandler = handler; + mClamperChangeListener = clamperChangeListener; + mInjector = injector; + onDisplayChanged(displayData); + } + + // Called in DisplayControllerHandler @Override public void apply(DisplayManagerInternal.DisplayPowerRequest request, DisplayBrightnessState.Builder stateBuilder) { - // noop + if (mHdrBrightnessData == null) { // no hdr data + return; + } + if (mMode == Mode.NO_HDR) { + return; + } + + float hdrBrightness = mDisplayDeviceConfig.getHdrBrightnessFromSdr( + stateBuilder.getBrightness(), mMaxDesiredHdrRatio, + mHdrBrightnessData.sdrToHdrRatioSpline); + stateBuilder.setHdrBrightness(hdrBrightness); } @Override @@ -34,11 +102,13 @@ public class HdrBrightnessModifier implements BrightnessStateModifier { // noop } + // Called in DisplayControllerHandler @Override public void stop() { - // noop + unregisterHdrListener(); } + @Override public boolean shouldListenToLightSensor() { return false; @@ -48,4 +118,117 @@ public class HdrBrightnessModifier implements BrightnessStateModifier { public void setAmbientLux(float lux) { // noop } + + @Override + public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData displayData) { + mHandler.post(() -> onDisplayChanged(displayData.mDisplayToken, displayData.mWidth, + displayData.mHeight, displayData.mDisplayDeviceConfig)); + } + + // Called in DisplayControllerHandler + @Override + public void applyStateChange( + BrightnessClamperController.ModifiersAggregatedState aggregatedState) { + if (mMode != Mode.NO_HDR) { + aggregatedState.mMaxDesiredHdrRatio = mMaxDesiredHdrRatio; + aggregatedState.mSdrHdrRatioSpline = mHdrBrightnessData.sdrToHdrRatioSpline; + aggregatedState.mHdrHbmEnabled = (mMode == Mode.HBM_HDR); + } + } + + // Called in DisplayControllerHandler + private void onDisplayChanged(IBinder displayToken, int width, int height, + DisplayDeviceConfig config) { + mDisplayDeviceConfig = config; + mScreenSize = (float) width * height; + HdrBrightnessData data = config.getHdrBrightnessData(); + if (data == null) { + unregisterHdrListener(); + } else { + registerHdrListener(displayToken); + } + recalculate(data, mMaxDesiredHdrRatio); + } + + // Called in DisplayControllerHandler + private void recalculate(@Nullable HdrBrightnessData data, float maxDesiredHdrRatio) { + Mode newMode = recalculateMode(data); + // if HDR mode changed, notify changed + boolean needToNotifyChange = mMode != newMode; + // If HDR mode is active, we need to check if other HDR params are changed + if (mMode != HdrBrightnessModifier.Mode.NO_HDR) { + if (!BrightnessSynchronizer.floatEquals(mMaxDesiredHdrRatio, maxDesiredHdrRatio) + || data != mHdrBrightnessData) { + needToNotifyChange = true; + } + } + + mMode = newMode; + mHdrBrightnessData = data; + mMaxDesiredHdrRatio = maxDesiredHdrRatio; + + if (needToNotifyChange) { + mClamperChangeListener.onChanged(); + } + } + + // Called in DisplayControllerHandler + private Mode recalculateMode(@Nullable HdrBrightnessData data) { + // no config + if (data == null) { + return Mode.NO_HDR; + } + // HDR layer < minHdr % for Nbm + if (mHdrLayerSize < mScreenSize * data.minimumHdrPercentOfScreenForNbm) { + return Mode.NO_HDR; + } + // HDR layer < minHdr % for Hbm, and HDR layer >= that minHdr % for Nbm + if (mHdrLayerSize < mScreenSize * data.minimumHdrPercentOfScreenForHbm) { + return Mode.NBM_HDR; + } + // HDR layer > that minHdr % for Hbm + return Mode.HBM_HDR; + } + + // Called in DisplayControllerHandler + private void onHdrInfoChanged(float hdrLayerSize, float maxDesiredHdrSdrRatio) { + mHdrLayerSize = hdrLayerSize; + recalculate(mHdrBrightnessData, maxDesiredHdrSdrRatio); + } + + // Called in DisplayControllerHandler + private void registerHdrListener(IBinder displayToken) { + if (mRegisteredDisplayToken == displayToken) { + return; + } + unregisterHdrListener(); + if (displayToken != null) { + mInjector.registerHdrListener(mHdrListener, displayToken); + mRegisteredDisplayToken = displayToken; + } + } + + // Called in DisplayControllerHandler + private void unregisterHdrListener() { + if (mRegisteredDisplayToken != null) { + mInjector.unregisterHdrListener(mHdrListener, mRegisteredDisplayToken); + mRegisteredDisplayToken = null; + mHdrLayerSize = DEFAULT_HDR_LAYER_SIZE; + } + } + + private enum Mode { + NO_HDR, NBM_HDR, HBM_HDR + } + + @SuppressLint("MissingPermission") + static class Injector { + void registerHdrListener(SurfaceControlHdrLayerInfoListener listener, IBinder token) { + listener.register(token); + } + + void unregisterHdrListener(SurfaceControlHdrLayerInfoListener listener, IBinder token) { + listener.unregister(token); + } + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index e982153acbd1..e04716ed80f6 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -24,6 +24,7 @@ import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -41,8 +42,8 @@ import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.server.display.DisplayBrightnessState; -import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState; import com.android.server.display.config.SensorData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DisplayManagerFlags; @@ -89,6 +90,10 @@ public class BrightnessClamperControllerTest { @Mock private BrightnessModifier mMockModifier; @Mock + private TestStatefulModifier mMockStatefulModifier; + @Mock + private TestDisplayListenerModifier mMockDisplayListenerModifier; + @Mock private DisplayManagerInternal.DisplayPowerRequest mMockRequest; @Mock @@ -99,7 +104,8 @@ public class BrightnessClamperControllerTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mTestInjector = new TestInjector(List.of(mMockClamper), List.of(mMockModifier)); + mTestInjector = new TestInjector(List.of(mMockClamper), + List.of(mMockModifier, mMockStatefulModifier, mMockDisplayListenerModifier)); when(mMockDisplayDeviceData.getDisplayId()).thenReturn(DISPLAY_ID); when(mMockDisplayDeviceData.getAmbientLightSensor()).thenReturn(mMockSensorData); @@ -168,6 +174,13 @@ public class BrightnessClamperControllerTest { } @Test + public void testOnDisplayChanged_DelegatesToDisplayListeners() { + mClamperController.onDisplayChanged(mMockDisplayDeviceData); + + verify(mMockDisplayListenerModifier).onDisplayChanged(mMockDisplayDeviceData); + } + + @Test public void testOnDisplayChanged_doesNotRestartLightSensor() { mClamperController.onDisplayChanged(mMockDisplayDeviceData); @@ -189,6 +202,8 @@ public class BrightnessClamperControllerTest { mClamperController.clamp(mMockRequest, initialBrightness, initialSlowChange, STATE_ON); verify(mMockModifier).apply(eq(mMockRequest), any()); + verify(mMockDisplayListenerModifier).apply(eq(mMockRequest), any()); + verify(mMockStatefulModifier).apply(eq(mMockRequest), any()); } @Test @@ -326,11 +341,40 @@ public class BrightnessClamperControllerTest { verify(mMockClamper).stop(); } + @Test + public void test_doesNotNotifyExternalListener_aggregatedStateNotChanged() { + mTestInjector.mCapturedChangeListener.onChanged(); + mTestHandler.flush(); + + verify(mMockExternalListener, never()).onChanged(); + } + + @Test + public void test_notifiesExternalListener_aggregatedStateChanged() { + doAnswer((invocation) -> { + ModifiersAggregatedState argument = invocation.getArgument(0); + argument.mHdrHbmEnabled = true; + return null; + }).when(mMockStatefulModifier).applyStateChange(any()); + mTestInjector.mCapturedChangeListener.onChanged(); + mTestHandler.flush(); + + verify(mMockExternalListener).onChanged(); + } + private BrightnessClamperController createBrightnessClamperController() { return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener, mMockDisplayDeviceData, mMockContext, mFlags, mSensorManager); } + interface TestDisplayListenerModifier extends BrightnessStateModifier, + BrightnessClamperController.DisplayDeviceDataListener { + } + + interface TestStatefulModifier extends BrightnessStateModifier, + BrightnessClamperController.StatefulModifier { + } + private class TestInjector extends BrightnessClamperController.Injector { private final List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> @@ -366,7 +410,7 @@ public class BrightnessClamperControllerTest { @Override List<BrightnessStateModifier> getModifiers(DisplayManagerFlags flags, Context context, Handler handler, BrightnessClamperController.ClamperChangeListener listener, - DisplayDeviceConfig displayDeviceConfig) { + BrightnessClamperController.DisplayDeviceData displayDeviceData) { return mModifiers; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt new file mode 100644 index 000000000000..5fd848f6adcc --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/ClamperTestUtils.kt @@ -0,0 +1,44 @@ +/* + * 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.brightness.clamper + +import android.os.IBinder +import android.view.Display +import com.android.server.display.DisplayDeviceConfig +import com.android.server.display.brightness.clamper.BrightnessClamperController.DisplayDeviceData + +fun createDisplayDeviceData( + displayDeviceConfig: DisplayDeviceConfig, + displayToken: IBinder, + uniqueDisplayId: String = "displayId", + thermalThrottlingDataId: String = "thermalId", + powerThrottlingDataId: String = "powerId", + width: Int = 100, + height: Int = 100, + displayId: Int = Display.DEFAULT_DISPLAY +): DisplayDeviceData { + return DisplayDeviceData( + uniqueDisplayId, + thermalThrottlingDataId, + powerThrottlingDataId, + displayDeviceConfig, + width, + height, + displayToken, + displayId + ) +}
\ No newline at end of file diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt new file mode 100644 index 000000000000..e9ec8112c399 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrBrightnessModifierTest.kt @@ -0,0 +1,252 @@ +/* + * 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.brightness.clamper + +import android.hardware.display.DisplayManagerInternal +import android.os.IBinder +import android.util.Spline +import android.view.SurfaceControlHdrLayerInfoListener +import androidx.test.filters.SmallTest +import com.android.server.display.DisplayBrightnessState +import com.android.server.display.DisplayDeviceConfig +import com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener +import com.android.server.display.brightness.clamper.BrightnessClamperController.ModifiersAggregatedState +import com.android.server.display.brightness.clamper.HdrBrightnessModifier.DEFAULT_MAX_HDR_SDR_RATIO +import com.android.server.display.brightness.clamper.HdrBrightnessModifier.Injector +import com.android.server.display.config.createHdrBrightnessData +import com.android.server.testutils.TestHandler +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +class HdrBrightnessModifierTest { + + private val testHandler = TestHandler(null) + private val testInjector = TestInjector() + private val mockChangeListener = mock<ClamperChangeListener>() + private val mockDisplayDeviceConfig = mock<DisplayDeviceConfig>() + private val mockDisplayBinder = mock<IBinder>() + private val mockDisplayBinderOther = mock<IBinder>() + private val mockSpline = mock<Spline>() + private val mockRequest = mock<DisplayManagerInternal.DisplayPowerRequest>() + + private lateinit var modifier: HdrBrightnessModifier + private val dummyData = createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinder) + private val dummyHdrData = createHdrBrightnessData() + + @Test + fun `change listener is not called on init`() { + initHdrModifier() + + verify(mockChangeListener, never()).onChanged() + } + + @Test + fun `hdr listener registered on init if hdr data is present`() { + initHdrModifier() + + assertThat(testInjector.registeredHdrListener).isNotNull() + assertThat(testInjector.registeredToken).isEqualTo(mockDisplayBinder) + } + + @Test + fun `hdr listener not registered on init if hdr data is missing`() { + whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(null) + modifier = HdrBrightnessModifier(testHandler, mockChangeListener, testInjector, dummyData) + + testHandler.flush() + + assertThat(testInjector.registeredHdrListener).isNull() + assertThat(testInjector.registeredToken).isNull() + } + + @Test + fun `unsubscribes hdr listener when display changed with no hdr data`() { + initHdrModifier() + + whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(null) + modifier.onDisplayChanged(dummyData) + testHandler.flush() + + assertThat(testInjector.registeredHdrListener).isNull() + assertThat(testInjector.registeredToken).isNull() + verify(mockChangeListener, never()).onChanged() + } + + @Test + fun `resubscribes hdr listener when display changed with different token`() { + initHdrModifier() + + modifier.onDisplayChanged( + createDisplayDeviceData(mockDisplayDeviceConfig, mockDisplayBinderOther)) + testHandler.flush() + + assertThat(testInjector.registeredHdrListener).isNotNull() + assertThat(testInjector.registeredToken).isEqualTo(mockDisplayBinderOther) + verify(mockChangeListener, never()).onChanged() + } + + @Test + fun `test NO_HDR mode`() { + initHdrModifier() + + whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(createHdrBrightnessData( + minimumHdrPercentOfScreenForNbm = 0.5f, + minimumHdrPercentOfScreenForHbm = 0.7f, + sdrToHdrRatioSpline = mockSpline + )) + // screen size = 10_000 + modifier.onDisplayChanged(createDisplayDeviceData( + mockDisplayDeviceConfig, mockDisplayBinder, + width = 100, + height = 100 + )) + testHandler.flush() + // hdr size = 900 + val desiredMaxHdrRatio = 8f + val hdrWidth = 30 + val hdrHeight = 30 + testInjector.registeredHdrListener!!.onHdrInfoChanged( + mockDisplayBinder, 1, hdrWidth, hdrHeight, 0, desiredMaxHdrRatio + ) + testHandler.flush() + + val modifierState = ModifiersAggregatedState() + modifier.applyStateChange(modifierState) + + assertThat(modifierState.mHdrHbmEnabled).isFalse() + assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(DEFAULT_MAX_HDR_SDR_RATIO) + assertThat(modifierState.mSdrHdrRatioSpline).isNull() + + val stateBuilder = DisplayBrightnessState.builder() + modifier.apply(mockRequest, stateBuilder) + + verify(mockDisplayDeviceConfig, never()).getHdrBrightnessFromSdr(any(), any(), any()) + assertThat(stateBuilder.hdrBrightness).isEqualTo(DisplayBrightnessState.BRIGHTNESS_NOT_SET) + } + + @Test + fun `test NBM_HDR mode`() { + initHdrModifier() + whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(createHdrBrightnessData( + minimumHdrPercentOfScreenForNbm = 0.5f, + minimumHdrPercentOfScreenForHbm = 0.7f, + sdrToHdrRatioSpline = mockSpline + )) + // screen size = 10_000 + modifier.onDisplayChanged(createDisplayDeviceData( + mockDisplayDeviceConfig, mockDisplayBinder, + width = 100, + height = 100 + )) + testHandler.flush() + // hdr size = 5_100 + val desiredMaxHdrRatio = 8f + val hdrWidth = 100 + val hdrHeight = 51 + testInjector.registeredHdrListener!!.onHdrInfoChanged( + mockDisplayBinder, 1, hdrWidth, hdrHeight, 0, desiredMaxHdrRatio + ) + testHandler.flush() + + val modifierState = ModifiersAggregatedState() + modifier.applyStateChange(modifierState) + + assertThat(modifierState.mHdrHbmEnabled).isFalse() + assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(desiredMaxHdrRatio) + assertThat(modifierState.mSdrHdrRatioSpline).isEqualTo(mockSpline) + + val expectedHdrBrightness = 0.85f + whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr( + 0f, desiredMaxHdrRatio, mockSpline)).thenReturn(expectedHdrBrightness) + val stateBuilder = DisplayBrightnessState.builder() + modifier.apply(mockRequest, stateBuilder) + + assertThat(stateBuilder.hdrBrightness).isEqualTo(expectedHdrBrightness) + } + + @Test + fun `test HBM_HDR mode`() { + initHdrModifier() + whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(createHdrBrightnessData( + minimumHdrPercentOfScreenForNbm = 0.5f, + minimumHdrPercentOfScreenForHbm = 0.7f, + sdrToHdrRatioSpline = mockSpline + )) + // screen size = 10_000 + modifier.onDisplayChanged(createDisplayDeviceData( + mockDisplayDeviceConfig, mockDisplayBinder, + width = 100, + height = 100 + )) + testHandler.flush() + // hdr size = 7_100 + val desiredMaxHdrRatio = 8f + val hdrWidth = 100 + val hdrHeight = 71 + testInjector.registeredHdrListener!!.onHdrInfoChanged( + mockDisplayBinder, 1, hdrWidth, hdrHeight, 0, desiredMaxHdrRatio + ) + testHandler.flush() + + val modifierState = ModifiersAggregatedState() + modifier.applyStateChange(modifierState) + + assertThat(modifierState.mHdrHbmEnabled).isTrue() + assertThat(modifierState.mMaxDesiredHdrRatio).isEqualTo(desiredMaxHdrRatio) + assertThat(modifierState.mSdrHdrRatioSpline).isEqualTo(mockSpline) + + val expectedHdrBrightness = 0.83f + whenever(mockDisplayDeviceConfig.getHdrBrightnessFromSdr( + 0f, desiredMaxHdrRatio, mockSpline)).thenReturn(expectedHdrBrightness) + val stateBuilder = DisplayBrightnessState.builder() + modifier.apply(mockRequest, stateBuilder) + + assertThat(stateBuilder.hdrBrightness).isEqualTo(expectedHdrBrightness) + } + + private fun initHdrModifier() { + whenever(mockDisplayDeviceConfig.hdrBrightnessData).thenReturn(dummyHdrData) + modifier = HdrBrightnessModifier(testHandler, mockChangeListener, testInjector, dummyData) + testHandler.flush() + } + + + internal class TestInjector : Injector() { + var registeredHdrListener: SurfaceControlHdrLayerInfoListener? = null + var registeredToken: IBinder? = null + + override fun registerHdrListener( + listener: SurfaceControlHdrLayerInfoListener, token: IBinder + ) { + registeredHdrListener = listener + registeredToken = token + } + + override fun unregisterHdrListener( + listener: SurfaceControlHdrLayerInfoListener, token: IBinder + ) { + registeredHdrListener = null + registeredToken = null + } + } +}
\ No newline at end of file |