diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index a4a6eb4312cca..8176f8d35d45c 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -30,6 +30,8 @@ import android.util.Slog; import android.util.Spline; import android.view.DisplayAddress; +import com.android.internal.annotations.VisibleForTesting; + import com.android.internal.R; import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.config.BrightnessThresholds; @@ -40,9 +42,12 @@ import com.android.server.display.config.DisplayConfiguration; import com.android.server.display.config.DisplayQuirks; import com.android.server.display.config.HbmTiming; import com.android.server.display.config.HighBrightnessMode; +import com.android.server.display.config.Interpolation; import com.android.server.display.config.NitsMap; import com.android.server.display.config.Point; import com.android.server.display.config.RefreshRateRange; +import com.android.server.display.config.SdrHdrRatioMap; +import com.android.server.display.config.SdrHdrRatioPoint; import com.android.server.display.config.SensorDetails; import com.android.server.display.config.ThermalStatus; import com.android.server.display.config.ThermalThrottling; @@ -70,6 +75,7 @@ import javax.xml.datatype.DatatypeConfigurationException; */ public class DisplayDeviceConfig { private static final String TAG = "DisplayDeviceConfig"; + private static final boolean DEBUG = false; public static final float HIGH_BRIGHTNESS_MODE_UNSUPPORTED = Float.NaN; @@ -86,6 +92,9 @@ public class DisplayDeviceConfig { private static final String NO_SUFFIX_FORMAT = "%d"; private static final long STABLE_FLAG = 1L << 62; + private static final int INTERPOLATION_DEFAULT = 0; + private static final int INTERPOLATION_LINEAR = 1; + // Float.NaN (used as invalid for brightness) cannot be stored in config.xml // so -2 is used instead private static final float INVALID_BRIGHTNESS_IN_CONFIG = -2f; @@ -99,6 +108,9 @@ public class DisplayDeviceConfig { // Length of the ambient light horizon used to calculate short-term estimate of ambient light. private static final int AMBIENT_LIGHT_SHORT_HORIZON_MILLIS = 2000; + @VisibleForTesting + static final float HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT = 0.5f; + private final Context mContext; // The details of the ambient light sensor associated with this display. @@ -114,6 +126,7 @@ public class DisplayDeviceConfig { // config.xml. These are the raw values and just used for the dumpsys private float[] mRawNits; private float[] mRawBacklight; + private int mInterpolationType; // These arrays are calculated from the raw arrays, but clamped to contain values equal to and // between mBacklightMinimum and mBacklightMaximum. These three arrays should all be the same @@ -142,6 +155,7 @@ public class DisplayDeviceConfig { private Spline mBrightnessToBacklightSpline; private Spline mBacklightToBrightnessSpline; private Spline mBacklightToNitsSpline; + private Spline mNitsToBacklightSpline; private List mQuirks; private boolean mIsHighBrightnessModeEnabled = false; private HighBrightnessModeData mHbmData; @@ -149,6 +163,7 @@ public class DisplayDeviceConfig { private String mLoadedFrom = null; private BrightnessThrottlingData mBrightnessThrottlingData; + private Spline mSdrToHdrRatioSpline; private DisplayDeviceConfig(Context context) { mContext = context; @@ -332,6 +347,45 @@ public class DisplayDeviceConfig { return mBacklightToNitsSpline.interpolate(backlight); } + /** + * Calculate the HDR brightness for the specified SDR brightenss. + * + * @return the HDR brightness or BRIGHTNESS_INVALID when no mapping exists. + */ + public float getHdrBrightnessFromSdr(float brightness) { + if (mSdrToHdrRatioSpline == null) { + return PowerManager.BRIGHTNESS_INVALID; + } + + float backlight = getBacklightFromBrightness(brightness); + float nits = getNitsFromBacklight(backlight); + if (nits == NITS_INVALID) { + return PowerManager.BRIGHTNESS_INVALID; + } + + float ratio = mSdrToHdrRatioSpline.interpolate(nits); + float hdrNits = nits * ratio; + if (mNitsToBacklightSpline == null) { + return PowerManager.BRIGHTNESS_INVALID; + } + + float hdrBacklight = mNitsToBacklightSpline.interpolate(hdrNits); + hdrBacklight = Math.max(mBacklightMinimum, Math.min(mBacklightMaximum, hdrBacklight)); + float hdrBrightness = mBacklightToBrightnessSpline.interpolate(hdrBacklight); + + if (DEBUG) { + Slog.d(TAG, "getHdrBrightnessFromSdr: sdr brightness " + brightness + + " backlight " + backlight + + " nits " + nits + + " ratio " + ratio + + " hdrNits " + hdrNits + + " hdrBacklight " + hdrBacklight + + " hdrBrightness " + hdrBrightness + ); + } + return hdrBrightness; + } + /** * Return an array of equal length to backlight and nits, that covers the entire system * brightness range of 0.0-1.0. @@ -444,15 +498,18 @@ public class DisplayDeviceConfig { + ", mNits=" + Arrays.toString(mNits) + ", mRawBacklight=" + Arrays.toString(mRawBacklight) + ", mRawNits=" + Arrays.toString(mRawNits) + + ", mInterpolationType=" + mInterpolationType + ", mBrightness=" + Arrays.toString(mBrightness) + ", mBrightnessToBacklightSpline=" + mBrightnessToBacklightSpline + ", mBacklightToBrightnessSpline=" + mBacklightToBrightnessSpline + + ", mNitsToBacklightSpline=" + mNitsToBacklightSpline + ", mBacklightMinimum=" + mBacklightMinimum + ", mBacklightMaximum=" + mBacklightMaximum + ", mBrightnessDefault=" + mBrightnessDefault + ", mQuirks=" + mQuirks + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled + ", mHbmData=" + mHbmData + + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline + ", mBrightnessThrottlingData=" + mBrightnessThrottlingData + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease @@ -653,6 +710,7 @@ public class DisplayDeviceConfig { float[] nits = new float[size]; float[] backlight = new float[size]; + mInterpolationType = convertInterpolationType(map.getInterpolation()); int i = 0; for (Point point : points) { nits[i] = point.getNits().floatValue(); @@ -678,6 +736,40 @@ public class DisplayDeviceConfig { constrainNitsAndBacklightArrays(); } + private Spline loadSdrHdrRatioMap(HighBrightnessMode hbmConfig) { + final SdrHdrRatioMap sdrHdrRatioMap = hbmConfig.getSdrHdrRatioMap_all(); + + if (sdrHdrRatioMap == null) { + return null; + } + + final List points = sdrHdrRatioMap.getPoint(); + final int size = points.size(); + if (size <= 0) { + return null; + } + + float[] nits = new float[size]; + float[] ratios = new float[size]; + + int i = 0; + for (SdrHdrRatioPoint point : points) { + nits[i] = point.getSdrNits().floatValue(); + if (i > 0) { + if (nits[i] < nits[i - 1]) { + Slog.e(TAG, "sdrHdrRatioMap must be non-decreasing, ignoring rest " + + " of configuration. nits: " + nits[i] + " < " + + nits[i - 1]); + return null; + } + } + ratios[i] = point.getHdrRatio().floatValue(); + ++i; + } + + return Spline.createSpline(nits, ratios); + } + private void loadBrightnessThrottlingMap(DisplayConfiguration config) { final ThermalThrottling throttlingConfig = config.getThermalThrottling(); if (throttlingConfig == null) { @@ -823,9 +915,18 @@ public class DisplayDeviceConfig { mBacklight[mBacklight.length - 1], PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, mBacklight[i]); } - mBrightnessToBacklightSpline = Spline.createSpline(mBrightness, mBacklight); - mBacklightToBrightnessSpline = Spline.createSpline(mBacklight, mBrightness); - mBacklightToNitsSpline = Spline.createSpline(mBacklight, mNits); + mBrightnessToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR + ? Spline.createLinearSpline(mBrightness, mBacklight) + : Spline.createSpline(mBrightness, mBacklight); + mBacklightToBrightnessSpline = mInterpolationType == INTERPOLATION_LINEAR + ? Spline.createLinearSpline(mBacklight, mBrightness) + : Spline.createSpline(mBacklight, mBrightness); + mBacklightToNitsSpline = mInterpolationType == INTERPOLATION_LINEAR + ? Spline.createLinearSpline(mBacklight, mNits) + : Spline.createSpline(mBacklight, mNits); + mNitsToBacklightSpline = mInterpolationType == INTERPOLATION_LINEAR + ? Spline.createLinearSpline(mNits, mBacklight) + : Spline.createSpline(mNits, mBacklight); } private void loadQuirks(DisplayConfiguration config) { @@ -862,6 +963,20 @@ public class DisplayDeviceConfig { mRefreshRateLimitations.add(new RefreshRateLimitation( DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, min, max)); } + BigDecimal minHdrPctOfScreen = hbm.getMinimumHdrPercentOfScreen_all(); + if (minHdrPctOfScreen != null) { + mHbmData.minimumHdrPercentOfScreen = minHdrPctOfScreen.floatValue(); + if (mHbmData.minimumHdrPercentOfScreen > 1 + || mHbmData.minimumHdrPercentOfScreen < 0) { + Slog.w(TAG, "Invalid minimum HDR percent of screen: " + + String.valueOf(mHbmData.minimumHdrPercentOfScreen)); + mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT; + } + } else { + mHbmData.minimumHdrPercentOfScreen = HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT; + } + + mSdrToHdrRatioSpline = loadSdrHdrRatioMap(hbm); } } @@ -1024,6 +1139,21 @@ public class DisplayDeviceConfig { } } + private int convertInterpolationType(Interpolation value) { + if (value == null) { + return INTERPOLATION_DEFAULT; + } + switch (value) { + case _default: + return INTERPOLATION_DEFAULT; + case linear: + return INTERPOLATION_LINEAR; + default: + Slog.wtf(TAG, "Unexpected Interpolation Type: " + value); + return INTERPOLATION_DEFAULT; + } + } + private void loadAmbientHorizonFromDdc(DisplayConfiguration config) { final BigInteger configLongHorizon = config.getAmbientLightHorizonLong(); if (configLongHorizon != null) { @@ -1088,11 +1218,15 @@ public class DisplayDeviceConfig { /** Minimum time that HBM can be on before being enabled. */ public long timeMinMillis; + /** Minimum HDR video size to enter high brightness mode */ + public float minimumHdrPercentOfScreen; + HighBrightnessModeData() {} HighBrightnessModeData(float minimumLux, float transitionPoint, long timeWindowMillis, long timeMaxMillis, long timeMinMillis, - @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode) { + @PowerManager.ThermalStatus int thermalStatusLimit, boolean allowInLowPowerMode, + float minimumHdrPercentOfScreen) { this.minimumLux = minimumLux; this.transitionPoint = transitionPoint; this.timeWindowMillis = timeWindowMillis; @@ -1100,6 +1234,7 @@ public class DisplayDeviceConfig { this.timeMinMillis = timeMinMillis; this.thermalStatusLimit = thermalStatusLimit; this.allowInLowPowerMode = allowInLowPowerMode; + this.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen; } /** @@ -1114,6 +1249,7 @@ public class DisplayDeviceConfig { other.transitionPoint = transitionPoint; other.thermalStatusLimit = thermalStatusLimit; other.allowInLowPowerMode = allowInLowPowerMode; + other.minimumHdrPercentOfScreen = minimumHdrPercentOfScreen; } @Override @@ -1126,6 +1262,7 @@ public class DisplayDeviceConfig { + ", timeMin: " + timeMinMillis + "ms" + ", thermalStatusLimit: " + thermalStatusLimit + ", allowInLowPowerMode: " + allowInLowPowerMode + + ", minimumHdrPercentOfScreen: " + minimumHdrPercentOfScreen + "} "; } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 1f448549c59c7..f7947c9caf2bc 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -831,7 +831,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call setUpAutoBrightness(mContext.getResources(), mHandler); reloadReduceBrightColours(); mHbmController.resetHbmData(info.width, info.height, token, info.uniqueId, - mDisplayDeviceConfig.getHighBrightnessModeData()); + mDisplayDeviceConfig.getHighBrightnessModeData(), + new HighBrightnessModeController.HdrBrightnessDeviceConfig() { + @Override + public float getHdrBrightnessFromSdr(float sdrBrightness) { + return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness); + } + }); mBrightnessThrottler.resetThrottlingData( mDisplayDeviceConfig.getBrightnessThrottlingData()); } @@ -1697,6 +1703,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); return new HighBrightnessModeController(mHandler, info.width, info.height, displayToken, displayUniqueId, PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, hbmData, + new HighBrightnessModeController.HdrBrightnessDeviceConfig() { + @Override + public float getHdrBrightnessFromSdr(float sdrBrightness) { + return mDisplayDeviceConfig.getHdrBrightnessFromSdr(sdrBrightness); + } + }, () -> { sendUpdatePowerStateLocked(); postBrightnessChangeRunnable(); diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java index 534ed5d8491a9..039edfe8d62e5 100644 --- a/services/core/java/com/android/server/display/HighBrightnessModeController.java +++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java @@ -58,11 +58,13 @@ class HighBrightnessModeController { private static final boolean DEBUG = false; - private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f; - @VisibleForTesting static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY; + public interface HdrBrightnessDeviceConfig { + float getHdrBrightnessFromSdr(float sdrBrightness); + } + private final float mBrightnessMin; private final float mBrightnessMax; private final Handler mHandler; @@ -76,6 +78,7 @@ class HighBrightnessModeController { private HdrListener mHdrListener; private HighBrightnessModeData mHbmData; + private HdrBrightnessDeviceConfig mHdrBrightnessCfg; private IBinder mRegisteredDisplayToken; private boolean mIsInAllowedAmbientRange = false; @@ -115,16 +118,17 @@ class HighBrightnessModeController { HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, - HighBrightnessModeData hbmData, Runnable hbmChangeCallback, Context context) { + HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, + Runnable hbmChangeCallback, Context context) { this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin, - brightnessMax, hbmData, hbmChangeCallback, context); + brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, context); } @VisibleForTesting HighBrightnessModeController(Injector injector, Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, - HighBrightnessModeData hbmData, Runnable hbmChangeCallback, - Context context) { + HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, + Runnable hbmChangeCallback, Context context) { mInjector = injector; mContext = context; mClock = injector.getClock(); @@ -138,7 +142,7 @@ class HighBrightnessModeController { mRecalcRunnable = this::recalculateTimeAllowance; mHdrListener = new HdrListener(); - resetHbmData(width, height, displayToken, displayUniqueId, hbmData); + resetHbmData(width, height, displayToken, displayUniqueId, hbmData, hdrBrightnessCfg); } void setAutoBrightnessEnabled(int state) { @@ -178,6 +182,13 @@ class HighBrightnessModeController { } float getHdrBrightnessValue() { + if (mHdrBrightnessCfg != null) { + float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr(mBrightness); + if (hdrBrightness != PowerManager.BRIGHTNESS_INVALID) { + return hdrBrightness; + } + } + // For HDR brightness, we take the current brightness and scale it to the max. The reason // we do this is because we want brightness to go to HBM max when it would normally go // to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition @@ -250,10 +261,11 @@ class HighBrightnessModeController { } void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, - HighBrightnessModeData hbmData) { + HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) { mWidth = width; mHeight = height; mHbmData = hbmData; + mHdrBrightnessCfg = hdrBrightnessCfg; mDisplayStatsId = displayUniqueId.hashCode(); unregisterHdrListener(); @@ -598,8 +610,8 @@ class HighBrightnessModeController { int maxW, int maxH, int flags) { mHandler.post(() -> { mIsHdrLayerPresent = numberOfHdrLayers > 0 - && (float) (maxW * maxH) - >= ((float) (mWidth * mHeight) * HDR_PERCENT_OF_SCREEN_REQUIRED); + && (float) (maxW * maxH) >= ((float) (mWidth * mHeight) + * mHbmData.minimumHdrPercentOfScreen); // Calling the brightness update so that we can recalculate // brightness with HDR in mind. onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason); diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java index b7af010103bc2..09e3a7b634724 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -22,11 +22,14 @@ import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; + import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; import static com.android.server.display.AutomaticBrightnessController .AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE; +import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT; + import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID; import static org.junit.Assert.assertEquals; @@ -111,7 +114,8 @@ public class HighBrightnessModeControllerTest { private static final HighBrightnessModeData DEFAULT_HBM_DATA = new HighBrightnessModeData(MINIMUM_LUX, TRANSITION_POINT, TIME_WINDOW_MILLIS, TIME_ALLOWED_IN_WINDOW_MILLIS, TIME_MINIMUM_AVAILABLE_TO_ENABLE_MILLIS, - THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE); + THERMAL_STATUS_LIMIT, ALLOW_IN_LOW_POWER_MODE, + HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT); @Before public void setUp() { @@ -136,7 +140,7 @@ public class HighBrightnessModeControllerTest { initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, - mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy); + mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy); assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f); } @@ -146,7 +150,7 @@ public class HighBrightnessModeControllerTest { initHandler(null); final HighBrightnessModeController hbmc = new HighBrightnessModeController( mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, - mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, () -> {}, mContextSpy); + mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, null, null, () -> {}, mContextSpy); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF); @@ -679,7 +683,7 @@ public class HighBrightnessModeControllerTest { initHandler(clock); return new HighBrightnessModeController(mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, mDisplayUniqueId, DEFAULT_MIN, DEFAULT_MAX, - DEFAULT_HBM_DATA, () -> {}, mContextSpy); + DEFAULT_HBM_DATA, null, () -> {}, mContextSpy); } private void initHandler(OffsettableClock clock) {