Load even dimmer config mappings

- Allow minimum nits to be specified in even dimmer config
- Adjust strength based on requested nits
- Add tests

Bug: 369217612
Flag: com.android.server.display.feature.flags.even_dimmer
Test: atest DisplayServiceTests
Change-Id: Idcdaf087f13e74f7f61b1ad510db6d1eb9b17d67
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index dc263c5..af9c9ac 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1080,7 +1080,7 @@
      */
     public float[] getNits() {
         if (mEvenDimmerBrightnessData != null) {
-            return mEvenDimmerBrightnessData.mNits;
+            return mEvenDimmerBrightnessData.nits;
         }
         return mNits;
     }
@@ -1093,7 +1093,7 @@
     @VisibleForTesting
     public float[] getBacklight() {
         if (mEvenDimmerBrightnessData != null) {
-            return mEvenDimmerBrightnessData.mBacklight;
+            return mEvenDimmerBrightnessData.backlight;
         }
         return mBacklight;
     }
@@ -1107,7 +1107,7 @@
      */
     public float getBacklightFromBrightness(float brightness) {
         if (mEvenDimmerBrightnessData != null) {
-            return mEvenDimmerBrightnessData.mBrightnessToBacklight.interpolate(brightness);
+            return mEvenDimmerBrightnessData.brightnessToBacklight.interpolate(brightness);
         }
         return mBrightnessToBacklightSpline.interpolate(brightness);
     }
@@ -1120,7 +1120,7 @@
      */
     public float getBrightnessFromBacklight(float backlight) {
         if (mEvenDimmerBrightnessData != null) {
-            return mEvenDimmerBrightnessData.mBacklightToBrightness.interpolate(backlight);
+            return mEvenDimmerBrightnessData.backlightToBrightness.interpolate(backlight);
         }
         return mBacklightToBrightnessSpline.interpolate(backlight);
     }
@@ -1131,7 +1131,7 @@
      */
     private Spline getBacklightToBrightnessSpline() {
         if (mEvenDimmerBrightnessData != null) {
-            return mEvenDimmerBrightnessData.mBacklightToBrightness;
+            return mEvenDimmerBrightnessData.backlightToBrightness;
         }
         return mBacklightToBrightnessSpline;
     }
