From 057955ebbda61e9ca19f4349d61d6e41f2184319 Mon Sep 17 00:00:00 2001 From: Christine Franks Date: Mon, 14 Dec 2020 01:04:32 -0800 Subject: [PATCH] Add reduce bright colors to brightness tracker Bug: 168065315 Test: atest FrameworksServicesTests:ColorDisplayServiceTest and atest FrameworksServicesTests:ReduceBrightColorsTintControllerTest and atest FrameworksServicesTests:BrightnessMappingStrategyTest Change-Id: I1913d45dfda8ee6ba80346541d04371a9fa90826 --- core/api/system-current.txt | 3 + .../display/BrightnessChangeEvent.java | 58 ++++++- .../hardware/display/ColorDisplayManager.java | 99 +++++++++++ .../display/IColorDisplayManager.aidl | 6 + core/res/res/values/config.xml | 31 ++-- core/res/res/values/symbols.xml | 3 +- .../display/BrightnessMappingStrategy.java | 44 +++-- .../server/display/BrightnessTracker.java | 37 ++++- .../display/DisplayPowerController.java | 36 ++++ .../display/color/ColorDisplayService.java | 132 ++++++++++++++- .../ReduceBrightColorsTintController.java | 47 ++++-- .../BrightnessMappingStrategyTest.java | 25 +++ .../server/display/BrightnessTrackerTest.java | 31 ++++ .../ReduceBrightColorsTintControllerTest.java | 156 ++++++++++++++++++ 14 files changed, 649 insertions(+), 59 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 683bc844d2644..c050adcb61a06 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2875,6 +2875,9 @@ package android.hardware.display { field public final boolean nightMode; field public final String packageName; field public final float powerBrightnessFactor; + field public final boolean reduceBrightColors; + field public final float reduceBrightColorsOffset; + field public final int reduceBrightColorsStrength; field public final long timeStamp; } diff --git a/core/java/android/hardware/display/BrightnessChangeEvent.java b/core/java/android/hardware/display/BrightnessChangeEvent.java index e2d836c590998..a6c6b46d5b81d 100644 --- a/core/java/android/hardware/display/BrightnessChangeEvent.java +++ b/core/java/android/hardware/display/BrightnessChangeEvent.java @@ -65,6 +65,23 @@ public final class BrightnessChangeEvent implements Parcelable { /** If night mode color filter is active this will be the temperature in kelvin */ public final int colorTemperature; + /** Whether the bright color reduction color transform is active */ + public final boolean reduceBrightColors; + + /** How strong the bright color reduction color transform is set (only applicable if active), + * specified as an integer from 0 - 100, inclusive. This value (scaled to 0-1, inclusive) is + * then used in Ynew = (a * scaledStrength^2 + b * scaledStrength + c) * Ycurrent, where a, b, + * and c are coefficients provided in the bright color reduction coefficient matrix, and + * Ycurrent is the current hardware brightness in nits. + */ + public final int reduceBrightColorsStrength; + + /** Applied offset for the bright color reduction color transform (only applicable if active). + * The offset is computed by summing the coefficients a, b, and c, from the coefficient matrix + * and multiplying by the current brightness. + */ + public final float reduceBrightColorsOffset; + /** Brightness level before slider adjustment */ public final float lastBrightness; @@ -105,8 +122,9 @@ public final class BrightnessChangeEvent implements Parcelable { private BrightnessChangeEvent(float brightness, long timeStamp, String packageName, int userId, float[] luxValues, long[] luxTimestamps, float batteryLevel, float powerBrightnessFactor, boolean nightMode, int colorTemperature, - float lastBrightness, boolean isDefaultBrightnessConfig, boolean isUserSetBrightness, - long[] colorValueBuckets, long colorSampleDuration) { + boolean reduceBrightColors, int reduceBrightColorsStrength, + float reduceBrightColorsOffset, float lastBrightness, boolean isDefaultBrightnessConfig, + boolean isUserSetBrightness, long[] colorValueBuckets, long colorSampleDuration) { this.brightness = brightness; this.timeStamp = timeStamp; this.packageName = packageName; @@ -117,6 +135,9 @@ public final class BrightnessChangeEvent implements Parcelable { this.powerBrightnessFactor = powerBrightnessFactor; this.nightMode = nightMode; this.colorTemperature = colorTemperature; + this.reduceBrightColors = reduceBrightColors; + this.reduceBrightColorsStrength = reduceBrightColorsStrength; + this.reduceBrightColorsOffset = reduceBrightColorsOffset; this.lastBrightness = lastBrightness; this.isDefaultBrightnessConfig = isDefaultBrightnessConfig; this.isUserSetBrightness = isUserSetBrightness; @@ -136,6 +157,9 @@ public final class BrightnessChangeEvent implements Parcelable { this.powerBrightnessFactor = other.powerBrightnessFactor; this.nightMode = other.nightMode; this.colorTemperature = other.colorTemperature; + this.reduceBrightColors = other.reduceBrightColors; + this.reduceBrightColorsStrength = other.reduceBrightColorsStrength; + this.reduceBrightColorsOffset = other.reduceBrightColorsOffset; this.lastBrightness = other.lastBrightness; this.isDefaultBrightnessConfig = other.isDefaultBrightnessConfig; this.isUserSetBrightness = other.isUserSetBrightness; @@ -154,6 +178,9 @@ public final class BrightnessChangeEvent implements Parcelable { powerBrightnessFactor = source.readFloat(); nightMode = source.readBoolean(); colorTemperature = source.readInt(); + reduceBrightColors = source.readBoolean(); + reduceBrightColorsStrength = source.readInt(); + reduceBrightColorsOffset = source.readFloat(); lastBrightness = source.readFloat(); isDefaultBrightnessConfig = source.readBoolean(); isUserSetBrightness = source.readBoolean(); @@ -188,6 +215,9 @@ public final class BrightnessChangeEvent implements Parcelable { dest.writeFloat(powerBrightnessFactor); dest.writeBoolean(nightMode); dest.writeInt(colorTemperature); + dest.writeBoolean(reduceBrightColors); + dest.writeInt(reduceBrightColorsStrength); + dest.writeFloat(reduceBrightColorsOffset); dest.writeFloat(lastBrightness); dest.writeBoolean(isDefaultBrightnessConfig); dest.writeBoolean(isUserSetBrightness); @@ -207,6 +237,9 @@ public final class BrightnessChangeEvent implements Parcelable { private float mPowerBrightnessFactor; private boolean mNightMode; private int mColorTemperature; + private boolean mReduceBrightColors; + private int mReduceBrightColorsStrength; + private float mReduceBrightColorsOffset; private float mLastBrightness; private boolean mIsDefaultBrightnessConfig; private boolean mIsUserSetBrightness; @@ -273,6 +306,24 @@ public final class BrightnessChangeEvent implements Parcelable { return this; } + /** {@see BrightnessChangeEvent#reduceBrightColors} */ + public Builder setReduceBrightColors(boolean reduceBrightColors) { + mReduceBrightColors = reduceBrightColors; + return this; + } + + /** {@see BrightnessChangeEvent#reduceBrightColorsStrength} */ + public Builder setReduceBrightColorsStrength(int strength) { + mReduceBrightColorsStrength = strength; + return this; + } + + /** {@see BrightnessChangeEvent#reduceBrightColorsOffset} */ + public Builder setReduceBrightColorsOffset(float offset) { + mReduceBrightColorsOffset = offset; + return this; + } + /** {@see BrightnessChangeEvent#lastBrightness} */ public Builder setLastBrightness(float lastBrightness) { mLastBrightness = lastBrightness; @@ -304,7 +355,8 @@ public final class BrightnessChangeEvent implements Parcelable { public BrightnessChangeEvent build() { return new BrightnessChangeEvent(mBrightness, mTimeStamp, mPackageName, mUserId, mLuxValues, mLuxTimestamps, mBatteryLevel, - mPowerBrightnessFactor, mNightMode, mColorTemperature, mLastBrightness, + mPowerBrightnessFactor, mNightMode, mColorTemperature, mReduceBrightColors, + mReduceBrightColorsStrength, mReduceBrightColorsOffset, mLastBrightness, mIsDefaultBrightnessConfig, mIsUserSetBrightness, mColorValueBuckets, mColorSampleDuration); } diff --git a/core/java/android/hardware/display/ColorDisplayManager.java b/core/java/android/hardware/display/ColorDisplayManager.java index efeb89ed96ace..e247df320115a 100644 --- a/core/java/android/hardware/display/ColorDisplayManager.java +++ b/core/java/android/hardware/display/ColorDisplayManager.java @@ -437,6 +437,56 @@ public final class ColorDisplayManager { return mManager.isDisplayWhiteBalanceEnabled(); } + /** + * Enables or disables reduce bright colors. + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setReduceBrightColorsActivated(boolean activated) { + return mManager.setReduceBrightColorsActivated(activated); + } + + /** + * Returns whether reduce bright colors is currently enabled. + * + * @hide + */ + public boolean isReduceBrightColorsActivated() { + return mManager.isReduceBrightColorsActivated(); + } + + /** + * Set the strength level of bright color reduction to apply to the display. + * + * @param strength 0-100 (inclusive), where 100 is full strength + * @return whether the change was applied successfully + * @hide + */ + @RequiresPermission(Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS) + public boolean setReduceBrightColorsStrength(@IntRange(from = 0, to = 100) int strength) { + return mManager.setReduceBrightColorsStrength(strength); + } + + /** + * Gets the strength of the bright color reduction transform. + * + * @hide + */ + public int getReduceBrightColorsStrength() { + return mManager.getReduceBrightColorsStrength(); + } + + /** + * Gets the brightness impact of the bright color reduction transform, as in the factor by which + * the current brightness (in nits) should be multiplied to obtain the brightness offset 'b'. + * + * @hide + */ + public float getReduceBrightColorsOffsetFactor() { + return mManager.getReduceBrightColorsOffsetFactor(); + } + /** * Returns {@code true} if Night Display is supported by the device. * @@ -477,6 +527,15 @@ public final class ColorDisplayManager { return context.getResources().getBoolean(R.bool.config_displayWhiteBalanceAvailable); } + /** + * Returns {@code true} if reduce bright colors is supported by the device. + * + * @hide + */ + public static boolean isReduceBrightColorsAvailable(Context context) { + return context.getResources().getBoolean(R.bool.config_reduceBrightColorsAvailable); + } + /** * Check if the color transforms are color accelerated. Some transforms are experimental only * on non-accelerated platforms due to the performance implications. @@ -678,6 +737,46 @@ public final class ColorDisplayManager { } } + boolean isReduceBrightColorsActivated() { + try { + return mCdm.isReduceBrightColorsActivated(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setReduceBrightColorsActivated(boolean activated) { + try { + return mCdm.setReduceBrightColorsActivated(activated); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + int getReduceBrightColorsStrength() { + try { + return mCdm.getReduceBrightColorsStrength(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + boolean setReduceBrightColorsStrength(int strength) { + try { + return mCdm.setReduceBrightColorsStrength(strength); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + float getReduceBrightColorsOffsetFactor() { + try { + return mCdm.getReduceBrightColorsOffsetFactor(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + int getColorMode() { try { return mCdm.getColorMode(); diff --git a/core/java/android/hardware/display/IColorDisplayManager.aidl b/core/java/android/hardware/display/IColorDisplayManager.aidl index 7b1033df8b62d..ef4f5f9b05e97 100644 --- a/core/java/android/hardware/display/IColorDisplayManager.aidl +++ b/core/java/android/hardware/display/IColorDisplayManager.aidl @@ -45,4 +45,10 @@ interface IColorDisplayManager { boolean isDisplayWhiteBalanceEnabled(); boolean setDisplayWhiteBalanceEnabled(boolean enabled); + + boolean isReduceBrightColorsActivated(); + boolean setReduceBrightColorsActivated(boolean activated); + int getReduceBrightColorsStrength(); + boolean setReduceBrightColorsStrength(int strength); + float getReduceBrightColorsOffsetFactor(); } \ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 01b8efafa2fd1..5c56ce413c329 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -822,28 +822,21 @@ -0.198650895 - - -0.691218457 - 0.050135153 - 0.917684143 - -0.691218457 - 0.050135153 - 0.917684143 - -0.691218457 - 0.050135153 - 0.917684143 + + @bool/config_setColorTransformAccelerated + + + -0.4429953456 + -0.2434077725 + 0.9809063061 - 0.00000000000000154 - -1.0 - 1.045977011 - 0.00000000000000224 - -1.0 - 1.045977011 - 0.0000000000000022 - -1.0 - 1.045977011 + -0.000000000000001 + -0.955555555555554 + 1.000000000000000 diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 815330ff18515..9b930fc07f33f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3191,8 +3191,9 @@ + - + diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index b8e579d58794c..4832e46be8a82 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -300,6 +300,8 @@ public abstract class BrightnessMappingStrategy { /** @return The default brightness configuration. */ public abstract BrightnessConfiguration getDefaultConfig(); + /** Recalculates the backlight-to-nits and nits-to-backlight splines. */ + public abstract void recalculateSplines(boolean applyAdjustment, float[] adjustment); /** * Returns the timeout for the short term model @@ -657,6 +659,11 @@ public abstract class BrightnessMappingStrategy { return null; } + @Override + public void recalculateSplines(boolean applyAdjustment, float[] adjustment) { + // Do nothing. + } + @Override public void dump(PrintWriter pw) { pw.println("SimpleMappingStrategy"); @@ -696,7 +703,7 @@ public abstract class BrightnessMappingStrategy { // A spline mapping from nits to the corresponding backlight value, normalized to the range // [0, 1.0]. - private final Spline mNitsToBacklightSpline; + private Spline mNitsToBacklightSpline; // The default brightness configuration. private final BrightnessConfiguration mDefaultConfig; @@ -705,6 +712,11 @@ public abstract class BrightnessMappingStrategy { // a brightness in nits. private Spline mBacklightToNitsSpline; + private float[] mNits; + private int[] mBacklight; + + private boolean mBrightnessRangeAdjustmentApplied; + private float mMaxGamma; private float mAutoBrightnessAdjustment; private float mUserLux; @@ -726,15 +738,9 @@ public abstract class BrightnessMappingStrategy { mUserLux = -1; mUserBrightness = -1; - // Setup the backlight spline - final int N = nits.length; - float[] normalizedBacklight = new float[N]; - for (int i = 0; i < N; i++) { - normalizedBacklight[i] = normalizeAbsoluteBrightness(backlight[i]); - } - - mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight); - mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits); + mNits = nits; + mBacklight = backlight; + computeNitsBrightnessSplines(mNits); mDefaultConfig = config; if (mLoggingEnabled) { @@ -867,6 +873,12 @@ public abstract class BrightnessMappingStrategy { return mDefaultConfig; } + @Override + public void recalculateSplines(boolean applyAdjustment, float[] adjustedNits) { + mBrightnessRangeAdjustmentApplied = applyAdjustment; + computeNitsBrightnessSplines(mBrightnessRangeAdjustmentApplied ? adjustedNits : mNits); + } + @Override public void dump(PrintWriter pw) { pw.println("PhysicalMappingStrategy"); @@ -878,6 +890,18 @@ public abstract class BrightnessMappingStrategy { pw.println(" mUserLux=" + mUserLux); pw.println(" mUserBrightness=" + mUserBrightness); pw.println(" mDefaultConfig=" + mDefaultConfig); + pw.println(" mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied); + } + + private void computeNitsBrightnessSplines(float[] nits) { + final int len = nits.length; + float[] normalizedBacklight = new float[len]; + for (int i = 0; i < len; i++) { + normalizedBacklight[i] = normalizeAbsoluteBrightness(mBacklight[i]); + } + + mNitsToBacklightSpline = Spline.createSpline(nits, normalizedBacklight); + mBacklightToNitsSpline = Spline.createSpline(normalizedBacklight, nits); } private void computeSpline() { diff --git a/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java index 2375f746c57db..a186e334c5c0b 100644 --- a/services/core/java/com/android/server/display/BrightnessTracker.java +++ b/services/core/java/com/android/server/display/BrightnessTracker.java @@ -63,7 +63,6 @@ import android.view.Display; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; -import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.RingBuffer; import com.android.server.LocalServices; @@ -71,7 +70,6 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; @@ -80,7 +78,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; -import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayDeque; import java.util.ArrayList; @@ -118,6 +115,9 @@ public class BrightnessTracker { private static final String ATTR_BATTERY_LEVEL = "batteryLevel"; private static final String ATTR_NIGHT_MODE = "nightMode"; private static final String ATTR_COLOR_TEMPERATURE = "colorTemperature"; + private static final String ATTR_REDUCE_BRIGHT_COLORS = "reduceBrightColors"; + private static final String ATTR_REDUCE_BRIGHT_COLORS_STRENGTH = "reduceBrightColorsStrength"; + private static final String ATTR_REDUCE_BRIGHT_COLORS_OFFSET = "reduceBrightColorsOffset"; private static final String ATTR_LAST_NITS = "lastNits"; private static final String ATTR_DEFAULT_CONFIG = "defaultConfig"; private static final String ATTR_POWER_SAVE = "powerSaveFactor"; @@ -398,6 +398,10 @@ public class BrightnessTracker { builder.setNightMode(mInjector.isNightDisplayActivated(mContext)); builder.setColorTemperature(mInjector.getNightDisplayColorTemperature(mContext)); + builder.setReduceBrightColors(mInjector.isReduceBrightColorsActivated(mContext)); + builder.setReduceBrightColorsStrength(mInjector.getReduceBrightColorsStrength(mContext)); + builder.setReduceBrightColorsOffset(mInjector.getReduceBrightColorsOffsetFactor(mContext) + * brightness); if (mColorSamplingEnabled) { DisplayedContentSample sample = mInjector.sampleColor(mNoFramesToSample); @@ -563,6 +567,12 @@ public class BrightnessTracker { out.attributeBoolean(null, ATTR_NIGHT_MODE, toWrite[i].nightMode); out.attributeInt(null, ATTR_COLOR_TEMPERATURE, toWrite[i].colorTemperature); + out.attributeBoolean(null, ATTR_REDUCE_BRIGHT_COLORS, + toWrite[i].reduceBrightColors); + out.attributeInt(null, ATTR_REDUCE_BRIGHT_COLORS_STRENGTH, + toWrite[i].reduceBrightColorsStrength); + out.attributeFloat(null, ATTR_REDUCE_BRIGHT_COLORS_OFFSET, + toWrite[i].reduceBrightColorsOffset); out.attributeFloat(null, ATTR_LAST_NITS, toWrite[i].lastBrightness); out.attributeBoolean(null, ATTR_DEFAULT_CONFIG, @@ -641,6 +651,12 @@ public class BrightnessTracker { builder.setNightMode(parser.getAttributeBoolean(null, ATTR_NIGHT_MODE)); builder.setColorTemperature( parser.getAttributeInt(null, ATTR_COLOR_TEMPERATURE)); + builder.setReduceBrightColors( + parser.getAttributeBoolean(null, ATTR_REDUCE_BRIGHT_COLORS)); + builder.setReduceBrightColorsStrength( + parser.getAttributeInt(null, ATTR_REDUCE_BRIGHT_COLORS_STRENGTH)); + builder.setReduceBrightColorsOffset( + parser.getAttributeFloat(null, ATTR_REDUCE_BRIGHT_COLORS_OFFSET)); builder.setLastBrightness(parser.getAttributeFloat(null, ATTR_LAST_NITS)); String luxValue = parser.getAttributeValue(null, ATTR_LUX); @@ -1114,6 +1130,21 @@ public class BrightnessTracker { return context.getSystemService(ColorDisplayManager.class).isNightDisplayActivated(); } + public int getReduceBrightColorsStrength(Context context) { + return context.getSystemService(ColorDisplayManager.class) + .getReduceBrightColorsStrength(); + } + + public float getReduceBrightColorsOffsetFactor(Context context) { + return context.getSystemService(ColorDisplayManager.class) + .getReduceBrightColorsOffsetFactor(); + } + + public boolean isReduceBrightColorsActivated(Context context) { + return context.getSystemService(ColorDisplayManager.class) + .isReduceBrightColorsActivated(); + } + public DisplayedContentSample sampleColor(int noFramesToSample) { final DisplayManagerInternal displayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 13dc0b9be21f5..8c65158caab76 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -58,6 +58,8 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; +import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; +import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; import com.android.server.display.whitebalance.DisplayWhiteBalanceController; import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory; import com.android.server.display.whitebalance.DisplayWhiteBalanceSettings; @@ -346,6 +348,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @Nullable private final DisplayWhiteBalanceController mDisplayWhiteBalanceController; + private final ColorDisplayServiceInternal mCdsi; + private final float[] mNitsRange; + // A record of state for skipping brightness ramps. private int mSkipRampState = RAMP_STATE_SKIP_NONE; @@ -580,6 +585,37 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } mDisplayWhiteBalanceSettings = displayWhiteBalanceSettings; mDisplayWhiteBalanceController = displayWhiteBalanceController; + + if (displayDeviceConfig != null && displayDeviceConfig.getNits() != null) { + mNitsRange = displayDeviceConfig.getNits(); + } else { + Slog.w(TAG, "Screen brightness nits configuration is unavailable; falling back"); + mNitsRange = BrightnessMappingStrategy.getFloatArray(context.getResources() + .obtainTypedArray(com.android.internal.R.array.config_screenBrightnessNits)); + } + mCdsi = LocalServices.getService(ColorDisplayServiceInternal.class); + boolean active = mCdsi.setReduceBrightColorsListener(new ReduceBrightColorsListener() { + @Override + public void onReduceBrightColorsActivationChanged(boolean activated) { + applyReduceBrightColorsSplineAdjustment(); + } + + @Override + public void onReduceBrightColorsStrengthChanged(int strength) { + applyReduceBrightColorsSplineAdjustment(); + } + }); + if (active) { + applyReduceBrightColorsSplineAdjustment(); + } + } + + private void applyReduceBrightColorsSplineAdjustment() { + float[] adjustedNits = new float[mNitsRange.length]; + for (int i = 0; i < mNitsRange.length; i++) { + adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]); + } + mBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), adjustedNits); } private Sensor findDisplayLightSensor(String sensorType) { diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 4706edc151f8f..88b26680631e8 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -173,6 +173,7 @@ public final class ColorDisplayService extends SystemService { private ContentObserver mContentObserver; private DisplayWhiteBalanceListener mDisplayWhiteBalanceListener; + private ReduceBrightColorsListener mReduceBrightColorsListener; private NightDisplayAutoMode mNightDisplayAutoMode; @@ -617,18 +618,24 @@ public final class ColorDisplayService extends SystemService { if (mCurrentUser == UserHandle.USER_NULL) { return; } - mReduceBrightColorsTintController.setActivated( - Secure.getIntForUser(getContext().getContentResolver(), - Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0, mCurrentUser) == 1); + final boolean activated = Secure.getIntForUser(getContext().getContentResolver(), + Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 0, mCurrentUser) == 1; + mReduceBrightColorsTintController.setActivated(activated); + if (mReduceBrightColorsListener != null) { + mReduceBrightColorsListener.onReduceBrightColorsActivationChanged(activated); + } } private void onReduceBrightColorsStrengthLevelChanged() { if (mCurrentUser == UserHandle.USER_NULL) { return; } - mReduceBrightColorsTintController.setMatrix( - Secure.getIntForUser(getContext().getContentResolver(), - Secure.REDUCE_BRIGHT_COLORS_LEVEL, 0, mCurrentUser)); + final int strength = Secure.getIntForUser(getContext().getContentResolver(), + Secure.REDUCE_BRIGHT_COLORS_LEVEL, 0, mCurrentUser); + mReduceBrightColorsTintController.setMatrix(strength); + if (mReduceBrightColorsListener != null) { + mReduceBrightColorsListener.onReduceBrightColorsStrengthChanged(strength); + } } /** @@ -762,6 +769,22 @@ public final class ColorDisplayService extends SystemService { mCurrentUser) == 1; } + private boolean setReduceBrightColorsActivatedInternal(boolean activated) { + if (mCurrentUser == UserHandle.USER_NULL) { + return false; + } + return Secure.putIntForUser(getContext().getContentResolver(), + Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, activated ? 1 : 0, mCurrentUser); + } + + private boolean setReduceBrightColorsStrengthInternal(int strength) { + if (mCurrentUser == UserHandle.USER_NULL) { + return false; + } + return Secure.putIntForUser(getContext().getContentResolver(), + Secure.REDUCE_BRIGHT_COLORS_LEVEL, strength, mCurrentUser); + } + private boolean isDeviceColorManagedInternal() { final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class); return dtm.isDeviceColorManaged(); @@ -1468,6 +1491,31 @@ public final class ColorDisplayService extends SystemService { return isDisplayWhiteBalanceSettingEnabled(); } + /** + * Sets the listener and returns whether reduce bright colors is currently enabled. + */ + public boolean setReduceBrightColorsListener(ReduceBrightColorsListener listener) { + mReduceBrightColorsListener = listener; + return mReduceBrightColorsTintController.isActivated(); + } + + /** + * Returns whether reduce bright colors is currently active. + */ + public boolean isReduceBrightColorsActivated() { + return mReduceBrightColorsTintController.isActivated(); + } + + /** + * Gets the computed brightness, in nits, when the reduce bright colors feature is applied + * at the current strength. + * + * @hide + */ + public float getReduceBrightColorsAdjustedBrightnessNits(float nits) { + return mReduceBrightColorsTintController.getAdjustedBrightness(nits); + } + /** * Adds a {@link WeakReference} for a newly started activity, and * invokes {@link ColorTransformController#applyAppSaturation(float[], float[])} if needed. @@ -1491,6 +1539,22 @@ public final class ColorDisplayService extends SystemService { void onDisplayWhiteBalanceStatusChanged(boolean activated); } + /** + * Listener for changes in reduce bright colors status. + */ + public interface ReduceBrightColorsListener { + + /** + * Notify that the reduce bright colors activation status has changed. + */ + void onReduceBrightColorsActivationChanged(boolean activated); + + /** + * Notify that the reduce bright colors strength has changed. + */ + void onReduceBrightColorsStrengthChanged(int strength); + } + private final class TintHandler extends Handler { private TintHandler(Looper looper) { @@ -1786,6 +1850,62 @@ public final class ColorDisplayService extends SystemService { } } + @Override + public boolean isReduceBrightColorsActivated() { + final long token = Binder.clearCallingIdentity(); + try { + return mReduceBrightColorsTintController.isActivated(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setReduceBrightColorsActivated(boolean activated) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set reduce bright colors activation state"); + final long token = Binder.clearCallingIdentity(); + try { + return setReduceBrightColorsActivatedInternal(activated); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public int getReduceBrightColorsStrength() { + final long token = Binder.clearCallingIdentity(); + try { + return mReduceBrightColorsTintController.getStrength(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public float getReduceBrightColorsOffsetFactor() { + final long token = Binder.clearCallingIdentity(); + try { + return mReduceBrightColorsTintController.getOffsetFactor(); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public boolean setReduceBrightColorsStrength(int strength) { + getContext().enforceCallingOrSelfPermission( + Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS, + "Permission required to set reduce bright colors strength"); + final long token = Binder.clearCallingIdentity(); + try { + return setReduceBrightColorsStrengthInternal(strength); + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) { diff --git a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java index 7e120c91ce9df..cb93cc8fc3a7d 100644 --- a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java +++ b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java @@ -34,7 +34,7 @@ import java.util.Arrays; public class ReduceBrightColorsTintController extends TintController { private final float[] mMatrix = new float[16]; - private final float[] mCoefficients = new float[9]; + private final float[] mCoefficients = new float[3]; private int mStrength; @@ -42,8 +42,8 @@ public class ReduceBrightColorsTintController extends TintController { public void setUp(Context context, boolean needsLinear) { final String[] coefficients = context.getResources().getStringArray( needsLinear ? R.array.config_reduceBrightColorsCoefficients - : R.array.config_reduceBrightColorsCoefficientsNative); - for (int i = 0; i < 9 && i < coefficients.length; i++) { + : R.array.config_reduceBrightColorsCoefficientsNonlinear); + for (int i = 0; i < 3 && i < coefficients.length; i++) { mCoefficients[i] = Float.parseFloat(coefficients[i]); } } @@ -67,20 +67,11 @@ public class ReduceBrightColorsTintController extends TintController { Matrix.setIdentityM(mMatrix, 0); - final float percentageStrength = strengthLevel / 100f; - final float squaredPercentageStrength = percentageStrength * percentageStrength; - final float red = - squaredPercentageStrength * mCoefficients[0] + percentageStrength * mCoefficients[1] - + mCoefficients[2]; - final float green = - squaredPercentageStrength * mCoefficients[3] + percentageStrength * mCoefficients[4] - + mCoefficients[5]; - final float blue = - squaredPercentageStrength * mCoefficients[6] + percentageStrength * mCoefficients[7] - + mCoefficients[8]; - mMatrix[0] = clamp(red); - mMatrix[5] = clamp(green); - mMatrix[10] = clamp(blue); + // All three (r,g,b) components are equal and calculated with the same formula. + final float componentValue = computeComponentValue(strengthLevel); + mMatrix[0] = componentValue; + mMatrix[5] = componentValue; + mMatrix[10] = componentValue; } private float clamp(float value) { @@ -110,4 +101,26 @@ public class ReduceBrightColorsTintController extends TintController { public int getStrength() { return mStrength; } + + /** Returns the offset factor at Ymax. */ + public float getOffsetFactor() { + // Strength terms drop out as strength --> 1, leaving the coefficients. + return mCoefficients[0] + mCoefficients[1] + mCoefficients[2]; + } + + /** + * Returns the effective brightness (in nits), which has been adjusted to account for the effect + * of the bright color reduction. + */ + public float getAdjustedBrightness(float nits) { + return computeComponentValue(mStrength) * nits; + } + + private float computeComponentValue(int strengthLevel) { + final float percentageStrength = strengthLevel / 100f; + final float squaredPercentageStrength = percentageStrength * percentageStrength; + return clamp( + squaredPercentageStrength * mCoefficients[0] + percentageStrength * mCoefficients[1] + + mCoefficients[2]); + } } diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java index ae966aaf2b583..f0b4f1bec77b9 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -226,6 +226,31 @@ public class BrightnessMappingStrategyTest { strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01f /*tolerance*/); } + @Test + public void testPhysicalStrategyRecalculateSplines() { + Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, + BACKLIGHT_RANGE); + BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res); + float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length]; + for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) { + adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f; + } + + // Default is unadjusted + assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */); + assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */); + + // When adjustment is turned on, adjustment array is used + strategy.recalculateSplines(true, adjustedNits50p); + assertEquals(1.3425f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */); + assertEquals(239.25f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */); + + // When adjustment is turned off, adjustment array is ignored + strategy.recalculateSplines(false, adjustedNits50p); + assertEquals(2.685f, strategy.convertToNits(BACKLIGHT_RANGE[0]), 0.01f /* tolerance */); + assertEquals(478.5f, strategy.convertToNits(BACKLIGHT_RANGE[1]), 0.01f /* tolerance */); + } + @Test public void testDefaultStrategyIsPhysical() { Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT, diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java index e02a46af38490..d362791092540 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java @@ -335,6 +335,9 @@ public class BrightnessTrackerTest { assertEquals(0.5, event.batteryLevel, FLOAT_DELTA); assertTrue(event.nightMode); assertEquals(3333, event.colorTemperature); + assertTrue(event.reduceBrightColors); + assertEquals(40, event.reduceBrightColorsStrength); + assertEquals(20f, event.reduceBrightColorsOffset, FLOAT_DELTA); assertEquals("a.package", event.packageName); assertEquals(0, event.userId); assertArrayEquals(new long[] {1, 10, 100, 1000, 300, 30, 10, 1}, event.colorValueBuckets); @@ -561,6 +564,9 @@ public class BrightnessTrackerTest { mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339); + mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1); + mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40); + startTracker(mTracker); mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), batteryChangeEvent(30, 100)); @@ -592,6 +598,9 @@ public class BrightnessTrackerTest { assertEquals(0.3, event.batteryLevel, FLOAT_DELTA); assertTrue(event.nightMode); assertEquals(3339, event.colorTemperature); + assertTrue(event.reduceBrightColors); + assertEquals(40, event.reduceBrightColorsStrength); + assertEquals(20f, event.reduceBrightColorsOffset, FLOAT_DELTA); assertEquals(0.5f, event.powerBrightnessFactor, FLOAT_DELTA); assertTrue(event.isUserSetBrightness); assertFalse(event.isDefaultBrightnessConfig); @@ -606,6 +615,9 @@ public class BrightnessTrackerTest { mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1); mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339); + mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, 1); + mInjector.mSecureIntSettings.put(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, 40); + startTracker(mTracker); mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(), batteryChangeEvent(30, 100)); @@ -639,6 +651,7 @@ public class BrightnessTrackerTest { assertEquals(brightness, event.brightness, FLOAT_DELTA); assertEquals(0.3, event.batteryLevel, FLOAT_DELTA); assertTrue(event.nightMode); + assertTrue(event.reduceBrightColors); assertEquals(3339, event.colorTemperature); } @@ -661,6 +674,9 @@ public class BrightnessTrackerTest { builder.setBatteryLevel(0.7f); builder.setNightMode(false); builder.setColorTemperature(345); + builder.setReduceBrightColors(false); + builder.setReduceBrightColorsStrength(40); + builder.setReduceBrightColorsOffset(20f); builder.setLastBrightness(50f); builder.setColorValues(new long[] {23, 34, 45}, 1000L); BrightnessChangeEvent event = builder.build(); @@ -684,6 +700,9 @@ public class BrightnessTrackerTest { assertEquals(event.batteryLevel, event2.batteryLevel, FLOAT_DELTA); assertEquals(event.nightMode, event2.nightMode); assertEquals(event.colorTemperature, event2.colorTemperature); + assertEquals(event.reduceBrightColors, event2.reduceBrightColors); + assertEquals(event.reduceBrightColorsStrength, event2.reduceBrightColorsStrength); + assertEquals(event.reduceBrightColorsOffset, event2.reduceBrightColorsOffset, FLOAT_DELTA); assertEquals(event.lastBrightness, event2.lastBrightness, FLOAT_DELTA); assertArrayEquals(event.colorValueBuckets, event2.colorValueBuckets); assertEquals(event.colorSampleDuration, event2.colorSampleDuration); @@ -1019,6 +1038,18 @@ public class BrightnessTrackerTest { 0) == 1; } + @Override + public int getReduceBrightColorsStrength(Context context) { + return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_LEVEL, + 0); + } + + @Override + public boolean isReduceBrightColorsActivated(Context context) { + return mSecureIntSettings.getOrDefault(Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, + 0) == 1; + } + @Override public DisplayedContentSample sampleColor(int noFramesToSample) { return new DisplayedContentSample(600L, diff --git a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java b/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java new file mode 100644 index 0000000000000..35014dcb7492b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2021 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.color; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class ReduceBrightColorsTintControllerTest { + + private Context mContext; + + @Before + public void setUp() { + final Resources mockResources = mock(Resources.class); + when(mockResources.getStringArray( + com.android.internal.R.array.config_reduceBrightColorsCoefficients)) + .thenReturn(new String[]{"-0.000000000000001", "-0.955555555555554", + "1.000000000000000"}); + when(mockResources.getStringArray( + com.android.internal.R.array.config_reduceBrightColorsCoefficientsNonlinear)) + .thenReturn(new String[]{"-0.4429953456", "-0.2434077725", "0.9809063061"}); + mContext = mock(Context.class); + when(mContext.getResources()).thenReturn(mockResources); + } + + @Test + public void setAndGetMatrix() { + final ReduceBrightColorsTintController tintController = + new ReduceBrightColorsTintController(); + tintController.setUp(mContext, /* needsLinear= */ true); + tintController.setMatrix(50); + tintController.setActivated(true); + assertThat(tintController.getStrength()).isEqualTo(50); + assertThat(tintController.getMatrix()).usingTolerance(0.00001f) + .containsExactly( + 0.5222222f, 0f, 0f, 0f, + 0f, 0.5222222f, 0f, 0f, + 0f, 0f, 0.5222222f, 0f, + 0f, 0f, 0f, 1f) + .inOrder(); + } + + @Test + public void setAndGetMatrixClampToZero() { + final ReduceBrightColorsTintController tintController = + new ReduceBrightColorsTintController(); + tintController.setUp(mContext, /* needsLinear= */ true); + tintController.setMatrix(-50); + tintController.setActivated(true); + assertThat(tintController.getStrength()).isEqualTo(0); + assertThat(tintController.getMatrix()).usingTolerance(0.00001f) + .containsExactly( + 1f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, + 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f) + .inOrder(); + } + + @Test + public void setAndGetMatrixClampTo100() { + final ReduceBrightColorsTintController tintController = + new ReduceBrightColorsTintController(); + tintController.setUp(mContext, /* needsLinear= */ true); + tintController.setMatrix(120); + tintController.setActivated(true); + assertThat(tintController.getStrength()).isEqualTo(100); + assertThat(tintController.getMatrix()).usingTolerance(0.00001f) + .containsExactly( + 0.04444444f, 0f, 0f, 0f, + 0f, 0.04444444f, 0f, 0f, + 0f, 0f, 0.04444444f, 0f, + 0f, 0f, 0f, 1f) + .inOrder(); + } + + @Test + public void returnsIdentityMatrixWhenNotActivated() { + final ReduceBrightColorsTintController tintController = + new ReduceBrightColorsTintController(); + tintController.setUp(mContext, /* needsLinear= */ true); + tintController.setMatrix(50); + tintController.setActivated(true); + tintController.setActivated(false); + assertThat(tintController.getStrength()).isEqualTo(50); + assertThat(tintController.getMatrix()).usingTolerance(0.00001f) + .containsExactly( + 1f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, + 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f) + .inOrder(); + } + + @Test + public void getAdjustedBrightnessZeroRbcStrengthFullBrightness() { + final ReduceBrightColorsTintController tintController = + new ReduceBrightColorsTintController(); + tintController.setUp(mContext, /* needsLinear= */ true); + tintController.setMatrix(0); + assertThat(tintController.getAdjustedBrightness(450f)).isEqualTo(450f); + } + + @Test + public void getAdjustedBrightnessFullRbcStrengthFullBrightness() { + final ReduceBrightColorsTintController tintController = + new ReduceBrightColorsTintController(); + tintController.setUp(mContext, /* needsLinear= */ true); + tintController.setMatrix(100); + assertThat(tintController.getAdjustedBrightness(450f)).isEqualTo(19.999998f); + } + + @Test + public void getAdjustedBrightnessZeroRbcStrengthLowBrightness() { + final ReduceBrightColorsTintController tintController = + new ReduceBrightColorsTintController(); + tintController.setUp(mContext, /* needsLinear= */ true); + tintController.setMatrix(0); + assertThat(tintController.getAdjustedBrightness(2.2f)).isEqualTo(2.2f); + } + + @Test + public void getAdjustedBrightnessFullRbcStrengthLowBrightness() { + final ReduceBrightColorsTintController tintController = + new ReduceBrightColorsTintController(); + tintController.setUp(mContext, /* needsLinear= */ true); + tintController.setMatrix(100); + assertThat(tintController.getAdjustedBrightness(2.2f)).isEqualTo(0.09777778f); + } +}