blob: fb25cf3f01e0bb3fd3643c0e15d723ad6e3a7046 [file] [log] [blame]
Michael Wrighteef0e132017-11-21 17:57:52 +00001/*
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
17package com.android.server.display;
18
19import static org.junit.Assert.assertEquals;
Michael Wrightd5df3612018-01-02 12:44:52 +000020import static org.junit.Assert.assertNotEquals;
Michael Wrighteef0e132017-11-21 17:57:52 +000021import static org.junit.Assert.assertNotNull;
22import static org.junit.Assert.assertNull;
23import static org.junit.Assert.assertTrue;
Michael Wright144aac92017-12-21 18:37:41 +000024import static org.mockito.ArgumentMatchers.anyFloat;
25import static org.mockito.ArgumentMatchers.anyInt;
26import static org.mockito.Mockito.mock;
27import static org.mockito.Mockito.when;
Michael Wrighteef0e132017-11-21 17:57:52 +000028
Michael Wright144aac92017-12-21 18:37:41 +000029import android.content.res.Resources;
30import android.content.res.TypedArray;
Michael Wrightd5df3612018-01-02 12:44:52 +000031import android.hardware.display.BrightnessConfiguration;
Michael Wrighteef0e132017-11-21 17:57:52 +000032import android.os.PowerManager;
33import android.support.test.filters.SmallTest;
34import android.support.test.runner.AndroidJUnit4;
35import android.util.Spline;
36
37import org.junit.Test;
38import org.junit.runner.RunWith;
39
40import java.util.Arrays;
41
42@SmallTest
43@RunWith(AndroidJUnit4.class)
44public class BrightnessMappingStrategyTest {
45
Michael Wright144aac92017-12-21 18:37:41 +000046 private static final int[] LUX_LEVELS = {
47 0,
48 5,
49 20,
50 40,
51 100,
52 325,
53 600,
54 1250,
55 2200,
56 4000,
57 5000
Michael Wrighteef0e132017-11-21 17:57:52 +000058 };
59
60 private static final float[] DISPLAY_LEVELS_NITS = {
61 13.25f,
62 54.0f,
63 78.85f,
64 105.02f,
65 132.7f,
66 170.12f,
67 212.1f,
68 265.2f,
69 335.8f,
70 415.2f,
71 478.5f,
72 };
73
74 private static final int[] DISPLAY_LEVELS_BACKLIGHT = {
75 9,
76 30,
77 45,
78 62,
79 78,
80 96,
81 119,
82 146,
83 178,
84 221,
85 255
86 };
87
88 private static final float[] DISPLAY_RANGE_NITS = { 2.685f, 478.5f };
89 private static final int[] BACKLIGHT_RANGE = { 1, 255 };
90
Michael Wright144aac92017-12-21 18:37:41 +000091 private static final float[] EMPTY_FLOAT_ARRAY = new float[0];
92 private static final int[] EMPTY_INT_ARRAY = new int[0];
93
Michael Wrighteef0e132017-11-21 17:57:52 +000094 @Test
95 public void testSimpleStrategyMappingAtControlPoints() {
Michael Wright144aac92017-12-21 18:37:41 +000096 Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
97 BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +000098 assertNotNull("BrightnessMappingStrategy should not be null", simple);
99 for (int i = 0; i < LUX_LEVELS.length; i++) {
100 final float expectedLevel =
101 (float) DISPLAY_LEVELS_BACKLIGHT[i] / PowerManager.BRIGHTNESS_ON;
102 assertEquals(expectedLevel,
103 simple.getBrightness(LUX_LEVELS[i]), 0.01f /*tolerance*/);
104 }
105 }
106
107 @Test
108 public void testSimpleStrategyMappingBetweenControlPoints() {
Michael Wright144aac92017-12-21 18:37:41 +0000109 Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
110 BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000111 assertNotNull("BrightnessMappingStrategy should not be null", simple);
112 for (int i = 1; i < LUX_LEVELS.length; i++) {
113 final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
114 final float backlight = simple.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
115 assertTrue("Desired brightness should be between adjacent control points.",
Michael Wrightd5df3612018-01-02 12:44:52 +0000116 backlight > DISPLAY_LEVELS_BACKLIGHT[i - 1]
Michael Wrighteef0e132017-11-21 17:57:52 +0000117 && backlight < DISPLAY_LEVELS_BACKLIGHT[i]);
118 }
119 }
120
121 @Test
Michael Wrightd5df3612018-01-02 12:44:52 +0000122 public void testSimpleStrategyIgnoresNewConfiguration() {
123 Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
124 BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
125
126 final int N = LUX_LEVELS.length;
127 final float[] lux = { 0f, 1f };
128 final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
129
130 BrightnessConfiguration config = new BrightnessConfiguration.Builder()
131 .setCurve(lux, nits)
132 .build();
133 strategy.setBrightnessConfiguration(config);
134 assertNotEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/);
135 }
136
137 @Test
138 public void testSimpleStrategyIgnoresNullConfiguration() {
139 Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
140 BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
141
142 strategy.setBrightnessConfiguration(null);
143 final int N = DISPLAY_LEVELS_BACKLIGHT.length;
144 final float expectedBrightness =
145 (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON;
146 assertEquals(expectedBrightness,
147 strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01 /*tolerance*/);
148 }
149
150 @Test
Michael Wrighteef0e132017-11-21 17:57:52 +0000151 public void testPhysicalStrategyMappingAtControlPoints() {
Michael Wright144aac92017-12-21 18:37:41 +0000152 Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
153 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
154 BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000155 assertNotNull("BrightnessMappingStrategy should not be null", physical);
156 for (int i = 0; i < LUX_LEVELS.length; i++) {
157 final float expectedLevel = DISPLAY_LEVELS_NITS[i] / DISPLAY_RANGE_NITS[1];
158 assertEquals(expectedLevel,
159 physical.getBrightness(LUX_LEVELS[i]), 0.01f /*tolerance*/);
160 }
161 }
162
163 @Test
164 public void testPhysicalStrategyMappingBetweenControlPoints() {
Michael Wright144aac92017-12-21 18:37:41 +0000165 Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
166 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
167 BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000168 assertNotNull("BrightnessMappingStrategy should not be null", physical);
169 Spline backlightToBrightness =
170 Spline.createSpline(toFloatArray(BACKLIGHT_RANGE), DISPLAY_RANGE_NITS);
171 for (int i = 1; i < LUX_LEVELS.length; i++) {
172 final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
173 final float backlight = physical.getBrightness(lux) * PowerManager.BRIGHTNESS_ON;
174 final float nits = backlightToBrightness.interpolate(backlight);
175 assertTrue("Desired brightness should be between adjacent control points.",
Michael Wrightd5df3612018-01-02 12:44:52 +0000176 nits > DISPLAY_LEVELS_NITS[i - 1] && nits < DISPLAY_LEVELS_NITS[i]);
Michael Wrighteef0e132017-11-21 17:57:52 +0000177 }
178 }
179
180 @Test
Michael Wrightd5df3612018-01-02 12:44:52 +0000181 public void testPhysicalStrategyUsesNewConfigurations() {
182 Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
183 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
184 BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
185
186 final float[] lux = { 0f, 1f };
187 final float[] nits = {
188 DISPLAY_RANGE_NITS[0],
189 DISPLAY_RANGE_NITS[DISPLAY_RANGE_NITS.length - 1]
190 };
191
192 BrightnessConfiguration config = new BrightnessConfiguration.Builder()
193 .setCurve(lux, nits)
194 .build();
195 strategy.setBrightnessConfiguration(config);
196 assertEquals(1.0f, strategy.getBrightness(1f), 0.01 /*tolerance*/);
197
198 // Check that null returns us to the default configuration.
199 strategy.setBrightnessConfiguration(null);
200 final int N = DISPLAY_LEVELS_NITS.length;
201 final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1];
202 assertEquals(expectedBrightness,
203 strategy.getBrightness(LUX_LEVELS[N - 1]), 0.01f /*tolerance*/);
204 }
205
206 @Test
Michael Wrighteef0e132017-11-21 17:57:52 +0000207 public void testDefaultStrategyIsPhysical() {
Michael Wright144aac92017-12-21 18:37:41 +0000208 Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT,
Michael Wrighteef0e132017-11-21 17:57:52 +0000209 DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
Michael Wright144aac92017-12-21 18:37:41 +0000210 BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000211 assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
212 }
213
214 @Test
215 public void testNonStrictlyIncreasingLuxLevelsFails() {
Michael Wright144aac92017-12-21 18:37:41 +0000216 final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length);
Michael Wrighteef0e132017-11-21 17:57:52 +0000217 final int idx = lux.length / 2;
Michael Wright144aac92017-12-21 18:37:41 +0000218 int tmp = lux[idx];
Michael Wrighteef0e132017-11-21 17:57:52 +0000219 lux[idx] = lux[idx+1];
220 lux[idx+1] = tmp;
Michael Wright144aac92017-12-21 18:37:41 +0000221 Resources res = createResources(lux, DISPLAY_LEVELS_NITS,
222 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
223 BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000224 assertNull(strategy);
225
226 // And make sure we get the same result even if it's monotone but not increasing.
227 lux[idx] = lux[idx+1];
Michael Wright144aac92017-12-21 18:37:41 +0000228 res = createResources(lux, DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
229 strategy = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000230 assertNull(strategy);
231 }
232
233 @Test
234 public void testDifferentNumberOfControlPointValuesFails() {
235 //Extra lux level
Michael Wright144aac92017-12-21 18:37:41 +0000236 final int[] lux = Arrays.copyOf(LUX_LEVELS, LUX_LEVELS.length+1);
Michael Wrighteef0e132017-11-21 17:57:52 +0000237 // Make sure it's strictly increasing so that the only failure is the differing array
238 // lengths
239 lux[lux.length - 1] = lux[lux.length - 2] + 1;
Michael Wright144aac92017-12-21 18:37:41 +0000240 Resources res = createResources(lux, DISPLAY_LEVELS_NITS,
241 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
242 BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000243 assertNull(strategy);
244
Michael Wright144aac92017-12-21 18:37:41 +0000245 res = createResources(lux, DISPLAY_LEVELS_BACKLIGHT);
246 strategy = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000247 assertNull(strategy);
248
249 // Extra backlight level
250 final int[] backlight = Arrays.copyOf(
251 DISPLAY_LEVELS_BACKLIGHT, DISPLAY_LEVELS_BACKLIGHT.length+1);
252 backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
Michael Wright144aac92017-12-21 18:37:41 +0000253 res = createResources(LUX_LEVELS, backlight);
254 strategy = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000255 assertNull(strategy);
256
257 // Extra nits level
258 final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length+1);
259 nits[nits.length - 1] = nits[nits.length - 2] + 1;
Michael Wright144aac92017-12-21 18:37:41 +0000260 res = createResources(LUX_LEVELS, nits, DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
261 strategy = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000262 assertNull(strategy);
263 }
264
265 @Test
266 public void testPhysicalStrategyRequiresNitsMapping() {
Michael Wright144aac92017-12-21 18:37:41 +0000267 Resources res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
268 DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/, BACKLIGHT_RANGE);
269 BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000270 assertNull(physical);
271
Michael Wright144aac92017-12-21 18:37:41 +0000272 res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
273 DISPLAY_LEVELS_NITS, DISPLAY_RANGE_NITS, EMPTY_INT_ARRAY /*backlightRange*/);
274 physical = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000275 assertNull(physical);
276
Michael Wright144aac92017-12-21 18:37:41 +0000277 res = createResources(LUX_LEVELS, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
278 DISPLAY_LEVELS_NITS, EMPTY_FLOAT_ARRAY /*nitsRange*/,
279 EMPTY_INT_ARRAY /*backlightRange*/);
280 physical = BrightnessMappingStrategy.create(res);
Michael Wrighteef0e132017-11-21 17:57:52 +0000281 assertNull(physical);
282 }
283
Michael Wrightd8460232018-01-16 18:04:59 +0000284 @Test
285 public void testStrategiesAdaptToUserDataPoint() {
286 Resources res = createResources(LUX_LEVELS, DISPLAY_LEVELS_NITS,
287 DISPLAY_RANGE_NITS, BACKLIGHT_RANGE);
288 assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res));
289 res = createResources(LUX_LEVELS, DISPLAY_LEVELS_BACKLIGHT);
290 assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res));
291 }
292
293 private static void assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy strategy) {
294 // Save out all of the initial brightness data for comparison after reset.
295 float[] initialBrightnessLevels = new float[LUX_LEVELS.length];
296 for (int i = 0; i < LUX_LEVELS.length; i++) {
297 initialBrightnessLevels[i] = strategy.getBrightness(LUX_LEVELS[i]);
298 }
299
300 // Add a data point in the middle of the curve where the user has set the brightness max
301 final int idx = LUX_LEVELS.length / 2;
302 strategy.addUserDataPoint(LUX_LEVELS[idx], 1.0f);
303
304 // Then make sure that all control points after the middle lux level are also set to max...
305 for (int i = idx; i < LUX_LEVELS.length; i++) {
306 assertEquals(strategy.getBrightness(LUX_LEVELS[idx]), 1.0, 0.01 /*tolerance*/);
307 }
308
309 // ...and that all control points before the middle lux level are strictly less than the
310 // previous one still.
311 float prevBrightness = strategy.getBrightness(LUX_LEVELS[idx]);
312 for (int i = idx - 1; i >= 0; i--) {
313 float brightness = strategy.getBrightness(LUX_LEVELS[i]);
314 assertTrue("Brightness levels must be monotonic after adapting to user data",
315 prevBrightness >= brightness);
316 prevBrightness = brightness;
317 }
318
319 // Now reset the curve and make sure we go back to the initial brightness levels recorded
320 // before adding the user data point.
321 strategy.clearUserDataPoints();
322 for (int i = 0; i < LUX_LEVELS.length; i++) {
323 assertEquals(initialBrightnessLevels[i], strategy.getBrightness(LUX_LEVELS[i]),
324 0.01 /*tolerance*/);
325 }
326
327 // Now set the middle of the lux range to something just above the minimum.
328 final float minBrightness = strategy.getBrightness(LUX_LEVELS[0]);
329 strategy.addUserDataPoint(LUX_LEVELS[idx], minBrightness + 0.01f);
330
331 // Then make sure the curve is still monotonic.
332 prevBrightness = 0f;
333 for (float lux : LUX_LEVELS) {
334 float brightness = strategy.getBrightness(lux);
335 assertTrue("Brightness levels must be monotonic after adapting to user data",
336 prevBrightness <= brightness);
337 prevBrightness = brightness;
338 }
339
340 // And that the lowest lux level still gives the absolute minimum brightness. This should
341 // be true assuming that there are more than two lux levels in the curve since we picked a
342 // brightness just barely above the minimum for the middle of the curve.
343 assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.001 /*tolerance*/);
344 }
345
Michael Wrighteef0e132017-11-21 17:57:52 +0000346 private static float[] toFloatArray(int[] vals) {
347 float[] newVals = new float[vals.length];
348 for (int i = 0; i < vals.length; i++) {
349 newVals[i] = (float) vals[i];
350 }
351 return newVals;
352 }
Michael Wright144aac92017-12-21 18:37:41 +0000353
354 private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight) {
355 return createResources(luxLevels, brightnessLevelsBacklight,
356 EMPTY_FLOAT_ARRAY /*brightnessLevelsNits*/, EMPTY_FLOAT_ARRAY /*nitsRange*/,
357 EMPTY_INT_ARRAY /*backlightRange*/);
358 }
359
360 private Resources createResources(int[] luxLevels, float[] brightnessLevelsNits,
361 float[] nitsRange, int[] backlightRange) {
362 return createResources(luxLevels, EMPTY_INT_ARRAY /*brightnessLevelsBacklight*/,
363 brightnessLevelsNits, nitsRange, backlightRange);
364 }
365
366 private Resources createResources(int[] luxLevels, int[] brightnessLevelsBacklight,
367 float[] brightnessLevelsNits, float[] nitsRange, int[] backlightRange) {
368 Resources mockResources = mock(Resources.class);
369 // For historical reasons, the lux levels resource implicitly defines the first point as 0,
370 // so we need to chop it off of the array the mock resource object returns.
371 int[] luxLevelsResource = Arrays.copyOfRange(luxLevels, 1, luxLevels.length);
372 when(mockResources.getIntArray(com.android.internal.R.array.config_autoBrightnessLevels))
373 .thenReturn(luxLevelsResource);
374
375 when(mockResources.getIntArray(
376 com.android.internal.R.array.config_autoBrightnessLcdBacklightValues))
377 .thenReturn(brightnessLevelsBacklight);
378
379 TypedArray mockBrightnessLevelNits = createFloatTypedArray(brightnessLevelsNits);
380 when(mockResources.obtainTypedArray(
381 com.android.internal.R.array.config_autoBrightnessDisplayValuesNits))
382 .thenReturn(mockBrightnessLevelNits);
383
384 TypedArray mockNitsRange = createFloatTypedArray(nitsRange);
385 when(mockResources.obtainTypedArray(
386 com.android.internal.R.array.config_screenBrightnessNits))
387 .thenReturn(mockNitsRange);
388
389 when(mockResources.getIntArray(
390 com.android.internal.R.array.config_screenBrightnessBacklight))
391 .thenReturn(backlightRange);
392
393 when(mockResources.getInteger(
394 com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
395 .thenReturn(1);
396 when(mockResources.getInteger(
397 com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
398 .thenReturn(255);
399 return mockResources;
400 }
401
402 private TypedArray createFloatTypedArray(float[] vals) {
403 TypedArray mockArray = mock(TypedArray.class);
404 when(mockArray.length()).thenAnswer(invocation -> {
405 return vals.length;
406 });
407 when(mockArray.getFloat(anyInt(), anyFloat())).thenAnswer(invocation -> {
408 final float def = (float) invocation.getArguments()[1];
409 if (vals == null) {
410 return def;
411 }
412 int idx = (int) invocation.getArguments()[0];
413 if (idx >= 0 && idx < vals.length) {
414 return vals[idx];
415 } else {
416 return def;
417 }
418 });
419 return mockArray;
420 }
421
Michael Wrighteef0e132017-11-21 17:57:52 +0000422}