@@ -1144,11 +1144,11 @@
      */
     public float getNitsFromBacklight(float backlight) {
         if (mEvenDimmerBrightnessData != null) {
-            if (mEvenDimmerBrightnessData.mBacklightToNits == null) {
+            if (mEvenDimmerBrightnessData.backlightToNits == null) {
                 return INVALID_NITS;
             }
             backlight = Math.max(backlight, mBacklightMinimum);
-            return mEvenDimmerBrightnessData.mBacklightToNits.interpolate(backlight);
+            return mEvenDimmerBrightnessData.backlightToNits.interpolate(backlight);
         }
 
         if (mBacklightToNitsSpline == null) {
@@ -1165,14 +1165,14 @@
      */
     public float getBacklightFromNits(float nits) {
         if (mEvenDimmerBrightnessData != null) {
-            return mEvenDimmerBrightnessData.mNitsToBacklight.interpolate(nits);
+            return mEvenDimmerBrightnessData.nitsToBacklight.interpolate(nits);
         }
         return mNitsToBacklightSpline.interpolate(nits);
     }
 
     private Spline getNitsToBacklightSpline() {
         if (mEvenDimmerBrightnessData != null) {
-            return mEvenDimmerBrightnessData.mNitsToBacklight;
+            return mEvenDimmerBrightnessData.nitsToBacklight;
         }
         return mNitsToBacklightSpline;
     }
@@ -1186,7 +1186,7 @@
         if (mEvenDimmerBrightnessData == null) {
             return INVALID_NITS;
         }
-        return mEvenDimmerBrightnessData.mMinLuxToNits.interpolate(lux);
+        return mEvenDimmerBrightnessData.minLuxToNits.interpolate(lux);
     }
 
     /**
@@ -1197,7 +1197,7 @@
         if (mEvenDimmerBrightnessData == null) {
             return PowerManager.BRIGHTNESS_MIN;
         }
-        return mEvenDimmerBrightnessData.mTransitionPoint;
+        return mEvenDimmerBrightnessData.transitionPoint;
     }
 
     /**
@@ -1268,7 +1268,7 @@
      */
     public float[] getBrightness() {
         if (mEvenDimmerBrightnessData != null) {
-            return mEvenDimmerBrightnessData.mBrightness;
+            return mEvenDimmerBrightnessData.brightness;
         }
         return mBrightness;
     }
@@ -2617,13 +2617,13 @@
                 List<NonNegativeFloatToFloatPoint> points = map.getMap().getPoint();
                 for (NonNegativeFloatToFloatPoint point : points) {
                     float lux = point.getFirst().floatValue();
-                    float maxBrightness = point.getSecond().floatValue();
-                    if (maxBrightness > hbmTransitionPoint) {
+                    float maxBacklight = point.getSecond().floatValue();
+                    if (maxBacklight > hbmTransitionPoint) {
                         Slog.wtf(TAG,
-                                "Invalid NBM config: maxBrightness is greater than hbm"
+                                "Invalid NBM config: maxBacklight is greater than hbm"
                                         + ".transitionPoint. type="
-                                        + type + "; lux=" + lux + "; maxBrightness="
-                                        + maxBrightness);
+                                        + type + "; lux=" + lux + "; maxBacklight="
+                                        + maxBacklight);
                         continue;
                     }
                     if (luxToTransitionPointMap.containsKey(lux)) {
@@ -2632,8 +2632,7 @@
                                         + lux);
                         continue;
                     }
-                    luxToTransitionPointMap.put(lux,
-                            getBrightnessFromBacklight(maxBrightness));
+                    luxToTransitionPointMap.put(lux, getBrightnessFromBacklight(maxBacklight));
                 }
                 if (!luxToTransitionPointMap.isEmpty()) {
                     mLuxThrottlingData.put(mappedType, luxToTransitionPointMap);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index c3faec0..d703898 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -22,6 +22,7 @@
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static com.android.server.display.brightness.BrightnessEvent.FLAG_EVEN_DIMMER;
 import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
 
 import android.animation.Animator;
@@ -1748,6 +1749,8 @@
         final float brightnessOnAvailableScale = MathUtils.constrainedMap(0.0f, 1.0f,
                 clampedState.getMinBrightness(), clampedMax,
                 brightnessState);
+        final boolean evenDimmerModeOn =
+                mCdsi != null && mCdsi.getReduceBrightColorsActivatedForEvenDimmer();
         mTempBrightnessEvent.setPercent(Math.round(
                 1000.0f * com.android.internal.display.BrightnessUtils.convertLinearToGamma(
                         brightnessOnAvailableScale) / 10)); // rounded to one dp
@@ -1762,7 +1765,8 @@
         mTempBrightnessEvent.setHbmMode(mBrightnessRangeController.getHighBrightnessMode());
         mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags()
                 | (mIsRbcActive ? BrightnessEvent.FLAG_RBC : 0)
-                | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0));
+                | (mPowerRequest.lowPowerMode ? BrightnessEvent.FLAG_LOW_POWER_MODE : 0)
+                | (evenDimmerModeOn ? FLAG_EVEN_DIMMER : 0));
         mTempBrightnessEvent.setRbcStrength(mCdsi != null
                 ? mCdsi.getReduceBrightColorsStrength() : -1);
         mTempBrightnessEvent.setPowerFactor(mPowerRequest.screenLowPowerBrightnessFactor);
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 0570b2a..5edea0a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -37,9 +37,9 @@
 import android.os.Trace;
 import android.util.DisplayUtils;
 import android.util.LongSparseArray;
-import android.util.MathUtils;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.Spline;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.DisplayCutout;
@@ -81,10 +81,6 @@
     private static final String UNIQUE_ID_PREFIX = "local:";
 
     private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.boot.emulator.circular";
-    // Min and max strengths for even dimmer feature.
-    private static final float EVEN_DIMMER_MIN_STRENGTH = 0.0f;
-    private static final float EVEN_DIMMER_MAX_STRENGTH = 90.0f;
-    private static final float BRIGHTNESS_MIN = 0.0f;
 
     private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
 
@@ -99,7 +95,9 @@
     private Context mOverlayContext;
 
     private int mEvenDimmerStrength = -1;
+    private boolean mEvenDimmerEnabled = false;
     private ColorDisplayService.ColorDisplayServiceInternal mCdsi;
+    private Spline mNitsToEvenDimmerStrength;
 
     // Called with SyncRoot lock held.
     LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, Context context,
@@ -279,7 +277,7 @@
             mIsFirstDisplay = isFirstDisplay;
             updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
-            mBacklightAdapter = new BacklightAdapter(displayToken, isFirstDisplay,
+            mBacklightAdapter = mInjector.getBacklightAdapter(displayToken, isFirstDisplay,
                     mSurfaceControlProxy);
             mActiveSfDisplayModeAtStartId = dynamicInfo.activeDisplayModeId;
         }
