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