Loading services/core/java/com/android/server/display/DisplayDeviceConfig.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; } Loading services/core/java/com/android/server/display/DisplayPowerController.java +10 −10 Original line number Diff line number Diff line Loading @@ -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(), Loading @@ -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(); Loading Loading @@ -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, Loading @@ -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 = Loading Loading @@ -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(); Loading services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +79 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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()); Loading @@ -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(); } Loading Loading @@ -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() { Loading Loading @@ -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; } Loading @@ -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, Loading @@ -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() { Loading Loading @@ -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; } } services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java +186 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; Loading @@ -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); } } } services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +47 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); Loading Loading @@ -167,6 +173,13 @@ public class BrightnessClamperControllerTest { verify(mMockClamper).onDisplayChanged(mMockDisplayDeviceData); } @Test public void testOnDisplayChanged_DelegatesToDisplayListeners() { mClamperController.onDisplayChanged(mMockDisplayDeviceData); verify(mMockDisplayListenerModifier).onDisplayChanged(mMockDisplayDeviceData); } @Test public void testOnDisplayChanged_doesNotRestartLightSensor() { mClamperController.onDisplayChanged(mMockDisplayDeviceData); Loading @@ -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 Loading Loading @@ -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>> Loading Loading @@ -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; } Loading Loading
services/core/java/com/android/server/display/DisplayDeviceConfig.java +12 −0 Original line number Diff line number Diff line Loading @@ -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; } Loading
services/core/java/com/android/server/display/DisplayPowerController.java +10 −10 Original line number Diff line number Diff line Loading @@ -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(), Loading @@ -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(); Loading Loading @@ -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, Loading @@ -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 = Loading Loading @@ -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(); Loading
services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +79 −11 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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()); Loading @@ -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(); } Loading Loading @@ -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() { Loading Loading @@ -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; } Loading @@ -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, Loading @@ -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() { Loading Loading @@ -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; } }
services/core/java/com/android/server/display/brightness/clamper/HdrBrightnessModifier.java +186 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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; Loading @@ -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); } } }
services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +47 −3 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -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); Loading Loading @@ -167,6 +173,13 @@ public class BrightnessClamperControllerTest { verify(mMockClamper).onDisplayChanged(mMockDisplayDeviceData); } @Test public void testOnDisplayChanged_DelegatesToDisplayListeners() { mClamperController.onDisplayChanged(mMockDisplayDeviceData); verify(mMockDisplayListenerModifier).onDisplayChanged(mMockDisplayDeviceData); } @Test public void testOnDisplayChanged_doesNotRestartLightSensor() { mClamperController.onDisplayChanged(mMockDisplayDeviceData); Loading @@ -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 Loading Loading @@ -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>> Loading Loading @@ -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; } Loading