@@ -998,26 +996,36 @@
                     }
 
                     private void applyColorMatrixBasedDimming(float brightnessState) {
-                        int strength = (int) (MathUtils.constrainedMap(
-                                // to this range:
-                                EVEN_DIMMER_MAX_STRENGTH, EVEN_DIMMER_MIN_STRENGTH,
-                                // from this range:
-                                BRIGHTNESS_MIN, mDisplayDeviceConfig.getEvenDimmerTransitionPoint(),
-                                // map this (+ rounded up):
-                                brightnessState) + 0.5);
-
-                        if (mEvenDimmerStrength < 0 // uninitialised
-                                || MathUtils.abs(mEvenDimmerStrength - strength) > 1
-                                || strength <= 1) {
-                            mEvenDimmerStrength = strength;
-                        }
-                        boolean enabled = mEvenDimmerStrength > 0.0f;
-
                         if (mCdsi == null) {
                             mCdsi = LocalServices.getService(
                                     ColorDisplayService.ColorDisplayServiceInternal.class);
                         }
-                        if (mCdsi != null) {
+                        if (mCdsi == null) {
+                            return;
+                        }
+
+                        final float minHardwareNits = backlightToNits(brightnessToBacklight(
+                                mDisplayDeviceConfig.getEvenDimmerTransitionPoint()));
+                        final float requestedNits =
+                                backlightToNits(brightnessToBacklight(brightnessState));
+                        mNitsToEvenDimmerStrength =
+                                mCdsi.fetchEvenDimmerSpline(minHardwareNits);
+
+                        if (mNitsToEvenDimmerStrength == null) {
+                            return;
+                        }
+
+                        // Find required dimming strength, rounded up.
+                        int strength = Math.round(mNitsToEvenDimmerStrength
+                                .interpolate(requestedNits));
+                        boolean enabled = strength > 0.0f;
+                        if (mEvenDimmerEnabled != enabled) {
+                            Slog.i(TAG, "Setting Extra Dim; strength: " + strength
+                                    + ", " + (enabled ? "enabled" : "disabled"));
+                        }
+                        if (mEvenDimmerStrength != strength || mEvenDimmerEnabled != enabled) {
+                            mEvenDimmerEnabled = enabled;
+                            mEvenDimmerStrength = strength;
                             mCdsi.applyEvenDimmerColorChanges(enabled, strength);
                         }
                     }
@@ -1290,6 +1298,9 @@
             pw.println("DisplayDeviceConfig: ");
             pw.println("---------------------");
             pw.println(mDisplayDeviceConfig);
+            pw.println("mEvenDimmerEnabled=" + mEvenDimmerEnabled);
+            pw.println("mEvenDimmerStrength=" + mEvenDimmerStrength);
+            pw.println("mNitsToEvenDimmerStrength=" + mNitsToEvenDimmerStrength);
         }
 
         private int findSfDisplayModeIdLocked(int displayModeId, int modeGroup) {
@@ -1461,6 +1472,12 @@
                 long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
             return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay, flags);
         }
+
+        public BacklightAdapter getBacklightAdapter(IBinder displayToken, boolean isFirstDisplay,
+                SurfaceControlProxy surfaceControlProxy) {
+            return new BacklightAdapter(displayToken, isFirstDisplay, surfaceControlProxy);
+
+        }
     }
 
     public interface DisplayEventListener {
diff --git a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
index ad57ebf..9e9b899 100644
--- a/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
+++ b/services/core/java/com/android/server/display/brightness/BrightnessEvent.java
@@ -44,6 +44,7 @@
     public static final int FLAG_DOZE_SCALE = 0x4;
     public static final int FLAG_USER_SET = 0x8;
     public static final int FLAG_LOW_POWER_MODE = 0x20;
+    public static final int FLAG_EVEN_DIMMER = 0x40;
 
     private static final SimpleDateFormat FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
 
@@ -492,6 +493,7 @@
                 + ((mFlags & FLAG_RBC) != 0 ? "rbc " : "")
                 + ((mFlags & FLAG_INVALID_LUX) != 0 ? "invalid_lux " : "")
                 + ((mFlags & FLAG_DOZE_SCALE) != 0 ? "doze_scale " : "")
-                + ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "");
+                + ((mFlags & FLAG_LOW_POWER_MODE) != 0 ? "low_power_mode " : "")
+                + ((mFlags & FLAG_EVEN_DIMMER) != 0 ? "even_dimmer " : "");
     }
 }
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 bd759f3..dc59e66 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -70,6 +70,7 @@
 import android.util.MathUtils;
 import android.util.Slog;
 import android.util.SparseIntArray;
