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}); + } +}