diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index c5f7ea6501ff8..5a3720cbed615 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -988,6 +988,25 @@
- 1.089058
+
+ 6500
+
+
+
+ - 0
+
+
+
+
+ - 1
+
+
true
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b7df6a482983c..b620b70425fca 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3420,6 +3420,9 @@
+
+
+
diff --git a/services/core/java/com/android/server/display/color/CctEvaluator.java b/services/core/java/com/android/server/display/color/CctEvaluator.java
new file mode 100644
index 0000000000000..878f7e5116b87
--- /dev/null
+++ b/services/core/java/com/android/server/display/color/CctEvaluator.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 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 android.animation.TypeEvaluator;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+
+/**
+ * Interpolates between CCT values by a given step.
+ */
+class CctEvaluator implements TypeEvaluator {
+
+ private static final String TAG = "CctEvaluator";
+
+ /**
+ * The minimum input value, which will represent index 0 in the mValues array. Each
+ * subsequent input value is offset by this amount.
+ */
+ private final int mIndexOffset;
+ /**
+ * Cached step values at each CCT value (offset by the {@link #mIndexOffset} above). For
+ * example, if the minimum CCT is 2000K (which is set to mIndexOffset), then the 0th index of
+ * this array is equivalent to the step value at 2000K, 1st index corresponds to 2001K, and so
+ * on.
+ */
+ @VisibleForTesting
+ final int[] mStepsAtOffsetCcts;
+ /**
+ * Pre-computed stepped CCTs. These will be accessed frequently; the memory cost of caching them
+ * is well-spent.
+ */
+ @VisibleForTesting
+ final int[] mSteppedCctsAtOffsetCcts;
+
+ CctEvaluator(int min, int max, int[] cctRangeMinimums, int[] steps) {
+ final int delta = max - min + 1;
+ mStepsAtOffsetCcts = new int[delta];
+ mSteppedCctsAtOffsetCcts = new int[delta];
+ mIndexOffset = min;
+
+ final int parallelArraysLength = cctRangeMinimums.length;
+ if (cctRangeMinimums.length != steps.length) {
+ Slog.e(TAG,
+ "Parallel arrays cctRangeMinimums and steps are different lengths; setting "
+ + "step of 1");
+ setStepOfOne();
+ } else if (parallelArraysLength == 0) {
+ Slog.e(TAG, "No cctRangeMinimums or steps are set; setting step of 1");
+ setStepOfOne();
+ } else {
+ int parallelArraysIndex = 0;
+ int index = 0;
+ int lastSteppedCct = Integer.MIN_VALUE;
+ while (index < delta) {
+ final int cct = index + mIndexOffset;
+ int nextParallelArraysIndex = parallelArraysIndex + 1;
+ while (nextParallelArraysIndex < parallelArraysLength
+ && cct >= cctRangeMinimums[nextParallelArraysIndex]) {
+ parallelArraysIndex = nextParallelArraysIndex;
+ nextParallelArraysIndex++;
+ }
+ mStepsAtOffsetCcts[index] = steps[parallelArraysIndex];
+ if (lastSteppedCct == Integer.MIN_VALUE
+ || Math.abs(lastSteppedCct - cct) >= steps[parallelArraysIndex]) {
+ lastSteppedCct = cct;
+ }
+ mSteppedCctsAtOffsetCcts[index] = lastSteppedCct;
+ index++;
+ }
+ }
+ }
+
+ @Override
+ public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+ final int cct = (int) (startValue + fraction * (endValue - startValue));
+ final int index = cct - mIndexOffset;
+ if (index < 0 || index >= mSteppedCctsAtOffsetCcts.length) {
+ Slog.e(TAG, "steppedCctValueAt: returning same since invalid requested index=" + index);
+ return cct;
+ }
+ return mSteppedCctsAtOffsetCcts[index];
+ }
+
+ private void setStepOfOne() {
+ Arrays.fill(mStepsAtOffsetCcts, 1);
+ for (int i = 0; i < mSteppedCctsAtOffsetCcts.length; i++) {
+ mSteppedCctsAtOffsetCcts[i] = mIndexOffset + i;
+ }
+ }
+}
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 0284d9c68689d..c0ea5fead9a5a 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -180,6 +180,8 @@ public final class ColorDisplayService extends SystemService {
*/
private SparseIntArray mColorModeCompositionColorSpaces = null;
+ private final Object mCctTintApplierLock = new Object();
+
public ColorDisplayService(Context context) {
super(context);
mHandler = new TintHandler(DisplayThread.get().getLooper());
@@ -698,6 +700,79 @@ public final class ColorDisplayService extends SystemService {
}
}
+ private void applyTintByCct(ColorTemperatureTintController tintController, boolean immediate) {
+ synchronized (mCctTintApplierLock) {
+ tintController.cancelAnimator();
+
+ final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
+ final int from = tintController.getAppliedCct();
+ final int to = tintController.isActivated() ? tintController.getTargetCct()
+ : tintController.getDisabledCct();
+
+ if (immediate) {
+ Slog.d(TAG, tintController.getClass().getSimpleName()
+ + " applied immediately: toCct=" + to + " fromCct=" + from);
+ dtm.setColorMatrix(tintController.getLevel(),
+ tintController.computeMatrixForCct(to));
+ tintController.setAppliedCct(to);
+ } else {
+ Slog.d(TAG, tintController.getClass().getSimpleName() + " animation started: toCct="
+ + to + " fromCct=" + from);
+ ValueAnimator valueAnimator = ValueAnimator.ofInt(from, to);
+ tintController.setAnimator(valueAnimator);
+ final CctEvaluator evaluator = tintController.getEvaluator();
+ if (evaluator != null) {
+ valueAnimator.setEvaluator(evaluator);
+ }
+ valueAnimator.setDuration(tintController.getTransitionDurationMilliseconds());
+ valueAnimator.setInterpolator(AnimationUtils.loadInterpolator(
+ getContext(), android.R.interpolator.linear));
+ valueAnimator.addUpdateListener((ValueAnimator animator) -> {
+ synchronized (mCctTintApplierLock) {
+ final int value = (int) animator.getAnimatedValue();
+ if (value != tintController.getAppliedCct()) {
+ dtm.setColorMatrix(tintController.getLevel(),
+ tintController.computeMatrixForCct(value));
+ tintController.setAppliedCct(value);
+ }
+ }
+ });
+ valueAnimator.addListener(new AnimatorListenerAdapter() {
+
+ private boolean mIsCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ Slog.d(TAG, tintController.getClass().getSimpleName()
+ + " animation cancelled");
+ mIsCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ synchronized (mCctTintApplierLock) {
+ Slog.d(TAG, tintController.getClass().getSimpleName()
+ + " animation ended: wasCancelled=" + mIsCancelled
+ + " toCct=" + to
+ + " fromCct=" + from);
+ if (!mIsCancelled) {
+ // Ensure final color matrix is set at the end of the animation.
+ // If the animation is cancelled then don't set the final color
+ // matrix so the new animator can pick up from where this one left
+ // off.
+ dtm.setColorMatrix(tintController.getLevel(),
+ tintController.computeMatrixForCct(to));
+ tintController.setAppliedCct(to);
+ }
+ tintController.setAnimator(null);
+ }
+ }
+ });
+ valueAnimator.start();
+ }
+ }
+ }
+
/**
* Returns the first date time corresponding to the local time that occurs before the provided
* date time.
@@ -747,7 +822,7 @@ public final class ColorDisplayService extends SystemService {
// If disabled, clear the tint. If enabled, do nothing more here and let the next
// temperature update set the correct tint.
- if (!activated) {
+ if (oldActivated && !activated) {
mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
}
}
@@ -1452,7 +1527,7 @@ public final class ColorDisplayService extends SystemService {
public class ColorDisplayServiceInternal {
/** Sets whether DWB should be allowed in the current state. */
- public void setDisplayWhiteBalanceAllowed(boolean allowed) {
+ public void setDisplayWhiteBalanceAllowed(boolean allowed) {
mDisplayWhiteBalanceTintController.setAllowed(allowed);
updateDisplayWhiteBalanceStatus();
}
@@ -1464,8 +1539,8 @@ public final class ColorDisplayService extends SystemService {
* @param cct the color temperature in Kelvin.
*/
public boolean setDisplayWhiteBalanceColorTemperature(int cct) {
- // Update the transform matrix even if it can't be applied.
- mDisplayWhiteBalanceTintController.setMatrix(cct);
+ // Update the transform target CCT even if it can't be applied.
+ mDisplayWhiteBalanceTintController.setTargetCct(cct);
if (mDisplayWhiteBalanceTintController.isActivated()) {
mHandler.sendEmptyMessage(MSG_APPLY_DISPLAY_WHITE_BALANCE);
@@ -1601,7 +1676,7 @@ public final class ColorDisplayService extends SystemService {
applyTint(mNightDisplayTintController, false);
break;
case MSG_APPLY_DISPLAY_WHITE_BALANCE:
- applyTint(mDisplayWhiteBalanceTintController, false);
+ applyTintByCct(mDisplayWhiteBalanceTintController, false);
break;
}
}
diff --git a/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java b/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java
new file mode 100644
index 0000000000000..0fbd9d409986b
--- /dev/null
+++ b/services/core/java/com/android/server/display/color/ColorTemperatureTintController.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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;
+
+abstract class ColorTemperatureTintController extends TintController {
+
+ abstract int getAppliedCct();
+
+ abstract void setAppliedCct(int cct);
+
+ abstract int getTargetCct();
+
+ abstract void setTargetCct(int cct);
+
+ /**
+ * Returns the CCT value most closely associated with the "disabled" (identity) matrix for
+ * this device, to use as the target when deactivating this transform.
+ */
+ abstract int getDisabledCct();
+
+ abstract float[] computeMatrixForCct(int cct);
+
+ abstract CctEvaluator getEvaluator();
+}
diff --git a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
index 106ac0c0d4788..bf0139f347f8c 100644
--- a/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
+++ b/services/core/java/com/android/server/display/color/DisplayWhiteBalanceTintController.java
@@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.display.color.DisplayTransformManager.LEVEL_COLOR_MATRIX_DISPLAY_WHITE_BALANCE;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.Size;
import android.content.Context;
import android.content.res.Resources;
@@ -36,7 +37,7 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
-final class DisplayWhiteBalanceTintController extends TintController {
+final class DisplayWhiteBalanceTintController extends ColorTemperatureTintController {
// Three chromaticity coordinates per color: X, Y, and Z
private static final int NUM_VALUES_PER_PRIMARY = 3;
@@ -52,9 +53,11 @@ final class DisplayWhiteBalanceTintController extends TintController {
private int mTemperatureDefault;
@VisibleForTesting
float[] mDisplayNominalWhiteXYZ = new float[NUM_VALUES_PER_PRIMARY];
+ private int mDisplayNominalWhiteCct;
@VisibleForTesting
ColorSpace.Rgb mDisplayColorSpaceRGB;
private float[] mChromaticAdaptationMatrix;
+ // The temperature currently represented in the matrix.
@VisibleForTesting
int mCurrentColorTemperature;
private float[] mCurrentColorTemperatureXYZ;
@@ -65,6 +68,9 @@ final class DisplayWhiteBalanceTintController extends TintController {
private Boolean mIsAvailable;
// This feature becomes disallowed if the device is in an unsupported strong/light state.
private boolean mIsAllowed = true;
+ private int mTargetCct;
+ private int mAppliedCct;
+ private CctEvaluator mCctEvaluator;
private final DisplayManagerInternal mDisplayManagerInternal;
@@ -108,6 +114,9 @@ final class DisplayWhiteBalanceTintController extends TintController {
displayNominalWhiteXYZ[i] = Float.parseFloat(nominalWhiteValues[i]);
}
+ final int displayNominalWhiteCct = res.getInteger(
+ R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+
final int colorTemperatureMin = res.getInteger(
R.integer.config_displayWhiteBalanceColorTemperatureMin);
if (colorTemperatureMin <= 0) {
@@ -124,19 +133,28 @@ final class DisplayWhiteBalanceTintController extends TintController {
return;
}
- final int colorTemperature = res.getInteger(
+ final int defaultTemperature = res.getInteger(
R.integer.config_displayWhiteBalanceColorTemperatureDefault);
mTransitionDuration = res.getInteger(
R.integer.config_displayWhiteBalanceTransitionTime);
+ int[] cctRangeMinimums = res.getIntArray(
+ R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+ int[] steps = res.getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+
synchronized (mLock) {
mDisplayColorSpaceRGB = displayColorSpaceRGB;
mDisplayNominalWhiteXYZ = displayNominalWhiteXYZ;
+ mDisplayNominalWhiteCct = displayNominalWhiteCct;
+ mTargetCct = mDisplayNominalWhiteCct;
+ mAppliedCct = mDisplayNominalWhiteCct;
mTemperatureMin = colorTemperatureMin;
mTemperatureMax = colorTemperatureMax;
- mTemperatureDefault = colorTemperature;
+ mTemperatureDefault = defaultTemperature;
mSetUp = true;
+ mCctEvaluator = new CctEvaluator(mTemperatureMin, mTemperatureMax,
+ cctRangeMinimums, steps);
}
setMatrix(mTemperatureDefault);
@@ -144,8 +162,16 @@ final class DisplayWhiteBalanceTintController extends TintController {
@Override
public float[] getMatrix() {
- return mSetUp && isActivated() ? mMatrixDisplayWhiteBalance
- : ColorDisplayService.MATRIX_IDENTITY;
+ if (!mSetUp || !isActivated()) {
+ return ColorDisplayService.MATRIX_IDENTITY;
+ }
+ computeMatrixForCct(mAppliedCct);
+ return mMatrixDisplayWhiteBalance;
+ }
+
+ @Override
+ public int getTargetCct() {
+ return mTargetCct;
}
/**
@@ -174,6 +200,12 @@ final class DisplayWhiteBalanceTintController extends TintController {
@Override
public void setMatrix(int cct) {
+ setTargetCct(cct);
+ computeMatrixForCct(mTargetCct);
+ }
+
+ @Override
+ public void setTargetCct(int cct) {
if (!mSetUp) {
Slog.w(ColorDisplayService.TAG,
"Can't set display white balance temperature: uninitialized");
@@ -183,50 +215,93 @@ final class DisplayWhiteBalanceTintController extends TintController {
if (cct < mTemperatureMin) {
Slog.w(ColorDisplayService.TAG,
"Requested display color temperature is below allowed minimum");
- cct = mTemperatureMin;
+ mTargetCct = mTemperatureMin;
} else if (cct > mTemperatureMax) {
Slog.w(ColorDisplayService.TAG,
"Requested display color temperature is above allowed maximum");
- cct = mTemperatureMax;
+ mTargetCct = mTemperatureMax;
+ } else {
+ mTargetCct = cct;
+ }
+ }
+
+ @Override
+ public int getDisabledCct() {
+ return mDisplayNominalWhiteCct;
+ }
+
+ @Override
+ public float[] computeMatrixForCct(int cct) {
+ if (!mSetUp || cct == 0) {
+ Slog.w(ColorDisplayService.TAG, "Couldn't compute matrix for cct=" + cct);
+ return ColorDisplayService.MATRIX_IDENTITY;
}
synchronized (mLock) {
mCurrentColorTemperature = cct;
- // Adapt the display's nominal white point to match the requested CCT value
- mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
-
- mChromaticAdaptationMatrix =
- ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
- mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
-
- // Convert the adaptation matrix to RGB space
- float[] result = mul3x3(mChromaticAdaptationMatrix,
- mDisplayColorSpaceRGB.getTransform());
- result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
-
- // Normalize the transform matrix to peak white value in RGB space
- final float adaptedMaxR = result[0] + result[3] + result[6];
- final float adaptedMaxG = result[1] + result[4] + result[7];
- final float adaptedMaxB = result[2] + result[5] + result[8];
- final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
-
- Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
- for (int i = 0; i < result.length; i++) {
- result[i] /= denum;
- if (!isColorMatrixCoeffValid(result[i])) {
- Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
- return;
- }
+ if (cct == mDisplayNominalWhiteCct && !isActivated()) {
+ // DWB is finished turning off. Clear the matrix.
+ Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
+ } else {
+ computeMatrixForCctLocked(cct);
}
- java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
- java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
- java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
+ Slog.d(ColorDisplayService.TAG, "computeDisplayWhiteBalanceMatrix: cct =" + cct
+ + " matrix =" + matrixToString(mMatrixDisplayWhiteBalance, 16));
+
+ return mMatrixDisplayWhiteBalance;
+ }
+ }
+
+ private void computeMatrixForCctLocked(int cct) {
+ // Adapt the display's nominal white point to match the requested CCT value
+ mCurrentColorTemperatureXYZ = ColorSpace.cctToXyz(cct);
+
+ mChromaticAdaptationMatrix =
+ ColorSpace.chromaticAdaptation(ColorSpace.Adaptation.BRADFORD,
+ mDisplayNominalWhiteXYZ, mCurrentColorTemperatureXYZ);
+
+ // Convert the adaptation matrix to RGB space
+ float[] result = mul3x3(mChromaticAdaptationMatrix,
+ mDisplayColorSpaceRGB.getTransform());
+ result = mul3x3(mDisplayColorSpaceRGB.getInverseTransform(), result);
+
+ // Normalize the transform matrix to peak white value in RGB space
+ final float adaptedMaxR = result[0] + result[3] + result[6];
+ final float adaptedMaxG = result[1] + result[4] + result[7];
+ final float adaptedMaxB = result[2] + result[5] + result[8];
+ final float denum = Math.max(Math.max(adaptedMaxR, adaptedMaxG), adaptedMaxB);
+
+ Matrix.setIdentityM(mMatrixDisplayWhiteBalance, 0);
+
+ for (int i = 0; i < result.length; i++) {
+ result[i] /= denum;
+ if (!isColorMatrixCoeffValid(result[i])) {
+ Slog.e(ColorDisplayService.TAG, "Invalid DWB color matrix");
+ return;
+ }
}
- Slog.d(ColorDisplayService.TAG, "setDisplayWhiteBalanceTemperatureMatrix: cct = " + cct
- + " matrix = " + matrixToString(mMatrixDisplayWhiteBalance, 16));
+ java.lang.System.arraycopy(result, 0, mMatrixDisplayWhiteBalance, 0, 3);
+ java.lang.System.arraycopy(result, 3, mMatrixDisplayWhiteBalance, 4, 3);
+ java.lang.System.arraycopy(result, 6, mMatrixDisplayWhiteBalance, 8, 3);
+ }
+
+ @Override
+ int getAppliedCct() {
+ return mAppliedCct;
+ }
+
+ @Override
+ void setAppliedCct(int cct) {
+ mAppliedCct = cct;
+ }
+
+ @Override
+ @Nullable
+ CctEvaluator getEvaluator() {
+ return mCctEvaluator;
}
@Override
@@ -258,7 +333,10 @@ final class DisplayWhiteBalanceTintController extends TintController {
pw.println(" mTemperatureMin = " + mTemperatureMin);
pw.println(" mTemperatureMax = " + mTemperatureMax);
pw.println(" mTemperatureDefault = " + mTemperatureDefault);
+ pw.println(" mDisplayNominalWhiteCct = " + mDisplayNominalWhiteCct);
pw.println(" mCurrentColorTemperature = " + mCurrentColorTemperature);
+ pw.println(" mTargetCct = " + mTargetCct);
+ pw.println(" mAppliedCct = " + mAppliedCct);
pw.println(" mCurrentColorTemperatureXYZ = "
+ matrixToString(mCurrentColorTemperatureXYZ, 3));
pw.println(" mDisplayColorSpaceRGB RGB-to-XYZ = "
@@ -340,11 +418,7 @@ final class DisplayWhiteBalanceTintController extends TintController {
}
private boolean isColorMatrixCoeffValid(float coeff) {
- if (Float.isNaN(coeff) || Float.isInfinite(coeff)) {
- return false;
- }
-
- return true;
+ return !Float.isNaN(coeff) && !Float.isInfinite(coeff);
}
private boolean isColorMatrixValid(float[] matrix) {
@@ -352,8 +426,8 @@ final class DisplayWhiteBalanceTintController extends TintController {
return false;
}
- for (int i = 0; i < matrix.length; i++) {
- if (!isColorMatrixCoeffValid(matrix[i])) {
+ for (float value : matrix) {
+ if (!isColorMatrixCoeffValid(value)) {
return false;
}
}
diff --git a/services/core/java/com/android/server/display/color/TintController.java b/services/core/java/com/android/server/display/color/TintController.java
index c53ac06c66e13..384333a574b5a 100644
--- a/services/core/java/com/android/server/display/color/TintController.java
+++ b/services/core/java/com/android/server/display/color/TintController.java
@@ -16,6 +16,7 @@
package com.android.server.display.color;
+import android.animation.ValueAnimator;
import android.content.Context;
import android.util.Slog;
@@ -28,14 +29,14 @@ abstract class TintController {
*/
private static final long TRANSITION_DURATION = 3000L;
- private ColorDisplayService.TintValueAnimator mAnimator;
+ private ValueAnimator mAnimator;
private Boolean mIsActivated;
- public ColorDisplayService.TintValueAnimator getAnimator() {
+ public ValueAnimator getAnimator() {
return mAnimator;
}
- public void setAnimator(ColorDisplayService.TintValueAnimator animator) {
+ public void setAnimator(ValueAnimator animator) {
mAnimator = animator;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
index 941a3a419d597..3faf394fc5349 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java
@@ -72,18 +72,28 @@ public class DisplayWhiteBalanceTintControllerTest {
mResources = InstrumentationRegistry.getContext().getResources();
// These Resources are common to all tests.
- doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin))
+ doReturn(4000)
.when(mMockedResources)
.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMin);
- doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax))
+ doReturn(8000)
.when(mMockedResources)
.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureMax);
- doReturn(mResources.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault))
+ doReturn(6500)
.when(mMockedResources)
.getInteger(R.integer.config_displayWhiteBalanceColorTemperatureDefault);
- doReturn(mResources.getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite))
- .when(mMockedResources)
- .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+ doReturn(new String[] {"0.950456", "1.000000", "1.089058"})
+ .when(mMockedResources)
+ .getStringArray(R.array.config_displayWhiteBalanceDisplayNominalWhite);
+ doReturn(6500)
+ .when(mMockedResources)
+ .getInteger(R.integer.config_displayWhiteBalanceDisplayNominalWhiteCct);
+ doReturn(new int[] {0})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplaySteps);
+ doReturn(new int[] {20})
+ .when(mMockedResources)
+ .getIntArray(R.array.config_displayWhiteBalanceDisplayRangeMinimums);
+
doReturn(mMockedResources).when(mMockedContext).getResources();
mDisplayToken = new Binder();
@@ -195,7 +205,7 @@ public class DisplayWhiteBalanceTintControllerTest {
* Matrix should match the precalculated one for given cct and display primaries.
*/
@Test
- public void displayWhiteBalance_validateTransformMatrix() {
+ public void displayWhiteBalance_getAndSetMatrix_validateTransformMatrix() {
DisplayPrimaries displayPrimaries = new DisplayPrimaries();
displayPrimaries.red = new CieXyz();
displayPrimaries.red.X = 0.412315f;
@@ -223,10 +233,12 @@ public class DisplayWhiteBalanceTintControllerTest {
final int cct = 6500;
mDisplayWhiteBalanceTintController.setMatrix(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(
+ mDisplayWhiteBalanceTintController.getTargetCct());
+
assertWithMessage("Failed to set temperature")
.that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
.isEqualTo(cct);
-
float[] matrixDwb = mDisplayWhiteBalanceTintController.getMatrix();
final float[] expectedMatrixDwb = {
0.971848f, -0.001421f, 0.000491f, 0.0f,
@@ -238,6 +250,54 @@ public class DisplayWhiteBalanceTintControllerTest {
1e-6f /* tolerance */);
}
+ /**
+ * Matrix should match the precalculated one for given cct and display primaries.
+ */
+ @Test
+ public void displayWhiteBalance_targetApplied_validateTransformMatrix() {
+ DisplayPrimaries displayPrimaries = new DisplayPrimaries();
+ displayPrimaries.red = new CieXyz();
+ displayPrimaries.red.X = 0.412315f;
+ displayPrimaries.red.Y = 0.212600f;
+ displayPrimaries.red.Z = 0.019327f;
+ displayPrimaries.green = new CieXyz();
+ displayPrimaries.green.X = 0.357600f;
+ displayPrimaries.green.Y = 0.715200f;
+ displayPrimaries.green.Z = 0.119200f;
+ displayPrimaries.blue = new CieXyz();
+ displayPrimaries.blue.X = 0.180500f;
+ displayPrimaries.blue.Y = 0.072200f;
+ displayPrimaries.blue.Z = 0.950633f;
+ displayPrimaries.white = new CieXyz();
+ displayPrimaries.white.X = 0.950456f;
+ displayPrimaries.white.Y = 1.000000f;
+ displayPrimaries.white.Z = 1.089058f;
+ when(mDisplayManagerInternal.getDisplayNativePrimaries(DEFAULT_DISPLAY))
+ .thenReturn(displayPrimaries);
+
+ setUpTintController();
+ assertWithMessage("Setup with valid SurfaceControl failed")
+ .that(mDisplayWhiteBalanceTintController.mSetUp)
+ .isTrue();
+
+ final int cct = 6500;
+ mDisplayWhiteBalanceTintController.setTargetCct(cct);
+ final float[] matrixDwb = mDisplayWhiteBalanceTintController.computeMatrixForCct(cct);
+ mDisplayWhiteBalanceTintController.setAppliedCct(cct);
+
+ assertWithMessage("Failed to set temperature")
+ .that(mDisplayWhiteBalanceTintController.mCurrentColorTemperature)
+ .isEqualTo(cct);
+ final float[] expectedMatrixDwb = {
+ 0.971848f, -0.001421f, 0.000491f, 0.0f,
+ 0.028193f, 0.945798f, 0.003207f, 0.0f,
+ -0.000042f, -0.000989f, 0.988659f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f
+ };
+ assertArrayEquals("Unexpected DWB matrix", expectedMatrixDwb, matrixDwb,
+ 1e-6f /* tolerance */);
+ }
+
private void setUpTintController() {
mDisplayWhiteBalanceTintController = new DisplayWhiteBalanceTintController(
mDisplayManagerInternal);
diff --git a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
new file mode 100644
index 0000000000000..b96666ae40a36
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 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 androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CctEvaluatorTest {
+
+ @Test
+ public void noEntriesInParallelArrays_setsEverythingToOne() {
+ final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{}, new int[]{});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{1, 1, 1, 1, 1, 1});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{0, 1, 2, 3, 4, 5});
+ }
+
+ @Test
+ public void unevenNumberOfEntriesInParallelArrays_setsEverythingToOne() {
+ final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{0}, new int[]{});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{1, 1, 1, 1, 1, 1});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{0, 1, 2, 3, 4, 5});
+ }
+
+ @Test
+ public void singleEntryInParallelArray_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(0, 5, new int[]{0}, new int[]{2});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{2, 2, 2, 2, 2, 2});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{0, 0, 2, 2, 4, 4});
+ }
+
+ @Test
+ public void minimumIsBelowFirstRange_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(3000, 3005, new int[]{3002},
+ new int[]{20});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(new int[]{20, 20, 20, 20, 20, 20});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{3000, 3000, 3000, 3000, 3000, 3000});
+ }
+
+ @Test
+ public void minimumIsAboveFirstRange_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(3000, 3008, new int[]{3002},
+ new int[]{20});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+ new int[]{20, 20, 20, 20, 20, 20, 20, 20, 20});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000, 3000});
+ }
+
+ @Test
+ public void multipleStepsStartsAtThreshold_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(5, 20, new int[]{0, 4, 5, 10, 18},
+ new int[]{11, 7, 2, 15, 9});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+ new int[]{2, 2, 2, 2, 2, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{5, 5, 7, 7, 9, 9, 9, 9, 9, 9, 9, 9, 9, 18, 18, 18});
+ }
+
+ @Test
+ public void multipleStepsStartsInBetween_computesCorrectly() {
+ final CctEvaluator evaluator = new CctEvaluator(4, 20, new int[]{0, 5, 10, 18},
+ new int[]{14, 2, 15, 9});
+ assertThat(evaluator.mStepsAtOffsetCcts).isEqualTo(
+ new int[]{14, 2, 2, 2, 2, 2, 15, 15, 15, 15, 15, 15, 15, 15, 9, 9, 9});
+ assertThat(evaluator.mSteppedCctsAtOffsetCcts).isEqualTo(
+ new int[]{4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 18, 18, 18});
+ }
+}