1 /*
2  * Copyright 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.display;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.ArgumentMatchers.anyFloat;
25 import static org.mockito.ArgumentMatchers.anyInt;
26 import static org.mockito.Mockito.mock;
27 import static org.mockito.Mockito.when;
28 
29 import android.content.res.Resources;
30 import android.content.res.TypedArray;
31 import android.hardware.display.BrightnessConfiguration;
32 import android.os.PowerManager;
33 import android.util.MathUtils;
34 import android.util.Spline;
35 
36 import androidx.test.filters.SmallTest;
37 import androidx.test.runner.AndroidJUnit4;
38 
39 import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
40 
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 import org.mockito.Mock;
44 
45 import java.util.Arrays;
46 
47 @SmallTest
48 @RunWith(AndroidJUnit4.class)
49 public class BrightnessMappingStrategyTest {
50 
51     private static final float[] LUX_LEVELS = {
52         0,
53         5,
54         20,
55         40,
56         100,
57         325,
58         600,
59         1250,
60         2200,
61         4000,
62         5000
63     };
64 
65     private static final int[] LUX_LEVELS_IDLE = {
66         0,
67         10,
68         40,
69         80,
70         200,
71         655,
72         1200,
73         2500,
74         4400,
75         8000,
76         10000
77     };
78 
79     private static final float[] DISPLAY_LEVELS_NITS = {
80         13.25f,
81         54.0f,
82         78.85f,
83         105.02f,
84         132.7f,
85         170.12f,
86         212.1f,
87         265.2f,
88         335.8f,
89         415.2f,
90         478.5f,
91     };
92 
93     private static final float[] DISPLAY_LEVELS_NITS_IDLE = {
94         23.25f,
95         64.0f,
96         88.85f,
97         115.02f,
98         142.7f,
99         180.12f,
100         222.1f,
101         275.2f,
102         345.8f,
103         425.2f,
104         468.5f,
105     };
106 
107     private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
108         9,
109         30,
110         45,
111         62,
112         78,
113         96,
114         119,
115         146,
116         178,
117         221,
118         255
119     };
120 
121     private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
122     private static final float[] BACKLIGHT_RANGE_ZERO_TO_ONE = { 0.0f, 1.0f };
123     private static final float[] DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT = { 0.03149606299f, 1.0f };
124 
125     private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
126     private static final int[] EMPTY_INT_ARRAY = new int[0];
127 
128     private static final float MAXIMUM_GAMMA = 3.0f;
129 
130     private static final float[] GAMMA_CORRECTION_LUX = {
131         0,
132         100,
133         1000,
134         2500,
135         4000,
136         4900,
137         5000
138     };
139     private static final float[] GAMMA_CORRECTION_NITS = {
140         1.0f,
141         10.55f,
142         96.5f,
143         239.75f,
144         383.0f,
145         468.95f,
146         478.5f,
147     };
148     private static final Spline GAMMA_CORRECTION_SPLINE = Spline.createSpline(
149             new float[] { 0.0f, 100.0f, 1000.0f, 2500.0f, 4000.0f, 4900.0f, 5000.0f },
150             new float[] { 0.0475f, 0.0475f, 0.2225f, 0.5140f, 0.8056f, 0.9805f, 1.0f });
151 
152     private static final float TOLERANCE = 0.0001f;
153 
154     @Mock
155     DisplayWhiteBalanceController mMockDwbc;
156 
157     @Test
testSimpleStrategyMappingAtControlPoints()158     public void testSimpleStrategyMappingAtControlPoints() {
159         Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
160         DisplayDeviceConfig ddc = createDdc();
161         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
162         assertNotNull("BrightnessMappingStrategy should not be null", simple);
163         for (int i = 0; i < LUX_LEVELS.length; i++) {
164             final float expectedLevel = MathUtils.map(PowerManager.BRIGHTNESS_OFF + 1,
165                     PowerManager.BRIGHTNESS_ON, PowerManager.BRIGHTNESS_MIN,
166                     PowerManager.BRIGHTNESS_MAX, DISPLAY_LEVELS_BACKLIGHT[i]);
167             assertEquals(expectedLevel,
168                     simple.getBrightness(LUX_LEVELS[i]), 0.0001f /*tolerance*/);
169         }
170     }
171 
172     @Test
testSimpleStrategyMappingBetweenControlPoints()173     public void testSimpleStrategyMappingBetweenControlPoints() {
174         Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
175         DisplayDeviceConfig ddc = createDdc();
176         BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
177         assertNotNull("BrightnessMappingStrategy should not be null", simple);
178         for (int i = 1; i < LUX_LEVELS.length; i++) {
179             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
180             final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
181             assertTrue("Desired brightness should be between adjacent control points.",
182                     backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1]
183                             && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
184         }
185     }
186 
187     @Test
testSimpleStrategyIgnoresNewConfiguration()188     public void testSimpleStrategyIgnoresNewConfiguration() {
189         Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
190         DisplayDeviceConfig ddc = createDdc();
191         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
192 
193         final float[] lux = { 0f, 1f };
194         final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
195 
196         BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
197                 .build();
198         strategy.setBrightnessConfiguration(config);
199         assertNotEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
200     }
201 
202     @Test
testSimpleStrategyIgnoresNullConfiguration()203     public void testSimpleStrategyIgnoresNullConfiguration() {
204         Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
205         DisplayDeviceConfig ddc = createDdc();
206         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
207 
208         strategy.setBrightnessConfiguration(null);
209         final int n = DISPLAY_LEVELS_BACKLIGHT.length;
210         final float expectedBrightness =
211                 (float) DISPLAY_LEVELS_BACKLIGHT[n - 1] / PowerManager.BRIGHTNESS_ON;
212         assertEquals(expectedBrightness,
213                 strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
214     }
215 
216     @Test
testPhysicalStrategyMappingAtControlPoints()217     public void testPhysicalStrategyMappingAtControlPoints() {
218         Resources res = createResources(EMPTY_INT_ARRAY);
219         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
220                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT,
221                 LUX_LEVELS, DISPLAY_LEVELS_NITS);
222         BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
223         assertNotNull("BrightnessMappingStrategy should not be null", physical);
224         for (int i = 0; i < LUX_LEVELS.length; i++) {
225             final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
226                     DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[0],
227                     DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT[1],
228                     DISPLAY_LEVELS_NITS[i]);
229             assertEquals(expectedLevel,
230                     physical.getBrightness(LUX_LEVELS[i]),
231                     0.0001f /*tolerance*/);
232         }
233     }
234 
235     @Test
testPhysicalStrategyMappingBetweenControlPoints()236     public void testPhysicalStrategyMappingBetweenControlPoints() {
237         Resources res = createResources(EMPTY_INT_ARRAY);
238         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
239                 LUX_LEVELS, DISPLAY_LEVELS_NITS);
240         BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
241         assertNotNull("BrightnessMappingStrategy should not be null", physical);
242         Spline brightnessToNits =
243                 Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
244         for (int i = 1; i < LUX_LEVELS.length; i++) {
245             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2.0f;
246             final float brightness = physical.getBrightness(lux);
247             final float nits = brightnessToNits.interpolate(brightness);
248             assertTrue("Desired brightness should be between adjacent control points: " + nits,
249                     nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]);
250         }
251     }
252 
253     @Test
testPhysicalStrategyUsesNewConfigurations()254     public void testPhysicalStrategyUsesNewConfigurations() {
255         Resources res = createResources(EMPTY_INT_ARRAY);
256         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
257                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
258         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
259 
260         final float[] lux = {0f, 1f};
261         final float[] nits = {
262                 DISPLAY_RANGE_NITS[0],
263                 DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1]
264         };
265 
266         BrightnessConfiguration config = new BrightnessConfiguration.Builder(lux, nits)
267                 .build();
268         strategy.setBrightnessConfiguration(config);
269         assertEquals(1.0f, strategy.getBrightness(1f), 0.0001f /*tolerance*/);
270 
271         // Check that null returns us to the default configuration.
272         strategy.setBrightnessConfiguration(null);
273         final int n = DISPLAY_LEVELS_NITS.length;
274         final float expectedBrightness = DISPLAY_LEVELS_NITS[n - 1] / DISPLAY_RANGE_NITS[1];
275         assertEquals(expectedBrightness,
276                 strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/);
277     }
278 
279     @Test
testPhysicalStrategyRecalculateSplines()280     public void testPhysicalStrategyRecalculateSplines() {
281         Resources res = createResources(EMPTY_INT_ARRAY);
282         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
283                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
284         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
285         float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
286         for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
287             adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
288         }
289 
290         // Default
291         assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
292                 TOLERANCE);
293         assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
294                 TOLERANCE);
295         assertEquals(DISPLAY_RANGE_NITS[0],
296                 strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
297         assertEquals(DISPLAY_RANGE_NITS[1],
298                 strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
299 
300         // Adjustment is turned on
301         strategy.recalculateSplines(true, adjustedNits50p);
302         assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
303                 TOLERANCE);
304         assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
305                 TOLERANCE);
306         assertEquals(DISPLAY_RANGE_NITS[0] / 2,
307                 strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
308         assertEquals(DISPLAY_RANGE_NITS[1] / 2,
309                 strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
310 
311         // Adjustment is turned off
312         strategy.recalculateSplines(false, adjustedNits50p);
313         assertEquals(DISPLAY_RANGE_NITS[0], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]),
314                 TOLERANCE);
315         assertEquals(DISPLAY_RANGE_NITS[1], strategy.convertToNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]),
316                 TOLERANCE);
317         assertEquals(DISPLAY_RANGE_NITS[0],
318                 strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[0]), TOLERANCE);
319         assertEquals(DISPLAY_RANGE_NITS[1],
320                 strategy.convertToAdjustedNits(BACKLIGHT_RANGE_ZERO_TO_ONE[1]), TOLERANCE);
321     }
322 
323     @Test
testDefaultStrategyIsPhysical()324     public void testDefaultStrategyIsPhysical() {
325         Resources res = createResources(DISPLAY_LEVELS_BACKLIGHT);
326         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
327                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, DISPLAY_LEVELS_NITS);
328         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
329         assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
330     }
331 
332     @Test
testNonStrictlyIncreasingLuxLevelsFails()333     public void testNonStrictlyIncreasingLuxLevelsFails() {
334         final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
335         final int idx = lux.length / 2;
336         float tmp = lux[idx];
337         lux[idx] = lux[idx + 1];
338         lux[idx + 1] = tmp;
339         Resources res = createResources(EMPTY_INT_ARRAY);
340         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
341                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
342         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
343         assertNull(strategy);
344 
345         // And make sure we get the same result even if it's monotone but not increasing.
346         lux[idx] = lux[idx + 1];
347         ddc = createDdc(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux,
348                 DISPLAY_LEVELS_NITS);
349         strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
350         assertNull(strategy);
351     }
352 
353     @Test
testDifferentNumberOfControlPointValuesFails()354     public void testDifferentNumberOfControlPointValuesFails() {
355         //Extra lux level
356         final float[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length + 1);
357         // Make sure it's strictly increasing so that the only failure is the differing array
358         // lengths
359         lux[lux.length - 1] = lux[lux.length - 2] + 1;
360         Resources res = createResources(EMPTY_INT_ARRAY);
361         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
362                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, lux, DISPLAY_LEVELS_NITS);
363         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
364         assertNull(strategy);
365 
366         res = createResources(DISPLAY_LEVELS_BACKLIGHT);
367         strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
368         assertNull(strategy);
369 
370         // Extra backlight level
371         final int[] backlight = Arrays.copyOf(
372                 DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length + 1);
373         backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
374         res = createResources(backlight);
375         ddc = createDdc(DISPLAY_RANGE_NITS,
376                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, EMPTY_FLOAT_ARRAY);
377         strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
378         assertNull(strategy);
379 
380         // Extra nits level
381         final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
382         nits[nits.length - 1] = nits[nits.length - 2] + 1;
383         res = createResources(EMPTY_INT_ARRAY);
384         ddc = createDdc(DISPLAY_RANGE_NITS,
385                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, LUX_LEVELS, nits);
386         strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
387         assertNull(strategy);
388     }
389 
390     @Test
testPhysicalStrategyRequiresNitsMapping()391     public void testPhysicalStrategyRequiresNitsMapping() {
392         Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
393         DisplayDeviceConfig ddc = createDdc(EMPTY_FLOAT_ARRAY /*nitsRange*/);
394         BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
395         assertNull(physical);
396 
397         res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
398         physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
399         assertNull(physical);
400 
401         res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
402         physical = BrightnessMappingStrategy.create(res, ddc, mMockDwbc);
403         assertNull(physical);
404     }
405 
406     @Test
testStrategiesAdaptToUserDataPoint()407     public void testStrategiesAdaptToUserDataPoint() {
408         Resources res = createResources(EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/);
409         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE,
410                 LUX_LEVELS, DISPLAY_LEVELS_NITS);
411         assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
412         ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
413         res = createResources(DISPLAY_LEVELS_BACKLIGHT);
414         assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc, mMockDwbc));
415     }
416 
417     @Test
testIdleModeConfigLoadsCorrectly()418     public void testIdleModeConfigLoadsCorrectly() {
419         Resources res = createResourcesIdle(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
420         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS, BACKLIGHT_RANGE_ZERO_TO_ONE);
421 
422         // Create an idle mode bms
423         // This will fail if it tries to fetch the wrong configuration.
424         BrightnessMappingStrategy bms = BrightnessMappingStrategy.createForIdleMode(res, ddc,
425                 mMockDwbc);
426         assertNotNull("BrightnessMappingStrategy should not be null", bms);
427 
428         // Ensure that the config is the one we set
429         // Ensure that the lux -> brightness -> nits path works. ()
430         for (int i = 0; i < DISPLAY_LEVELS_NITS_IDLE.length; i++) {
431             assertEquals(LUX_LEVELS_IDLE[i], bms.getDefaultConfig().getCurve().first[i], TOLERANCE);
432             assertEquals(DISPLAY_LEVELS_NITS_IDLE[i], bms.getDefaultConfig().getCurve().second[i],
433                     TOLERANCE);
434             assertEquals(bms.convertToNits(bms.getBrightness(LUX_LEVELS_IDLE[i])),
435                     DISPLAY_LEVELS_NITS_IDLE[i], TOLERANCE);
436         }
437     }
438 
assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy)439     private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) {
440         // Save out all of the initial brightness data for comparison after reset.
441         float[] initialBrightnessLevels = new float[LUX_LEVELS.length];
442         for (int i = 0; i < LUX_LEVELS.length; i++) {
443             initialBrightnessLevels[i] = strategy.getBrightness(LUX_LEVELS[i]);
444         }
445 
446         // Add a data point in the middle of the curve where the user has set the brightness max
447         final int idx = LUX_LEVELS.length / 2;
448         strategy.addUserDataPoint(LUX_LEVELS[idx], 1.0f);
449 
450         // Then make sure that all control points after the middle lux level are also set to max...
451         for (int i = idx; i < LUX_LEVELS.length; i++) {
452             assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.0001f /*tolerance*/);
453         }
454 
455         // ...and that all control points before the middle lux level are strictly less than the
456         // previous one still.
457         float prevBrightness = strategy.getBrightness(LUX_LEVELS[idx]);
458         for (int i = idx - 1; i >= 0; i--) {
459             float brightness = strategy.getBrightness(LUX_LEVELS[i]);
460             assertTrue("Brightness levels must be monotonic after adapting to user data",
461                     prevBrightness >= brightness);
462             prevBrightness = brightness;
463         }
464 
465         // Now reset the curve and make sure we go back to the initial brightness levels recorded
466         // before adding the user data point.
467         strategy.clearUserDataPoints();
468         for (int i = 0; i < LUX_LEVELS.length; i++) {
469             assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]),
470                     0.0001f /*tolerance*/);
471         }
472 
473         // Now set the middle of the lux range to something just above the minimum.
474         float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
475         strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.0001f);
476 
477         // Then make sure the curve is still monotonic.
478         prevBrightness = 0f;
479         for (float lux : LUX_LEVELS) {
480             float brightness = strategy.getBrightness(lux);
481             assertTrue("Brightness levels must be monotonic after adapting to user data",
482                     prevBrightness <= brightness);
483             prevBrightness = brightness;
484         }
485 
486         // And that the lowest lux level still gives the absolute minimum brightness. This should
487         // be true assuming that there are more than two lux levels in the curve since we picked a
488         // brightness just barely above the minimum for the middle of the curve.
489         minBrightness = (float) MathUtils.pow(minBrightness, MAXIMUM_GAMMA); // Gamma correction.
490         assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
491     }
492 
createResources(int[] brightnessLevelsBacklight)493     private Resources createResources(int[] brightnessLevelsBacklight) {
494         return createResources(brightnessLevelsBacklight, EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
495     }
496 
createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle)497     private Resources createResourcesIdle(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
498         return createResources(EMPTY_INT_ARRAY,
499                 luxLevelsIdle, brightnessLevelsNitsIdle);
500     }
501 
createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle)502     private Resources createResources(int[] brightnessLevelsBacklight, int[] luxLevelsIdle,
503             float[] brightnessLevelsNitsIdle) {
504 
505         Resources mockResources = mock(Resources.class);
506         if (luxLevelsIdle.length > 0) {
507             int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
508                     luxLevelsIdle.length);
509             when(mockResources.getIntArray(
510                     com.android.internal.R.array.config_autoBrightnessLevelsIdle))
511                     .thenReturn(luxLevelsIdleResource);
512         }
513 
514         when(mockResources.getIntArray(
515                 com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
516                 .thenReturn(brightnessLevelsBacklight);
517 
518         TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
519         when(mockResources.obtainTypedArray(
520                 com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
521                 .thenReturn(mockBrightnessLevelNitsIdle);
522 
523         when(mockResources.getInteger(
524                 com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
525                 .thenReturn(1);
526         when(mockResources.getInteger(
527                 com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
528                 .thenReturn(255);
529         when(mockResources.getFraction(
530                 com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
531                 .thenReturn(MAXIMUM_GAMMA);
532         return mockResources;
533     }
534 
createDdc()535     private DisplayDeviceConfig createDdc() {
536         return createDdc(DISPLAY_RANGE_NITS);
537     }
538 
createDdc(float[] nitsArray)539     private DisplayDeviceConfig createDdc(float[] nitsArray) {
540         return createDdc(nitsArray, DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
541     }
542 
createDdc(float[] nitsArray, float[] backlightArray)543     private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray) {
544         DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
545         when(mockDdc.getNits()).thenReturn(nitsArray);
546         when(mockDdc.getBrightness()).thenReturn(backlightArray);
547         when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
548         when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
549         return mockDdc;
550     }
551 
createDdc(float[] nitsArray, float[] backlightArray, float[] luxLevelsFloat, float[] brightnessLevelsNits)552     private DisplayDeviceConfig createDdc(float[] nitsArray, float[] backlightArray,
553             float[] luxLevelsFloat, float[] brightnessLevelsNits) {
554         DisplayDeviceConfig mockDdc = mock(DisplayDeviceConfig.class);
555         when(mockDdc.getNits()).thenReturn(nitsArray);
556         when(mockDdc.getBrightness()).thenReturn(backlightArray);
557         when(mockDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevelsFloat);
558         when(mockDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(brightnessLevelsNits);
559         return mockDdc;
560     }
561 
createFloatTypedArray(float[] vals)562     private TypedArray createFloatTypedArray(float[] vals) {
563         TypedArray mockArray = mock(TypedArray.class);
564         when(mockArray.length()).thenAnswer(invocation -> {
565             return vals.length;
566         });
567         when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
568             final float def = (float) invocation.getArguments()[1];
569             if (vals == null) {
570                 return def;
571             }
572             int idx = (int) invocation.getArguments()[0];
573             if (idx >= 0 && idx < vals.length) {
574                 return vals[idx];
575             } else {
576                 return def;
577             }
578         });
579         return mockArray;
580     }
581 
582     // Gamma correction tests.
583     // x0 = 100   y0 = ~0.01
584     // x1 = 1000  y1 = ~0.20
585     // x2 = 2500  y2 = ~0.50
586     // x3 = 4000  y3 = ~0.80
587     // x4 = 4900  y4 = ~0.99
588 
589     @Test
testGammaCorrectionLowChangeAtCenter()590     public void testGammaCorrectionLowChangeAtCenter() {
591         // If we set a user data point at (x2, y2^0.5), i.e. gamma = 0.5, it should bump the rest
592         // of the spline accordingly.
593         final int x1 = 1000;
594         final int x2 = 2500;
595         final int x3 = 4000;
596         final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
597         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
598         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
599 
600         Resources resources = createResources(EMPTY_INT_ARRAY);
601         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
602                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
603                 GAMMA_CORRECTION_NITS);
604         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
605                 mMockDwbc);
606         // Let's start with a validity check:
607         assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
608         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
609         assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
610         // OK, let's roll:
611         float gamma = 0.5f;
612         strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
613         assertEquals(MathUtils.pow(y1, gamma), strategy.getBrightness(x1), 0.0001f /* tolerance */);
614         assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
615         assertEquals(MathUtils.pow(y3, gamma), strategy.getBrightness(x3), 0.0001f /* tolerance */);
616         // The adjustment should be +0.6308 (manual calculation).
617         assertEquals(+0.6308f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
618     }
619 
620     @Test
testGammaCorrectionHighChangeAtCenter()621     public void testGammaCorrectionHighChangeAtCenter() {
622         // This time we set a user data point at (x2, y2^0.25), i.e. gamma = 0.3 (the minimum),
623         // which should bump the rest of the spline accordingly, and further correct x2 to hit
624         // y2^0.25 (not y2^0.3).
625         final int x1 = 1000;
626         final int x2 = 2500;
627         final int x3 = 4000;
628         final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
629         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
630         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
631         Resources resources = createResources(EMPTY_INT_ARRAY);
632         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
633                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
634                 GAMMA_CORRECTION_NITS);
635         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
636                 mMockDwbc);
637         // Validity check:
638         assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
639         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
640         assertEquals(y3, strategy.getBrightness(x3), 0.0001f /* tolerance */);
641         // Let's roll:
642         float gamma = 0.25f;
643         final float minGamma = 1.0f / MAXIMUM_GAMMA;
644         strategy.addUserDataPoint(x2, (float) MathUtils.pow(y2, gamma));
645         assertEquals(MathUtils.pow(y1, minGamma),
646                 strategy.getBrightness(x1), 0.0001f /* tolerance */);
647         assertEquals(MathUtils.pow(y2, gamma),
648                 strategy.getBrightness(x2), 0.0001f /* tolerance */);
649         assertEquals(MathUtils.pow(y3, minGamma),
650                 strategy.getBrightness(x3), 0.0001f /* tolerance */);
651         // The adjustment should be +1.0 (maximum adjustment).
652         assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
653     }
654 
655     @Test
testGammaCorrectionExtremeChangeAtCenter()656     public void testGammaCorrectionExtremeChangeAtCenter() {
657         // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
658         // just make sure the adjustment reflects the change.
659         Resources resources = createResources(EMPTY_INT_ARRAY);
660         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
661                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
662                 GAMMA_CORRECTION_NITS);
663         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
664                 mMockDwbc);
665         assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
666         strategy.addUserDataPoint(2500, 1.0f);
667         assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
668         strategy.addUserDataPoint(2500, 0.0f);
669         assertEquals(-1.0f, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
670     }
671 
672     @Test
testGammaCorrectionChangeAtEdges()673     public void testGammaCorrectionChangeAtEdges() {
674         // The algorithm behaves differently at the edges, because gamma correction there tends to
675         // be extreme. If we add a user data point at (x0, y0+0.3), the adjustment should be 0.3,
676         // resulting in a gamma of 3**-0.6 = ~0.52.
677         final int x0 = 100;
678         final int x2 = 2500;
679         final int x4 = 4900;
680         final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
681         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
682         final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
683         Resources resources = createResources(EMPTY_INT_ARRAY);
684         DisplayDeviceConfig ddc = createDdc(DISPLAY_RANGE_NITS,
685                 DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT, GAMMA_CORRECTION_LUX,
686                 GAMMA_CORRECTION_NITS);
687         BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
688                 mMockDwbc);
689         // Validity, as per tradition:
690         assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
691         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
692         assertEquals(y4, strategy.getBrightness(x4), 0.0001f /* tolerance */);
693         // Rollin':
694         float adjustment = 0.3f;
695         float gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
696         strategy.addUserDataPoint(x0, y0 + adjustment);
697         assertEquals(y0 + adjustment, strategy.getBrightness(x0), 0.0001f /* tolerance */);
698         assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
699         assertEquals(MathUtils.pow(y4, gamma), strategy.getBrightness(x4), 0.0001f /* tolerance */);
700         assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
701         // Similarly, if we set a user data point at (x4, 1.0), the adjustment should be 1 - y4.
702         adjustment = 1.0f - y4;
703         gamma = (float) MathUtils.pow(MAXIMUM_GAMMA, -adjustment);
704         strategy.addUserDataPoint(x4, 1.0f);
705         assertEquals(MathUtils.pow(y0, gamma), strategy.getBrightness(x0), 0.0001f /* tolerance */);
706         assertEquals(MathUtils.pow(y2, gamma), strategy.getBrightness(x2), 0.0001f /* tolerance */);
707         assertEquals(1.0f, strategy.getBrightness(x4), 0.0001f /* tolerance */);
708         assertEquals(adjustment, strategy.getAutoBrightnessAdjustment(), 0.0001f /* tolerance */);
709     }
710 }
711