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