+import android.util.Spline;
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.accessibility.AccessibilityManager;
@@ -114,6 +115,8 @@
         Matrix.setIdentityM(MATRIX_IDENTITY, 0);
     }
 
+    private static final int EVEN_DIMMER_MAX_PERCENT_ALLOWED = 100;
+
     private static final int MSG_USER_CHANGED = 0;
     private static final int MSG_SET_UP = 1;
     private static final int MSG_APPLY_NIGHT_DISPLAY_IMMEDIATE = 2;
@@ -193,6 +196,9 @@
     private final boolean mVisibleBackgroundUsersEnabled;
     private final UserManagerService mUserManager;
 
+    private Spline mEvenDimmerSpline;
+    private boolean mEvenDimmerActivated;
+
     public ColorDisplayService(Context context) {
         super(context);
         mHandler = new TintHandler(DisplayThread.get().getLooper());
@@ -1625,6 +1631,16 @@
         }
 
         /**
+         * Gets the adjusted nits, given a strength and nits.
+         * @param strength of reduce bright colors
+         * @param nits target nits
+         * @return the actual nits that would be output, given input nits and rbc strength.
+         */
+        public float getAdjustedNitsForStrength(float nits, int strength) {
+            return mReduceBrightColorsTintController.getAdjustedNitsForStrength(nits, strength);
+        }
+
+        /**
          * Sets the listener and returns whether reduce bright colors is currently enabled.
          */
         public boolean setReduceBrightColorsListener(ReduceBrightColorsListener listener) {
@@ -1644,6 +1660,14 @@
         }
 
         /**
+         *
+         * @return whether reduce bright colors is on, due to even dimmer being activated
+         */
+        public boolean getReduceBrightColorsActivatedForEvenDimmer() {
+            return mEvenDimmerActivated;
+        }
+
+        /**
          * Gets the computed brightness, in nits, when the reduce bright colors feature is applied
          * at the current strength.
          *
@@ -1667,10 +1691,42 @@
          * Applies tint changes for even dimmer feature.
          */
         public void applyEvenDimmerColorChanges(boolean enabled, int strength) {
+            mEvenDimmerActivated = enabled;
             mReduceBrightColorsTintController.setActivated(enabled);
             mReduceBrightColorsTintController.setMatrix(strength);
             mHandler.sendEmptyMessage(MSG_APPLY_REDUCE_BRIGHT_COLORS);
         }
