1 /* 2 * Copyright (C) 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 android.annotation.Nullable; 20 import android.content.pm.ApplicationInfo; 21 import android.content.res.Resources; 22 import android.content.res.TypedArray; 23 import android.hardware.display.BrightnessConfiguration; 24 import android.hardware.display.BrightnessCorrection; 25 import android.os.PowerManager; 26 import android.util.MathUtils; 27 import android.util.Pair; 28 import android.util.Slog; 29 import android.util.Spline; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.internal.display.BrightnessSynchronizer; 33 import com.android.internal.util.Preconditions; 34 import com.android.server.display.utils.Plog; 35 36 import java.io.PrintWriter; 37 import java.util.Arrays; 38 import java.util.Objects; 39 40 /** 41 * A utility to map from an ambient brightness to a display's "backlight" brightness based on the 42 * available display information and brightness configuration. 43 * 44 * Note that without a mapping from the nits to a display backlight level, any 45 * {@link BrightnessConfiguration}s that are set are just ignored. 46 */ 47 public abstract class BrightnessMappingStrategy { 48 private static final String TAG = "BrightnessMappingStrategy"; 49 50 private static final float LUX_GRAD_SMOOTHING = 0.25f; 51 private static final float MAX_GRAD = 1.0f; 52 private static final float SHORT_TERM_MODEL_THRESHOLD_RATIO = 0.6f; 53 54 // Constant that ensures that each step of the curve can increase by up to at least 55 // MIN_PERMISSABLE_INCREASE. Otherwise when the brightness is set to 0, the curve will never 56 // increase and will always be 0. 57 private static final float MIN_PERMISSABLE_INCREASE = 0.004f; 58 59 protected boolean mLoggingEnabled; 60 61 private static final Plog PLOG = Plog.createSystemPlog(TAG); 62 63 @Nullable create(Resources resources, DisplayDeviceConfig displayDeviceConfig)64 public static BrightnessMappingStrategy create(Resources resources, 65 DisplayDeviceConfig displayDeviceConfig) { 66 67 // Display independent values 68 float[] luxLevels = getLuxLevels(resources.getIntArray( 69 com.android.internal.R.array.config_autoBrightnessLevels)); 70 int[] brightnessLevelsBacklight = resources.getIntArray( 71 com.android.internal.R.array.config_autoBrightnessLcdBacklightValues); 72 float[] brightnessLevelsNits = getFloatArray(resources.obtainTypedArray( 73 com.android.internal.R.array.config_autoBrightnessDisplayValuesNits)); 74 float autoBrightnessAdjustmentMaxGamma = resources.getFraction( 75 com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 76 1, 1); 77 long shortTermModelTimeout = resources.getInteger( 78 com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout); 79 80 // Display dependent values - used for physical mapping strategy nits -> brightness 81 final float[] nitsRange = displayDeviceConfig.getNits(); 82 final float[] brightnessRange = displayDeviceConfig.getBrightness(); 83 84 if (isValidMapping(nitsRange, brightnessRange) 85 && isValidMapping(luxLevels, brightnessLevelsNits)) { 86 87 BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder( 88 luxLevels, brightnessLevelsNits); 89 builder.setShortTermModelTimeoutMillis(shortTermModelTimeout); 90 builder.setShortTermModelLowerLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO); 91 builder.setShortTermModelUpperLuxMultiplier(SHORT_TERM_MODEL_THRESHOLD_RATIO); 92 return new PhysicalMappingStrategy(builder.build(), nitsRange, brightnessRange, 93 autoBrightnessAdjustmentMaxGamma); 94 } else if (isValidMapping(luxLevels, brightnessLevelsBacklight)) { 95 return new SimpleMappingStrategy(luxLevels, brightnessLevelsBacklight, 96 autoBrightnessAdjustmentMaxGamma, shortTermModelTimeout); 97 } else { 98 return null; 99 } 100 } 101 getLuxLevels(int[] lux)102 private static float[] getLuxLevels(int[] lux) { 103 // The first control point is implicit and always at 0 lux. 104 float[] levels = new float[lux.length + 1]; 105 for (int i = 0; i < lux.length; i++) { 106 levels[i + 1] = (float) lux[i]; 107 } 108 return levels; 109 } 110 111 /** 112 * Extracts a float array from the specified {@link TypedArray}. 113 * 114 * @param array The array to convert. 115 * @return the given array as a float array. 116 */ getFloatArray(TypedArray array)117 public static float[] getFloatArray(TypedArray array) { 118 final int N = array.length(); 119 float[] vals = new float[N]; 120 for (int i = 0; i < N; i++) { 121 vals[i] = array.getFloat(i, PowerManager.BRIGHTNESS_OFF_FLOAT); 122 } 123 array.recycle(); 124 return vals; 125 } 126 isValidMapping(float[] x, float[] y)127 private static boolean isValidMapping(float[] x, float[] y) { 128 if (x == null || y == null || x.length == 0 || y.length == 0) { 129 return false; 130 } 131 if (x.length != y.length) { 132 return false; 133 } 134 final int N = x.length; 135 float prevX = x[0]; 136 float prevY = y[0]; 137 if (prevX < 0 || prevY < 0 || Float.isNaN(prevX) || Float.isNaN(prevY)) { 138 return false; 139 } 140 for (int i = 1; i < N; i++) { 141 if (prevX >= x[i] || prevY > y[i]) { 142 return false; 143 } 144 if (Float.isNaN(x[i]) || Float.isNaN(y[i])) { 145 return false; 146 } 147 prevX = x[i]; 148 prevY = y[i]; 149 } 150 return true; 151 } 152 isValidMapping(float[] x, int[] y)153 private static boolean isValidMapping(float[] x, int[] y) { 154 if (x == null || y == null || x.length == 0 || y.length == 0) { 155 return false; 156 } 157 if (x.length != y.length) { 158 return false; 159 } 160 final int N = x.length; 161 float prevX = x[0]; 162 int prevY = y[0]; 163 if (prevX < 0 || prevY < 0 || Float.isNaN(prevX)) { 164 return false; 165 } 166 for (int i = 1; i < N; i++) { 167 if (prevX >= x[i] || prevY > y[i]) { 168 return false; 169 } 170 if (Float.isNaN(x[i])) { 171 return false; 172 } 173 prevX = x[i]; 174 prevY = y[i]; 175 } 176 return true; 177 } 178 179 /** 180 * Enable/disable logging. 181 * 182 * @param loggingEnabled 183 * Whether logging should be on/off. 184 * 185 * @return Whether the method succeeded or not. 186 */ setLoggingEnabled(boolean loggingEnabled)187 public boolean setLoggingEnabled(boolean loggingEnabled) { 188 if (mLoggingEnabled == loggingEnabled) { 189 return false; 190 } 191 mLoggingEnabled = loggingEnabled; 192 return true; 193 } 194 195 /** 196 * Sets the {@link BrightnessConfiguration}. 197 * 198 * @param config The new configuration. If {@code null} is passed, the default configuration is 199 * used. 200 * @return Whether the brightness configuration has changed. 201 */ setBrightnessConfiguration(@ullable BrightnessConfiguration config)202 public abstract boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config); 203 204 /** 205 * Gets the current {@link BrightnessConfiguration}. 206 */ 207 @Nullable getBrightnessConfiguration()208 public abstract BrightnessConfiguration getBrightnessConfiguration(); 209 210 /** 211 * Returns the desired brightness of the display based on the current ambient lux, including 212 * any context-related corrections. 213 * 214 * The returned brightness will be in the range [0, 1.0], where 1.0 is the display at max 215 * brightness and 0 is the display at minimum brightness. 216 * 217 * @param lux The current ambient brightness in lux. 218 * @param packageName the foreground app package name. 219 * @param category the foreground app package category. 220 * @return The desired brightness of the display normalized to the range [0, 1.0]. 221 */ getBrightness(float lux, String packageName, @ApplicationInfo.Category int category)222 public abstract float getBrightness(float lux, String packageName, 223 @ApplicationInfo.Category int category); 224 225 /** 226 * Returns the desired brightness of the display based on the current ambient lux. 227 * 228 * The returned brightness wil be in the range [0, 1.0], where 1.0 is the display at max 229 * brightness and 0 is the display at minimum brightness. 230 * 231 * @param lux The current ambient brightness in lux. 232 * 233 * @return The desired brightness of the display normalized to the range [0, 1.0]. 234 */ getBrightness(float lux)235 public float getBrightness(float lux) { 236 return getBrightness(lux, null /* packageName */, ApplicationInfo.CATEGORY_UNDEFINED); 237 } 238 239 /** 240 * Returns the current auto-brightness adjustment. 241 * 242 * The returned adjustment is a value in the range [-1.0, 1.0] such that 243 * {@code config_autoBrightnessAdjustmentMaxGamma<sup>-adjustment</sup>} is used to gamma 244 * correct the brightness curve. 245 */ getAutoBrightnessAdjustment()246 public abstract float getAutoBrightnessAdjustment(); 247 248 /** 249 * Sets the auto-brightness adjustment. 250 * 251 * @param adjustment The desired auto-brightness adjustment. 252 * @return Whether the auto-brightness adjustment has changed. 253 * 254 * @Deprecated The auto-brightness adjustment should not be set directly, but rather inferred 255 * from user data points. 256 */ setAutoBrightnessAdjustment(float adjustment)257 public abstract boolean setAutoBrightnessAdjustment(float adjustment); 258 259 /** 260 * Converts the provided brightness value to nits if possible. 261 * 262 * Returns -1.0f if there's no available mapping for the brightness to nits. 263 */ convertToNits(float brightness)264 public abstract float convertToNits(float brightness); 265 266 /** 267 * Adds a user interaction data point to the brightness mapping. 268 * 269 * This data point <b>must</b> exist on the brightness curve as a result of this call. This is 270 * so that the next time we come to query what the screen brightness should be, we get what the 271 * user requested rather than immediately changing to some other value. 272 * 273 * Currently, we only keep track of one of these at a time to constrain what can happen to the 274 * curve. 275 */ addUserDataPoint(float lux, float brightness)276 public abstract void addUserDataPoint(float lux, float brightness); 277 278 /** 279 * Removes any short term adjustments made to the curve from user interactions. 280 * 281 * Note that this does *not* reset the mapping to its initial state, any brightness 282 * configurations that have been applied will continue to be in effect. This solely removes the 283 * effects of user interactions on the model. 284 */ clearUserDataPoints()285 public abstract void clearUserDataPoints(); 286 287 /** @return True if there are any short term adjustments applied to the curve. */ hasUserDataPoints()288 public abstract boolean hasUserDataPoints(); 289 290 /** @return True if the current brightness configuration is the default one. */ isDefaultConfig()291 public abstract boolean isDefaultConfig(); 292 293 /** @return The default brightness configuration. */ getDefaultConfig()294 public abstract BrightnessConfiguration getDefaultConfig(); 295 296 /** Recalculates the backlight-to-nits and nits-to-backlight splines. */ recalculateSplines(boolean applyAdjustment, float[] adjustment)297 public abstract void recalculateSplines(boolean applyAdjustment, float[] adjustment); 298 299 /** 300 * Returns the timeout for the short term model 301 * 302 * Timeout after which we remove the effects any user interactions might've had on the 303 * brightness mapping. This timeout doesn't start until we transition to a non-interactive 304 * display policy so that we don't reset while users are using their devices, but also so that 305 * we don't erroneously keep the short-term model if the device is dozing but the 306 * display is fully on. 307 */ getShortTermModelTimeout()308 public abstract long getShortTermModelTimeout(); 309 dump(PrintWriter pw)310 public abstract void dump(PrintWriter pw); 311 312 /** 313 * Check if the short term model should be reset given the anchor lux the last 314 * brightness change was made at and the current ambient lux. 315 */ shouldResetShortTermModel(float ambientLux, float shortTermModelAnchor)316 public boolean shouldResetShortTermModel(float ambientLux, float shortTermModelAnchor) { 317 BrightnessConfiguration config = getBrightnessConfiguration(); 318 float minThresholdRatio = SHORT_TERM_MODEL_THRESHOLD_RATIO; 319 float maxThresholdRatio = SHORT_TERM_MODEL_THRESHOLD_RATIO; 320 if (config != null) { 321 if (!Float.isNaN(config.getShortTermModelLowerLuxMultiplier())) { 322 minThresholdRatio = config.getShortTermModelLowerLuxMultiplier(); 323 } 324 if (!Float.isNaN(config.getShortTermModelUpperLuxMultiplier())) { 325 maxThresholdRatio = config.getShortTermModelUpperLuxMultiplier(); 326 } 327 } 328 final float minAmbientLux = 329 shortTermModelAnchor - shortTermModelAnchor * minThresholdRatio; 330 final float maxAmbientLux = 331 shortTermModelAnchor + shortTermModelAnchor * maxThresholdRatio; 332 if (minAmbientLux < ambientLux && ambientLux <= maxAmbientLux) { 333 if (mLoggingEnabled) { 334 Slog.d(TAG, "ShortTermModel: re-validate user data, ambient lux is " 335 + minAmbientLux + " < " + ambientLux + " < " + maxAmbientLux); 336 } 337 return false; 338 } else { 339 Slog.d(TAG, "ShortTermModel: reset data, ambient lux is " + ambientLux 340 + "(" + minAmbientLux + ", " + maxAmbientLux + ")"); 341 return true; 342 } 343 } 344 345 // Normalize entire brightness range to 0 - 1. normalizeAbsoluteBrightness(int brightness)346 protected static float normalizeAbsoluteBrightness(int brightness) { 347 return BrightnessSynchronizer.brightnessIntToFloat(brightness); 348 } 349 insertControlPoint( float[] luxLevels, float[] brightnessLevels, float lux, float brightness)350 private Pair<float[], float[]> insertControlPoint( 351 float[] luxLevels, float[] brightnessLevels, float lux, float brightness) { 352 final int idx = findInsertionPoint(luxLevels, lux); 353 final float[] newLuxLevels; 354 final float[] newBrightnessLevels; 355 if (idx == luxLevels.length) { 356 newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1); 357 newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1); 358 newLuxLevels[idx] = lux; 359 newBrightnessLevels[idx] = brightness; 360 } else if (luxLevels[idx] == lux) { 361 newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length); 362 newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length); 363 newBrightnessLevels[idx] = brightness; 364 } else { 365 newLuxLevels = Arrays.copyOf(luxLevels, luxLevels.length + 1); 366 System.arraycopy(newLuxLevels, idx, newLuxLevels, idx+1, luxLevels.length - idx); 367 newLuxLevels[idx] = lux; 368 newBrightnessLevels = Arrays.copyOf(brightnessLevels, brightnessLevels.length + 1); 369 System.arraycopy(newBrightnessLevels, idx, newBrightnessLevels, idx+1, 370 brightnessLevels.length - idx); 371 newBrightnessLevels[idx] = brightness; 372 } 373 smoothCurve(newLuxLevels, newBrightnessLevels, idx); 374 return Pair.create(newLuxLevels, newBrightnessLevels); 375 } 376 377 /** 378 * Returns the index of the first value that's less than or equal to {@code val}. 379 * 380 * This assumes that {@code arr} is sorted. If all values in {@code arr} are greater 381 * than val, then it will return the length of arr as the insertion point. 382 */ findInsertionPoint(float[] arr, float val)383 private int findInsertionPoint(float[] arr, float val) { 384 for (int i = 0; i < arr.length; i++) { 385 if (val <= arr[i]) { 386 return i; 387 } 388 } 389 return arr.length; 390 } 391 smoothCurve(float[] lux, float[] brightness, int idx)392 private void smoothCurve(float[] lux, float[] brightness, int idx) { 393 if (mLoggingEnabled) { 394 PLOG.logCurve("unsmoothed curve", lux, brightness); 395 } 396 float prevLux = lux[idx]; 397 float prevBrightness = brightness[idx]; 398 // Smooth curve for data points above the newly introduced point 399 for (int i = idx+1; i < lux.length; i++) { 400 float currLux = lux[i]; 401 float currBrightness = brightness[i]; 402 float maxBrightness = MathUtils.max( 403 prevBrightness * permissibleRatio(currLux, prevLux), 404 prevBrightness + MIN_PERMISSABLE_INCREASE); 405 float newBrightness = MathUtils.constrain( 406 currBrightness, prevBrightness, maxBrightness); 407 if (newBrightness == currBrightness) { 408 break; 409 } 410 prevLux = currLux; 411 prevBrightness = newBrightness; 412 brightness[i] = newBrightness; 413 } 414 // Smooth curve for data points below the newly introduced point 415 prevLux = lux[idx]; 416 prevBrightness = brightness[idx]; 417 for (int i = idx-1; i >= 0; i--) { 418 float currLux = lux[i]; 419 float currBrightness = brightness[i]; 420 float minBrightness = prevBrightness * permissibleRatio(currLux, prevLux); 421 float newBrightness = MathUtils.constrain( 422 currBrightness, minBrightness, prevBrightness); 423 if (newBrightness == currBrightness) { 424 break; 425 } 426 prevLux = currLux; 427 prevBrightness = newBrightness; 428 brightness[i] = newBrightness; 429 } 430 if (mLoggingEnabled) { 431 PLOG.logCurve("smoothed curve", lux, brightness); 432 } 433 } 434 permissibleRatio(float currLux, float prevLux)435 private float permissibleRatio(float currLux, float prevLux) { 436 return MathUtils.pow((currLux + LUX_GRAD_SMOOTHING) 437 / (prevLux + LUX_GRAD_SMOOTHING), MAX_GRAD); 438 } 439 inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness, float currentBrightness)440 protected float inferAutoBrightnessAdjustment(float maxGamma, float desiredBrightness, 441 float currentBrightness) { 442 float adjustment = 0; 443 float gamma = Float.NaN; 444 // Extreme edge cases: use a simpler heuristic, as proper gamma correction around the edges 445 // affects the curve rather drastically. 446 if (currentBrightness <= 0.1f || currentBrightness >= 0.9f) { 447 adjustment = (desiredBrightness - currentBrightness); 448 // Edge case: darkest adjustment possible. 449 } else if (desiredBrightness == 0) { 450 adjustment = -1; 451 // Edge case: brightest adjustment possible. 452 } else if (desiredBrightness == 1) { 453 adjustment = +1; 454 } else { 455 // current^gamma = desired => gamma = log[current](desired) 456 gamma = MathUtils.log(desiredBrightness) / MathUtils.log(currentBrightness); 457 // max^-adjustment = gamma => adjustment = -log[max](gamma) 458 adjustment = -MathUtils.log(gamma) / MathUtils.log(maxGamma); 459 } 460 adjustment = MathUtils.constrain(adjustment, -1, +1); 461 if (mLoggingEnabled) { 462 Slog.d(TAG, "inferAutoBrightnessAdjustment: " + maxGamma + "^" + -adjustment + "=" + 463 MathUtils.pow(maxGamma, -adjustment) + " == " + gamma); 464 Slog.d(TAG, "inferAutoBrightnessAdjustment: " + currentBrightness + "^" + gamma + "=" + 465 MathUtils.pow(currentBrightness, gamma) + " == " + desiredBrightness); 466 } 467 return adjustment; 468 } 469 getAdjustedCurve(float[] lux, float[] brightness, float userLux, float userBrightness, float adjustment, float maxGamma)470 protected Pair<float[], float[]> getAdjustedCurve(float[] lux, float[] brightness, 471 float userLux, float userBrightness, float adjustment, float maxGamma) { 472 float[] newLux = lux; 473 float[] newBrightness = Arrays.copyOf(brightness, brightness.length); 474 if (mLoggingEnabled) { 475 PLOG.logCurve("unadjusted curve", newLux, newBrightness); 476 } 477 adjustment = MathUtils.constrain(adjustment, -1, 1); 478 float gamma = MathUtils.pow(maxGamma, -adjustment); 479 if (mLoggingEnabled) { 480 Slog.d(TAG, "getAdjustedCurve: " + maxGamma + "^" + -adjustment + "=" + 481 MathUtils.pow(maxGamma, -adjustment) + " == " + gamma); 482 } 483 if (gamma != 1) { 484 for (int i = 0; i < newBrightness.length; i++) { 485 newBrightness[i] = MathUtils.pow(newBrightness[i], gamma); 486 } 487 } 488 if (mLoggingEnabled) { 489 PLOG.logCurve("gamma adjusted curve", newLux, newBrightness); 490 } 491 if (userLux != -1) { 492 Pair<float[], float[]> curve = insertControlPoint(newLux, newBrightness, userLux, 493 userBrightness); 494 newLux = curve.first; 495 newBrightness = curve.second; 496 if (mLoggingEnabled) { 497 PLOG.logCurve("gamma and user adjusted curve", newLux, newBrightness); 498 // This is done for comparison. 499 curve = insertControlPoint(lux, brightness, userLux, userBrightness); 500 PLOG.logCurve("user adjusted curve", curve.first ,curve.second); 501 } 502 } 503 return Pair.create(newLux, newBrightness); 504 } 505 506 /** 507 * A {@link BrightnessMappingStrategy} that maps from ambient room brightness directly to the 508 * backlight of the display. 509 * 510 * Since we don't have information about the display's physical brightness, any brightness 511 * configurations that are set are just ignored. 512 */ 513 private static class SimpleMappingStrategy extends BrightnessMappingStrategy { 514 // Lux control points 515 private final float[] mLux; 516 // Brightness control points normalized to [0, 1] 517 private final float[] mBrightness; 518 519 private Spline mSpline; 520 private float mMaxGamma; 521 private float mAutoBrightnessAdjustment; 522 private float mUserLux; 523 private float mUserBrightness; 524 private long mShortTermModelTimeout; 525 SimpleMappingStrategy(float[] lux, int[] brightness, float maxGamma, long timeout)526 private SimpleMappingStrategy(float[] lux, int[] brightness, float maxGamma, 527 long timeout) { 528 Preconditions.checkArgument(lux.length != 0 && brightness.length != 0, 529 "Lux and brightness arrays must not be empty!"); 530 Preconditions.checkArgument(lux.length == brightness.length, 531 "Lux and brightness arrays must be the same length!"); 532 Preconditions.checkArrayElementsInRange(lux, 0, Float.MAX_VALUE, "lux"); 533 Preconditions.checkArrayElementsInRange(brightness, 534 0, Integer.MAX_VALUE, "brightness"); 535 536 final int N = brightness.length; 537 mLux = new float[N]; 538 mBrightness = new float[N]; 539 for (int i = 0; i < N; i++) { 540 mLux[i] = lux[i]; 541 mBrightness[i] = normalizeAbsoluteBrightness(brightness[i]); 542 } 543 544 mMaxGamma = maxGamma; 545 mAutoBrightnessAdjustment = 0; 546 mUserLux = -1; 547 mUserBrightness = -1; 548 if (mLoggingEnabled) { 549 PLOG.start("simple mapping strategy"); 550 } 551 computeSpline(); 552 mShortTermModelTimeout = timeout; 553 } 554 555 @Override getShortTermModelTimeout()556 public long getShortTermModelTimeout() { 557 return mShortTermModelTimeout; 558 } 559 560 @Override setBrightnessConfiguration(@ullable BrightnessConfiguration config)561 public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) { 562 return false; 563 } 564 565 @Override getBrightnessConfiguration()566 public BrightnessConfiguration getBrightnessConfiguration() { 567 return null; 568 } 569 570 @Override getBrightness(float lux, String packageName, @ApplicationInfo.Category int category)571 public float getBrightness(float lux, String packageName, 572 @ApplicationInfo.Category int category) { 573 return mSpline.interpolate(lux); 574 } 575 576 @Override getAutoBrightnessAdjustment()577 public float getAutoBrightnessAdjustment() { 578 return mAutoBrightnessAdjustment; 579 } 580 581 @Override setAutoBrightnessAdjustment(float adjustment)582 public boolean setAutoBrightnessAdjustment(float adjustment) { 583 adjustment = MathUtils.constrain(adjustment, -1, 1); 584 if (adjustment == mAutoBrightnessAdjustment) { 585 return false; 586 } 587 if (mLoggingEnabled) { 588 Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " + 589 adjustment); 590 PLOG.start("auto-brightness adjustment"); 591 } 592 mAutoBrightnessAdjustment = adjustment; 593 computeSpline(); 594 return true; 595 } 596 597 @Override convertToNits(float brightness)598 public float convertToNits(float brightness) { 599 return -1.0f; 600 } 601 602 @Override addUserDataPoint(float lux, float brightness)603 public void addUserDataPoint(float lux, float brightness) { 604 float unadjustedBrightness = getUnadjustedBrightness(lux); 605 if (mLoggingEnabled) { 606 Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")"); 607 PLOG.start("add user data point") 608 .logPoint("user data point", lux, brightness) 609 .logPoint("current brightness", lux, unadjustedBrightness); 610 } 611 float adjustment = inferAutoBrightnessAdjustment(mMaxGamma, 612 brightness /* desiredBrightness */, 613 unadjustedBrightness /* currentBrightness */); 614 if (mLoggingEnabled) { 615 Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " + 616 adjustment); 617 } 618 mAutoBrightnessAdjustment = adjustment; 619 mUserLux = lux; 620 mUserBrightness = brightness; 621 computeSpline(); 622 } 623 624 @Override clearUserDataPoints()625 public void clearUserDataPoints() { 626 if (mUserLux != -1) { 627 if (mLoggingEnabled) { 628 Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0"); 629 PLOG.start("clear user data points") 630 .logPoint("user data point", mUserLux, mUserBrightness); 631 } 632 mAutoBrightnessAdjustment = 0; 633 mUserLux = -1; 634 mUserBrightness = -1; 635 computeSpline(); 636 } 637 } 638 639 @Override hasUserDataPoints()640 public boolean hasUserDataPoints() { 641 return mUserLux != -1; 642 } 643 644 @Override isDefaultConfig()645 public boolean isDefaultConfig() { 646 return true; 647 } 648 649 @Override getDefaultConfig()650 public BrightnessConfiguration getDefaultConfig() { 651 return null; 652 } 653 654 @Override recalculateSplines(boolean applyAdjustment, float[] adjustment)655 public void recalculateSplines(boolean applyAdjustment, float[] adjustment) { 656 // Do nothing. 657 } 658 659 @Override dump(PrintWriter pw)660 public void dump(PrintWriter pw) { 661 pw.println("SimpleMappingStrategy"); 662 pw.println(" mSpline=" + mSpline); 663 pw.println(" mMaxGamma=" + mMaxGamma); 664 pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); 665 pw.println(" mUserLux=" + mUserLux); 666 pw.println(" mUserBrightness=" + mUserBrightness); 667 } 668 computeSpline()669 private void computeSpline() { 670 Pair<float[], float[]> curve = getAdjustedCurve(mLux, mBrightness, mUserLux, 671 mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma); 672 mSpline = Spline.createSpline(curve.first, curve.second); 673 } 674 getUnadjustedBrightness(float lux)675 private float getUnadjustedBrightness(float lux) { 676 Spline spline = Spline.createSpline(mLux, mBrightness); 677 return spline.interpolate(lux); 678 } 679 } 680 681 /** A {@link BrightnessMappingStrategy} that maps from ambient room brightness to the physical 682 * range of the display, rather than to the range of the backlight control (typically 0-255). 683 * 684 * By mapping through the physical brightness, the curve becomes portable across devices and 685 * gives us more resolution in the resulting mapping. 686 */ 687 @VisibleForTesting 688 static class PhysicalMappingStrategy extends BrightnessMappingStrategy { 689 // The current brightness configuration. 690 private BrightnessConfiguration mConfig; 691 692 // A spline mapping from the current ambient light in lux to the desired display brightness 693 // in nits. 694 private Spline mBrightnessSpline; 695 696 // A spline mapping from nits to the corresponding brightness value, normalized to the range 697 // [0, 1.0]. 698 private Spline mNitsToBrightnessSpline; 699 700 // A spline mapping from the system brightness value, normalized to the range [0, 1.0], to 701 // a brightness in nits. 702 private Spline mBrightnessToNitsSpline; 703 704 // The default brightness configuration. 705 private final BrightnessConfiguration mDefaultConfig; 706 707 private final float[] mNits; 708 private final float[] mBrightness; 709 710 private boolean mBrightnessRangeAdjustmentApplied; 711 712 private final float mMaxGamma; 713 private float mAutoBrightnessAdjustment; 714 private float mUserLux; 715 private float mUserBrightness; 716 PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits, float[] brightness, float maxGamma)717 public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits, 718 float[] brightness, float maxGamma) { 719 720 Preconditions.checkArgument(nits.length != 0 && brightness.length != 0, 721 "Nits and brightness arrays must not be empty!"); 722 723 Preconditions.checkArgument(nits.length == brightness.length, 724 "Nits and brightness arrays must be the same length!"); 725 Objects.requireNonNull(config); 726 Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits"); 727 Preconditions.checkArrayElementsInRange(brightness, 728 PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX, "brightness"); 729 730 mMaxGamma = maxGamma; 731 mAutoBrightnessAdjustment = 0; 732 mUserLux = -1; 733 mUserBrightness = -1; 734 735 mNits = nits; 736 mBrightness = brightness; 737 computeNitsBrightnessSplines(mNits); 738 739 mDefaultConfig = config; 740 if (mLoggingEnabled) { 741 PLOG.start("physical mapping strategy"); 742 } 743 mConfig = config; 744 computeSpline(); 745 } 746 747 @Override getShortTermModelTimeout()748 public long getShortTermModelTimeout() { 749 if (mConfig.getShortTermModelTimeoutMillis() >= 0) { 750 return mConfig.getShortTermModelTimeoutMillis(); 751 } else { 752 return mDefaultConfig.getShortTermModelTimeoutMillis(); 753 } 754 } 755 756 @Override setBrightnessConfiguration(@ullable BrightnessConfiguration config)757 public boolean setBrightnessConfiguration(@Nullable BrightnessConfiguration config) { 758 if (config == null) { 759 config = mDefaultConfig; 760 } 761 if (config.equals(mConfig)) { 762 return false; 763 } 764 if (mLoggingEnabled) { 765 PLOG.start("brightness configuration"); 766 } 767 mConfig = config; 768 computeSpline(); 769 return true; 770 } 771 772 @Override getBrightnessConfiguration()773 public BrightnessConfiguration getBrightnessConfiguration() { 774 return mConfig; 775 } 776 777 @Override getBrightness(float lux, String packageName, @ApplicationInfo.Category int category)778 public float getBrightness(float lux, String packageName, 779 @ApplicationInfo.Category int category) { 780 float nits = mBrightnessSpline.interpolate(lux); 781 float brightness = mNitsToBrightnessSpline.interpolate(nits); 782 // Correct the brightness according to the current application and its category, but 783 // only if no user data point is set (as this will override the user setting). 784 if (mUserLux == -1) { 785 brightness = correctBrightness(brightness, packageName, category); 786 } else if (mLoggingEnabled) { 787 Slog.d(TAG, "user point set, correction not applied"); 788 } 789 return brightness; 790 } 791 792 @Override getAutoBrightnessAdjustment()793 public float getAutoBrightnessAdjustment() { 794 return mAutoBrightnessAdjustment; 795 } 796 797 @Override setAutoBrightnessAdjustment(float adjustment)798 public boolean setAutoBrightnessAdjustment(float adjustment) { 799 adjustment = MathUtils.constrain(adjustment, -1, 1); 800 if (adjustment == mAutoBrightnessAdjustment) { 801 return false; 802 } 803 if (mLoggingEnabled) { 804 Slog.d(TAG, "setAutoBrightnessAdjustment: " + mAutoBrightnessAdjustment + " => " + 805 adjustment); 806 PLOG.start("auto-brightness adjustment"); 807 } 808 mAutoBrightnessAdjustment = adjustment; 809 computeSpline(); 810 return true; 811 } 812 813 @Override convertToNits(float brightness)814 public float convertToNits(float brightness) { 815 return mBrightnessToNitsSpline.interpolate(brightness); 816 } 817 818 @Override addUserDataPoint(float lux, float brightness)819 public void addUserDataPoint(float lux, float brightness) { 820 float unadjustedBrightness = getUnadjustedBrightness(lux); 821 if (mLoggingEnabled) { 822 Slog.d(TAG, "addUserDataPoint: (" + lux + "," + brightness + ")"); 823 PLOG.start("add user data point") 824 .logPoint("user data point", lux, brightness) 825 .logPoint("current brightness", lux, unadjustedBrightness); 826 } 827 float adjustment = inferAutoBrightnessAdjustment(mMaxGamma, 828 brightness /* desiredBrightness */, 829 unadjustedBrightness /* currentBrightness */); 830 if (mLoggingEnabled) { 831 Slog.d(TAG, "addUserDataPoint: " + mAutoBrightnessAdjustment + " => " + 832 adjustment); 833 } 834 mAutoBrightnessAdjustment = adjustment; 835 mUserLux = lux; 836 mUserBrightness = brightness; 837 computeSpline(); 838 } 839 840 @Override clearUserDataPoints()841 public void clearUserDataPoints() { 842 if (mUserLux != -1) { 843 if (mLoggingEnabled) { 844 Slog.d(TAG, "clearUserDataPoints: " + mAutoBrightnessAdjustment + " => 0"); 845 PLOG.start("clear user data points") 846 .logPoint("user data point", mUserLux, mUserBrightness); 847 } 848 mAutoBrightnessAdjustment = 0; 849 mUserLux = -1; 850 mUserBrightness = -1; 851 computeSpline(); 852 } 853 } 854 855 @Override hasUserDataPoints()856 public boolean hasUserDataPoints() { 857 return mUserLux != -1; 858 } 859 860 @Override isDefaultConfig()861 public boolean isDefaultConfig() { 862 return mDefaultConfig.equals(mConfig); 863 } 864 865 @Override getDefaultConfig()866 public BrightnessConfiguration getDefaultConfig() { 867 return mDefaultConfig; 868 } 869 870 @Override recalculateSplines(boolean applyAdjustment, float[] adjustedNits)871 public void recalculateSplines(boolean applyAdjustment, float[] adjustedNits) { 872 mBrightnessRangeAdjustmentApplied = applyAdjustment; 873 computeNitsBrightnessSplines(mBrightnessRangeAdjustmentApplied ? adjustedNits : mNits); 874 } 875 876 @Override dump(PrintWriter pw)877 public void dump(PrintWriter pw) { 878 pw.println("PhysicalMappingStrategy"); 879 pw.println(" mConfig=" + mConfig); 880 pw.println(" mBrightnessSpline=" + mBrightnessSpline); 881 pw.println(" mNitsToBrightnessSpline=" + mNitsToBrightnessSpline); 882 pw.println(" mBrightnessToNitsSpline=" + mBrightnessToNitsSpline); 883 pw.println(" mMaxGamma=" + mMaxGamma); 884 pw.println(" mAutoBrightnessAdjustment=" + mAutoBrightnessAdjustment); 885 pw.println(" mUserLux=" + mUserLux); 886 pw.println(" mUserBrightness=" + mUserBrightness); 887 pw.println(" mDefaultConfig=" + mDefaultConfig); 888 pw.println(" mBrightnessRangeAdjustmentApplied=" + mBrightnessRangeAdjustmentApplied); 889 } 890 computeNitsBrightnessSplines(float[] nits)891 private void computeNitsBrightnessSplines(float[] nits) { 892 mNitsToBrightnessSpline = Spline.createSpline(nits, mBrightness); 893 mBrightnessToNitsSpline = Spline.createSpline(mBrightness, nits); 894 } 895 computeSpline()896 private void computeSpline() { 897 Pair<float[], float[]> defaultCurve = mConfig.getCurve(); 898 float[] defaultLux = defaultCurve.first; 899 float[] defaultNits = defaultCurve.second; 900 float[] defaultBrightness = new float[defaultNits.length]; 901 for (int i = 0; i < defaultBrightness.length; i++) { 902 defaultBrightness[i] = mNitsToBrightnessSpline.interpolate(defaultNits[i]); 903 } 904 Pair<float[], float[]> curve = getAdjustedCurve(defaultLux, defaultBrightness, mUserLux, 905 mUserBrightness, mAutoBrightnessAdjustment, mMaxGamma); 906 float[] lux = curve.first; 907 float[] brightness = curve.second; 908 float[] nits = new float[brightness.length]; 909 for (int i = 0; i < nits.length; i++) { 910 nits[i] = mBrightnessToNitsSpline.interpolate(brightness[i]); 911 } 912 mBrightnessSpline = Spline.createSpline(lux, nits); 913 } 914 getUnadjustedBrightness(float lux)915 private float getUnadjustedBrightness(float lux) { 916 Pair<float[], float[]> curve = mConfig.getCurve(); 917 Spline spline = Spline.createSpline(curve.first, curve.second); 918 return mNitsToBrightnessSpline.interpolate(spline.interpolate(lux)); 919 } 920 correctBrightness(float brightness, String packageName, int category)921 private float correctBrightness(float brightness, String packageName, int category) { 922 if (packageName != null) { 923 BrightnessCorrection correction = mConfig.getCorrectionByPackageName(packageName); 924 if (correction != null) { 925 return correction.apply(brightness); 926 } 927 } 928 if (category != ApplicationInfo.CATEGORY_UNDEFINED) { 929 BrightnessCorrection correction = mConfig.getCorrectionByCategory(category); 930 if (correction != null) { 931 return correction.apply(brightness); 932 } 933 } 934 return brightness; 935 } 936 } 937 } 938