+
+        /**
+         * Get spline to map between requested nits, and required even dimmer strength.
+         * @return nits to strength spline
+         */
+        public Spline fetchEvenDimmerSpline(float nits) {
+            if (mEvenDimmerSpline == null) {
+                mEvenDimmerSpline = createNitsToStrengthSpline(nits);
+            }
+            return mEvenDimmerSpline;
+        }
+
+        /**
+         * Creates a spline mapping requested nits values, for each resulting strength value
+         * (in percent) for even dimmer.
+         * Returns null if coefficients are not initialised.
+         * @return spline from nits to strength
+         */
+        private Spline createNitsToStrengthSpline(float nits) {
+            final float[] requestedNits = new float[EVEN_DIMMER_MAX_PERCENT_ALLOWED + 1];
+            final float[] resultingStrength = new float[EVEN_DIMMER_MAX_PERCENT_ALLOWED + 1];
+            for (int i = 0; i <= EVEN_DIMMER_MAX_PERCENT_ALLOWED; i++) {
+                resultingStrength[EVEN_DIMMER_MAX_PERCENT_ALLOWED - i] = i;
+                requestedNits[EVEN_DIMMER_MAX_PERCENT_ALLOWED - i] =
+                        getAdjustedNitsForStrength(nits, i);
+                if (requestedNits[EVEN_DIMMER_MAX_PERCENT_ALLOWED - i] == 0) {
+                    return null;
+                }
+            }
+            return new Spline.LinearSpline(requestedNits, resultingStrength);
+        }
     }
 
     /**
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 f529c4c..4f6fbd0 100644
--- a/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
+++ b/services/core/java/com/android/server/display/color/ReduceBrightColorsTintController.java
@@ -123,6 +123,14 @@
         return computeComponentValue(mStrength) * nits;
     }
 
+    /**
+     * Returns the effective brightness (in nits), which has been adjusted to account for the effect
+     * of the bright color reduction.
+     */
+    public float getAdjustedNitsForStrength(float nits, int strength) {
+        return computeComponentValue(strength) * nits;
+    }
+
     private float computeComponentValue(int strengthLevel) {
         final float percentageStrength = strengthLevel / 100f;
         final float squaredPercentageStrength = percentageStrength * percentageStrength;
diff --git a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
index 7e2e10a..9a590a2 100644
--- a/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
+++ b/services/core/java/com/android/server/display/config/EvenDimmerBrightnessData.java
@@ -34,72 +34,76 @@
     /**
      * Brightness value at which even dimmer methods are used.
      */
-    public final float mTransitionPoint;
+    public final float transitionPoint;
 
     /**
      * Nits array, maps to mBacklight
      */
-    public final float[] mNits;
+    public final float[] nits;
 
     /**
      * Backlight array, maps to mBrightness and mNits
      */
-    public final float[] mBacklight;
+    public final float[] backlight;
 
     /**
      * Brightness array, maps to mBacklight
      */
-    public final float[] mBrightness;
+    public final float[] brightness;
+
     /**
      * Spline, mapping between backlight and nits
      */
-    public final Spline mBacklightToNits;
+    public final Spline backlightToNits;
+
     /**
      * Spline, mapping between nits and backlight
      */
-    public final Spline mNitsToBacklight;
+    public final Spline nitsToBacklight;
+
     /**
      * Spline, mapping between brightness and backlight
      */
-    public final Spline mBrightnessToBacklight;
+    public final Spline brightnessToBacklight;
+
     /**
      * Spline, mapping between backlight and brightness
      */
-    public final Spline mBacklightToBrightness;
+    public final Spline backlightToBrightness;
 
     /**
      * Spline, mapping the minimum nits for each lux condition.
      */
-    public final Spline mMinLuxToNits;
+    public final Spline minLuxToNits;
 
     @VisibleForTesting
     public EvenDimmerBrightnessData(float transitionPoint, float[] nits,
             float[] backlight, float[] brightness, Spline backlightToNits,
             Spline nitsToBacklight, Spline brightnessToBacklight, Spline backlightToBrightness,
             Spline minLuxToNits) {
-        mTransitionPoint = transitionPoint;
-        mNits = nits;
-        mBacklight = backlight;
-        mBrightness = brightness;
-        mBacklightToNits = backlightToNits;
-        mNitsToBacklight = nitsToBacklight;
-        mBrightnessToBacklight = brightnessToBacklight;
-        mBacklightToBrightness = backlightToBrightness;
-        mMinLuxToNits = minLuxToNits;
+        this.transitionPoint = transitionPoint;
+        this.nits = nits;
+        this.backlight = backlight;
+        this.brightness = brightness;
+        this.backlightToNits = backlightToNits;
+        this.nitsToBacklight = nitsToBacklight;
+        this.brightnessToBacklight = brightnessToBacklight;
+        this.backlightToBrightness = backlightToBrightness;
+        this.minLuxToNits = minLuxToNits;
     }
 
     @Override
     public String toString() {
         return "EvenDimmerBrightnessData {"
-                + "mTransitionPoint: " + mTransitionPoint
-                + ", mNits: " + Arrays.toString(mNits)
-                + ", mBacklight: " + Arrays.toString(mBacklight)
-                + ", mBrightness: " + Arrays.toString(mBrightness)
-                + ", mBacklightToNits: " + mBacklightToNits
-                + ", mNitsToBacklight: " + mNitsToBacklight
-                + ", mBrightnessToBacklight: " + mBrightnessToBacklight
-                + ", mBacklightToBrightness: " + mBacklightToBrightness
-                + ", mMinLuxToNits: " + mMinLuxToNits
+                + "transitionPoint: " + transitionPoint
+                + ", nits: " + Arrays.toString(nits)
+                + ", backlight: " + Arrays.toString(backlight)
+                + ", brightness: " + Arrays.toString(brightness)
+                + ", backlightToNits: " + backlightToNits
+                + ", nitsToBacklight: " + nitsToBacklight
+                + ", brightnessToBacklight: " + brightnessToBacklight
+                + ", backlightToBrightness: " + backlightToBrightness
+                + ", minLuxToNits: " + minLuxToNits
                 + "} ";
     }
 
@@ -122,7 +126,6 @@
         if (map == null) {
             return null;
         }
-        String interpolation = map.getInterpolation();
 
         List<BrightnessPoint> brightnessPoints = map.getBrightnessPoint();
         if (brightnessPoints.isEmpty()) {
@@ -169,22 +172,11 @@
             ++i;
         }
 
-        // Explicitly choose linear interpolation.
-        if ("linear".equals(interpolation)) {
-            return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
-                    new Spline.LinearSpline(backlight, nits),
-                    new Spline.LinearSpline(nits, backlight),
-                    new Spline.LinearSpline(brightness, backlight),
-                    new Spline.LinearSpline(backlight, brightness),
-                    new Spline.LinearSpline(minLux, minNits)
-            );
-        }
-
         return new EvenDimmerBrightnessData(transitionPoint, nits, backlight, brightness,
-                Spline.createSpline(backlight, nits),
-                Spline.createSpline(nits, backlight),
-                Spline.createSpline(brightness, backlight),
-                Spline.createSpline(backlight, brightness),
+                new Spline.LinearSpline(backlight, nits),
+                new Spline.LinearSpline(nits, backlight),
+                new Spline.LinearSpline(brightness, backlight),
+                new Spline.LinearSpline(backlight, brightness),
                 Spline.createSpline(minLux, minNits)
         );
     }
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index 0eafb59..a07facf 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -512,8 +512,6 @@
                 <xs:annotation name="final"/>
             </xs:element>
         </xs:sequence>
-        <!-- valid value of interpolation if specified: linear -->
-        <xs:attribute name="interpolation" type="xs:string" use="optional"/>
     </xs:complexType>
 
     <xs:complexType name="brightnessPoint">
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 355b0ab..5309263 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -91,8 +91,6 @@
   public class ComprehensiveBrightnessMap {
     ctor public ComprehensiveBrightnessMap();
     method @NonNull public final java.util.List<com.android.server.display.config.BrightnessPoint> getBrightnessPoint();
-    method public String getInterpolation();
-    method public void setInterpolation(String);
   }
 
   public class Density {
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index fd05b26..8e1be9a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -392,7 +392,7 @@
     public void testInvalidLuxThrottling() throws Exception {
         setupDisplayDeviceConfigFromDisplayConfigFile(
                 getContent(getInvalidLuxThrottling(), getValidProxSensor(),
-                        /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
+                        /* includeIdleMode= */ true, /* enableEvenDimmer= */ false));
 
         Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
                 mDisplayDeviceConfig.getLuxThrottlingData();
@@ -600,7 +600,7 @@
     public void testProximitySensorWithEmptyValuesFromDisplayConfig() throws IOException {
         setupDisplayDeviceConfigFromDisplayConfigFile(
                 getContent(getValidLuxThrottling(), getProxSensorWithEmptyValues(),
-                        /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
+                        /* includeIdleMode= */ true, /* enableEvenDimmer= */ false));
         assertNull(mDisplayDeviceConfig.getProximitySensor());
     }
 
@@ -608,7 +608,7 @@
     public void testProximitySensorWithRefreshRatesFromDisplayConfig() throws IOException {
         setupDisplayDeviceConfigFromDisplayConfigFile(
                 getContent(getValidLuxThrottling(), getValidProxSensorWithRefreshRateAndVsyncRate(),
-                        /* includeIdleMode= */ true, /* enableEvenDimmer */ false));
+                        /* includeIdleMode= */ true, /* enableEvenDimmer= */ false));
         assertEquals("test_proximity_sensor",
                 mDisplayDeviceConfig.getProximitySensor().type);
         assertEquals("Test Proximity Sensor",
@@ -803,7 +803,7 @@
     @Test
     public void testBrightnessRamps_IdleFallsBackToConfigInteractive() throws IOException {
         setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
-                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
 
         assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
         assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
@@ -820,14 +820,14 @@
     @Test
     public void testBrightnessCapForWearBedtimeMode() throws IOException {
         setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
-                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
         assertEquals(0.1f, mDisplayDeviceConfig.getBrightnessCapForWearBedtimeMode(), ZERO_DELTA);
     }
 
     @Test
     public void testAutoBrightnessBrighteningLevels() throws IOException {
         setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
-                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
 
         assertArrayEquals(new float[]{0.0f, 80},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
@@ -890,7 +890,7 @@
         when(mFlags.areAutoBrightnessModesEnabled()).thenReturn(false);
         setupDisplayDeviceConfigFromConfigResourceFile();
         setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
-                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ false));
+                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ false));
 
         assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
                         brightnessIntToFloat(150)},
@@ -929,7 +929,7 @@
         when(mFlags.isEvenDimmerEnabled()).thenReturn(true);
         when(mResources.getBoolean(R.bool.config_evenDimmerEnabled)).thenReturn(true);
         setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getValidLuxThrottling(),
-                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer */ true));
+                getValidProxSensor(), /* includeIdleMode= */ false, /* enableEvenDimmer= */ true));
 
         assertTrue(mDisplayDeviceConfig.isEvenDimmerAvailable());
         assertEquals(0.01f, mDisplayDeviceConfig.getBacklightFromBrightness(0.002f), ZERO_DELTA);
@@ -1365,7 +1365,7 @@
 
     private String getContent() {
         return getContent(getValidLuxThrottling(), getValidProxSensor(),
-                /* includeIdleMode= */ true, false);
+                /* includeIdleMode= */ true, /* enableEvenDimmer= */ false);
     }
 
     private String getContent(String brightnessCapConfig, String proxSensor,
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 120cc84..f5bed99 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -29,8 +29,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -45,6 +47,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.util.Spline;
 import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.SurfaceControl;
@@ -59,6 +62,7 @@
 import com.android.internal.R;
 import com.android.server.LocalServices;
 import com.android.server.display.LocalDisplayAdapter.BacklightAdapter;
+import com.android.server.display.color.ColorDisplayService;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.display.mode.DisplayModeDirector;
 import com.android.server.display.notifications.DisplayNotificationManager;
@@ -119,6 +123,8 @@
     private DisplayManagerFlags mFlags;
     @Mock
     private DisplayPowerController mMockedDisplayPowerController;
+    @Mock
+    private ColorDisplayService.ColorDisplayServiceInternal mMockedColorDisplayServiceInternal;
 
     private Handler mHandler;
 
@@ -133,6 +139,11 @@
     private Injector mInjector;
 
     @Mock
+    private DisplayDeviceConfig mMockDisplayDeviceConfig;
+    @Mock
+    private BacklightAdapter mMockBacklightAdapter;
+
+    @Mock
     private LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy;
     private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
     private static final int[] BACKLIGHT_RANGE = { 1, 255 };
@@ -150,6 +161,9 @@
         doReturn(mMockedResources).when(mMockedContext).getResources();
         LocalServices.removeServiceForTest(LightsManager.class);
         LocalServices.addService(LightsManager.class, mMockedLightsManager);
+        LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
+        LocalServices.addService(ColorDisplayService.ColorDisplayServiceInternal.class,
+                mMockedColorDisplayServiceInternal);
         mInjector = new Injector();
         when(mSurfaceControlProxy.getBootDisplayModeSupport()).thenReturn(true);
         mAdapter = new LocalDisplayAdapter(mMockedSyncRoot, mMockedContext, mHandler,
@@ -211,7 +225,15 @@
         when(mMockedResources.getIntArray(
                 com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
                 .thenReturn(new int[]{});
+
+        when(mMockedColorDisplayServiceInternal.fetchEvenDimmerSpline(3)).thenReturn(
+                new Spline.LinearSpline(
+                        new float[]{2f, 3.0f, 500f, 2000f},
+                        new float[]{100, 0, 0, 0}));
+        when(mMockDisplayDeviceConfig.isEvenDimmerAvailable()).thenReturn(true);
+
         doReturn(true).when(mFlags).isDisplayOffloadEnabled();
+        doReturn(true).when(mFlags).isEvenDimmerEnabled();
         initDisplayOffloadSession();
     }
 
@@ -222,6 +244,122 @@
         }
     }
 
+    @Test
+    public void testEvenDimmer() throws InterruptedException {
+        // Set up
+        FakeDisplay display = new FakeDisplay(PORT_A);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+        // brightness|backlight| nits | strength
+        //  0.5      |   0.45  | 600  |  0     // initial setup value
+        //  0.4      |   0.35  | 500  |  0     // normal range value
+        //  0.31     |   0.2   |   3  |  0     // transition point
+        //  0.16     |  0.125  | 2.5  |  50    // mid point of even dimmer
+        //  0.1      |   0.05  |   2  |  100   // bottom of even dimmer range
+        //  0.05     |   0.01  |   1  |  100+ // beyond strength=100 range (should still return 100)
+        when(mMockDisplayDeviceConfig.getEvenDimmerTransitionPoint()).thenReturn(0.31f);
+        when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.5f)).thenReturn(0.45f);
+        when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.4f)).thenReturn(0.35f);
+        when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.31f)).thenReturn(0.2f);
+        when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.16f)).thenReturn(0.125f);
+        when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.1f)).thenReturn(0.05f);
+        when(mMockDisplayDeviceConfig.getBacklightFromBrightness(0.05f)).thenReturn(0.01f);
+        when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.45f)).thenReturn(600f);
+        when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.35f)).thenReturn(500f);
+        when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.2f)).thenReturn(3f);
+        when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.125f)).thenReturn(2.5f);
+        when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.05f)).thenReturn(2f);
+        when(mMockDisplayDeviceConfig.getNitsFromBacklight(0.01f)).thenReturn(1f);
+
+        // initialise brightness to 0.5
+        Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON,
+                0.5f, 0.5f, null);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        verify(mSurfaceControlProxy).setDisplayPowerMode(any(), anyInt());
+        verify(mMockBacklightAdapter).setBacklight(anyFloat(), anyFloat(), anyFloat(), anyFloat());
+        verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(false), eq(0));
+        verify(mMockedColorDisplayServiceInternal).fetchEvenDimmerSpline(eq(3.0f));
+
+        // set up normal brightness range
+        changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.4f, 0.4f,
+                null);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // verify normal brightness range
+        verify(mMockBacklightAdapter).setBacklight(0.35f, 500f, 0.35f, 500f);
+        verify(mMockedColorDisplayServiceInternal,
+                times(1)) // no more, since the strength is the same
+                .applyEvenDimmerColorChanges(eq(false), eq(0));
+        verify(mMockedColorDisplayServiceInternal, times(2)).fetchEvenDimmerSpline(eq(3.0f));
+
+        // set up even dimmer edge range
+        changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.31f,
+                0.31f, null);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // verify even dimmer edge range
+        verify(mMockBacklightAdapter).setBacklight(0.2f, 3f, 0.2f, 3f);
+        // verify no more times, since the strength and enabled-ness is the same
+        verify(mMockedColorDisplayServiceInternal, times(1)).applyEvenDimmerColorChanges(eq(false),
+                eq(0));
+        verify(mMockedColorDisplayServiceInternal, times(3)).fetchEvenDimmerSpline(eq(3.0f));
+
+        // set up mid point of even dimmer range
+        changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.16f,
+                0.16f, null);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // verify within even dimmer range
+        verify(mMockBacklightAdapter).setBacklight(0.125f, 2.5f, 0.125f, 2.5f);
+        verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(50));
+        verify(mMockedColorDisplayServiceInternal, times(4)).fetchEvenDimmerSpline(eq(3.0f));
+
+        // set up within even dimmer range
+        changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.1f, 0.1f,
+                null);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // verify within even dimmer range
+        verify(mMockBacklightAdapter).setBacklight(0.05f, 2f, 0.05f, 2f);
+        verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(100));
+        verify(mMockedColorDisplayServiceInternal, times(5)).fetchEvenDimmerSpline(eq(3.0f));
+
+        // set up below even dimmer range
+        changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.05f,
+                0.05f, null);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // verify within even dimmer range
+        verify(mMockBacklightAdapter).setBacklight(0.01f, 1f, 0.01f, 1f);
+        // ensure no greater than 100 strength is returned, therefore not called again.
+        verify(mMockedColorDisplayServiceInternal).applyEvenDimmerColorChanges(eq(true), eq(100));
+        verify(mMockedColorDisplayServiceInternal, times(6)).fetchEvenDimmerSpline(eq(3.0f));
+
+        // set up return to normal range
+        changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0.4f, 0.4f,
+                null);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        changeStateRunnable.run();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+        // verify return to normal range
+        verify(mMockBacklightAdapter, times(2)).setBacklight(0.35f, 500f, 0.35f, 500f);
+        verify(mMockedColorDisplayServiceInternal, times(2)).applyEvenDimmerColorChanges(eq(false),
+                anyInt());
+        verify(mMockedColorDisplayServiceInternal, times(7)).fetchEvenDimmerSpline(eq(3.0f));
+    }
+
     /**
      * Confirm that display is marked as private when it is listed in
      * com.android.internal.R.array.config_localPrivateDisplayPorts.
@@ -1461,15 +1599,16 @@
             return mSurfaceControlProxy;
         }
 
-        // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay)
-        // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure
-        // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting
-        // consistent behaviour. Please also note that context passed to this method, is
-        // mMockContext and values will be loaded from mMockResources.
         @Override
         public DisplayDeviceConfig createDisplayDeviceConfig(Context context,
                 long physicalDisplayId, boolean isFirstDisplay, DisplayManagerFlags flags) {
-            return DisplayDeviceConfig.create(context, isFirstDisplay, flags);
+            return mMockDisplayDeviceConfig;
+        }
+
+        @Override
+        public BacklightAdapter getBacklightAdapter(IBinder displayToken, boolean isFirstDisplay,
+                LocalDisplayAdapter.SurfaceControlProxy surfaceControlProxy) {
+            return mMockBacklightAdapter;
         }
     }