1 /* 2 * Copyright (C) 2016 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.config; 18 19 import android.annotation.ArrayRes; 20 import android.annotation.Nullable; 21 import android.content.res.Resources; 22 import android.util.Pair; 23 import android.util.Slog; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.server.display.utils.DebugUtils; 27 28 import java.util.Arrays; 29 import java.util.List; 30 31 /** 32 * A helper class for handling access to illuminance hysteresis level values. 33 */ 34 public class HysteresisLevels { 35 private static final String TAG = "HysteresisLevels"; 36 37 private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f}; 38 private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f}; 39 private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f}; 40 private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f}; 41 private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f}; 42 private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f}; 43 44 // To enable these logs, run: 45 // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot' 46 private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); 47 48 /** 49 * The array that describes the brightness threshold percentage change 50 * at each brightness level described in mBrighteningThresholdLevels. 51 */ 52 private final float[] mBrighteningThresholdsPercentages; 53 54 /** 55 * The array that describes the brightness threshold percentage change 56 * at each brightness level described in mDarkeningThresholdLevels. 57 */ 58 private final float[] mDarkeningThresholdsPercentages; 59 60 /** 61 * The array that describes the range of brightness that each threshold percentage applies to 62 * 63 * The (zero-based) index is calculated as follows 64 * value = current brightness value 65 * level = mBrighteningThresholdLevels 66 * 67 * condition return 68 * value < mBrighteningThresholdLevels[0] = 0.0f 69 * level[n] <= value < level[n+1] = mBrighteningThresholdsPercentages[n] 70 * level[MAX] <= value = mBrighteningThresholdsPercentages[MAX] 71 */ 72 private final float[] mBrighteningThresholdLevels; 73 74 /** 75 * The array that describes the range of brightness that each threshold percentage applies to 76 * 77 * The (zero-based) index is calculated as follows 78 * value = current brightness value 79 * level = mDarkeningThresholdLevels 80 * 81 * condition return 82 * value < level[0] = 0.0f 83 * level[n] <= value < level[n+1] = mDarkeningThresholdsPercentages[n] 84 * level[MAX] <= value = mDarkeningThresholdsPercentages[MAX] 85 */ 86 private final float[] mDarkeningThresholdLevels; 87 88 /** 89 * The minimum value decrease for darkening event 90 */ 91 private final float mMinDarkening; 92 93 /** 94 * The minimum value increase for brightening event. 95 */ 96 private final float mMinBrightening; 97 98 /** 99 * Creates a {@code HysteresisLevels} object with the given equal-length 100 * float arrays. 101 * 102 * @param brighteningThresholdsPercentages 0-100 of thresholds 103 * @param darkeningThresholdsPercentages 0-100 of thresholds 104 * @param brighteningThresholdLevels float array of brightness values in the relevant 105 * units 106 * @param minBrighteningThreshold the minimum value for which the brightening value 107 * needs to 108 * return. 109 * @param minDarkeningThreshold the minimum value for which the darkening value needs 110 * to return. 111 */ 112 @VisibleForTesting HysteresisLevels(float[] brighteningThresholdsPercentages, float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, float[] darkeningThresholdLevels, float minDarkeningThreshold, float minBrighteningThreshold)113 public HysteresisLevels(float[] brighteningThresholdsPercentages, 114 float[] darkeningThresholdsPercentages, 115 float[] brighteningThresholdLevels, float[] darkeningThresholdLevels, 116 float minDarkeningThreshold, float minBrighteningThreshold) { 117 if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length 118 || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) { 119 throw new IllegalArgumentException("Mismatch between hysteresis array lengths."); 120 } 121 mBrighteningThresholdsPercentages = 122 setArrayFormat(brighteningThresholdsPercentages, 100.0f); 123 mDarkeningThresholdsPercentages = 124 setArrayFormat(darkeningThresholdsPercentages, 100.0f); 125 mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f); 126 mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f); 127 mMinDarkening = minDarkeningThreshold; 128 mMinBrightening = minBrighteningThreshold; 129 } 130 131 /** 132 * Return the brightening hysteresis threshold for the given value level. 133 */ getBrighteningThreshold(float value)134 public float getBrighteningThreshold(float value) { 135 final float brightConstant = getReferenceLevel(value, 136 mBrighteningThresholdLevels, mBrighteningThresholdsPercentages); 137 138 float brightThreshold = value * (1.0f + brightConstant); 139 if (DEBUG) { 140 Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold=" 141 + brightThreshold + ", value=" + value); 142 } 143 144 brightThreshold = Math.max(brightThreshold, value + mMinBrightening); 145 return brightThreshold; 146 } 147 148 /** 149 * Return the darkening hysteresis threshold for the given value level. 150 */ getDarkeningThreshold(float value)151 public float getDarkeningThreshold(float value) { 152 final float darkConstant = getReferenceLevel(value, 153 mDarkeningThresholdLevels, mDarkeningThresholdsPercentages); 154 float darkThreshold = value * (1.0f - darkConstant); 155 if (DEBUG) { 156 Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold=" 157 + darkThreshold + ", value=" + value); 158 } 159 darkThreshold = Math.min(darkThreshold, value - mMinDarkening); 160 return Math.max(darkThreshold, 0.0f); 161 } 162 163 @VisibleForTesting getBrighteningThresholdsPercentages()164 public float[] getBrighteningThresholdsPercentages() { 165 return mBrighteningThresholdsPercentages; 166 } 167 168 @VisibleForTesting getDarkeningThresholdsPercentages()169 public float[] getDarkeningThresholdsPercentages() { 170 return mDarkeningThresholdsPercentages; 171 } 172 173 @VisibleForTesting getBrighteningThresholdLevels()174 public float[] getBrighteningThresholdLevels() { 175 return mBrighteningThresholdLevels; 176 } 177 178 @VisibleForTesting getDarkeningThresholdLevels()179 public float[] getDarkeningThresholdLevels() { 180 return mDarkeningThresholdLevels; 181 } 182 183 @VisibleForTesting getMinDarkening()184 public float getMinDarkening() { 185 return mMinDarkening; 186 } 187 188 @VisibleForTesting getMinBrightening()189 public float getMinBrightening() { 190 return mMinBrightening; 191 } 192 193 /** 194 * Return the hysteresis constant for the closest threshold value from the given array. 195 */ getReferenceLevel(float value, float[] thresholdLevels, float[] thresholdPercentages)196 private float getReferenceLevel(float value, float[] thresholdLevels, 197 float[] thresholdPercentages) { 198 if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) { 199 return 0.0f; 200 } 201 int index = 0; 202 while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) { 203 index++; 204 } 205 return thresholdPercentages[index]; 206 } 207 208 /** 209 * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}. 210 */ setArrayFormat(float[] configArray, float divideFactor)211 private float[] setArrayFormat(float[] configArray, float divideFactor) { 212 float[] levelArray = new float[configArray.length]; 213 for (int index = 0; levelArray.length > index; ++index) { 214 levelArray[index] = configArray[index] / divideFactor; 215 } 216 return levelArray; 217 } 218 219 @Override toString()220 public String toString() { 221 return "HysteresisLevels {" 222 + "\n" 223 + " mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels) 224 + ",\n" 225 + " mBrighteningThresholdsPercentages=" 226 + Arrays.toString(mBrighteningThresholdsPercentages) 227 + ",\n" 228 + " mMinBrightening=" + mMinBrightening 229 + ",\n" 230 + " mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels) 231 + ",\n" 232 + " mDarkeningThresholdsPercentages=" 233 + Arrays.toString(mDarkeningThresholdsPercentages) 234 + ",\n" 235 + " mMinDarkening=" + mMinDarkening 236 + "\n" 237 + "}"; 238 } 239 240 /** 241 * Creates hysteresis levels for Active Ambient Lux 242 */ loadAmbientBrightnessConfig( @ullable DisplayConfiguration config, @Nullable Resources resources)243 public static HysteresisLevels loadAmbientBrightnessConfig( 244 @Nullable DisplayConfiguration config, @Nullable Resources resources) { 245 return createHysteresisLevels( 246 config == null ? null : config.getAmbientBrightnessChangeThresholds(), 247 com.android.internal.R.array.config_ambientThresholdLevels, 248 com.android.internal.R.array.config_ambientBrighteningThresholds, 249 com.android.internal.R.array.config_ambientDarkeningThresholds, 250 DEFAULT_AMBIENT_THRESHOLD_LEVELS, 251 DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS, 252 DEFAULT_AMBIENT_DARKENING_THRESHOLDS, 253 resources, /* potentialOldBrightnessScale= */ false); 254 } 255 256 /** 257 * Creates hysteresis levels for Active Screen Brightness 258 */ loadDisplayBrightnessConfig( @ullable DisplayConfiguration config, @Nullable Resources resources)259 public static HysteresisLevels loadDisplayBrightnessConfig( 260 @Nullable DisplayConfiguration config, @Nullable Resources resources) { 261 return createHysteresisLevels( 262 config == null ? null : config.getDisplayBrightnessChangeThresholds(), 263 com.android.internal.R.array.config_screenThresholdLevels, 264 com.android.internal.R.array.config_screenBrighteningThresholds, 265 com.android.internal.R.array.config_screenDarkeningThresholds, 266 DEFAULT_SCREEN_THRESHOLD_LEVELS, 267 DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS, 268 DEFAULT_SCREEN_DARKENING_THRESHOLDS, 269 resources, /* potentialOldBrightnessScale= */ true); 270 } 271 272 /** 273 * Creates hysteresis levels for Idle Ambient Lux 274 */ loadAmbientBrightnessIdleConfig( @ullable DisplayConfiguration config, @Nullable Resources resources)275 public static HysteresisLevels loadAmbientBrightnessIdleConfig( 276 @Nullable DisplayConfiguration config, @Nullable Resources resources) { 277 return createHysteresisLevels( 278 config == null ? null : config.getAmbientBrightnessChangeThresholdsIdle(), 279 com.android.internal.R.array.config_ambientThresholdLevels, 280 com.android.internal.R.array.config_ambientBrighteningThresholds, 281 com.android.internal.R.array.config_ambientDarkeningThresholds, 282 DEFAULT_AMBIENT_THRESHOLD_LEVELS, 283 DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS, 284 DEFAULT_AMBIENT_DARKENING_THRESHOLDS, 285 resources, /* potentialOldBrightnessScale= */ false); 286 } 287 288 /** 289 * Creates hysteresis levels for Idle Screen Brightness 290 */ loadDisplayBrightnessIdleConfig( @ullable DisplayConfiguration config, @Nullable Resources resources)291 public static HysteresisLevels loadDisplayBrightnessIdleConfig( 292 @Nullable DisplayConfiguration config, @Nullable Resources resources) { 293 return createHysteresisLevels( 294 config == null ? null : config.getDisplayBrightnessChangeThresholdsIdle(), 295 com.android.internal.R.array.config_screenThresholdLevels, 296 com.android.internal.R.array.config_screenBrighteningThresholds, 297 com.android.internal.R.array.config_screenDarkeningThresholds, 298 DEFAULT_SCREEN_THRESHOLD_LEVELS, 299 DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS, 300 DEFAULT_SCREEN_DARKENING_THRESHOLDS, 301 resources, /* potentialOldBrightnessScale= */ true); 302 } 303 304 createHysteresisLevels( @ullable Thresholds thresholds, @ArrayRes int configLevels, @ArrayRes int configBrighteningThresholds, @ArrayRes int configDarkeningThresholds, float[] defaultLevels, float[] defaultBrighteningThresholds, float[] defaultDarkeningThresholds, @Nullable Resources resources, boolean potentialOldBrightnessScale )305 private static HysteresisLevels createHysteresisLevels( 306 @Nullable Thresholds thresholds, 307 @ArrayRes int configLevels, 308 @ArrayRes int configBrighteningThresholds, 309 @ArrayRes int configDarkeningThresholds, 310 float[] defaultLevels, 311 float[] defaultBrighteningThresholds, 312 float[] defaultDarkeningThresholds, 313 @Nullable Resources resources, 314 boolean potentialOldBrightnessScale 315 ) { 316 BrightnessThresholds brighteningThresholds = 317 thresholds == null ? null : thresholds.getBrighteningThresholds(); 318 BrightnessThresholds darkeningThresholds = 319 thresholds == null ? null : thresholds.getDarkeningThresholds(); 320 321 Pair<float[], float[]> brighteningPair = getBrightnessLevelAndPercentage( 322 brighteningThresholds, 323 configLevels, configBrighteningThresholds, 324 defaultLevels, defaultBrighteningThresholds, 325 potentialOldBrightnessScale, resources); 326 327 Pair<float[], float[]> darkeningPair = getBrightnessLevelAndPercentage( 328 darkeningThresholds, 329 configLevels, configDarkeningThresholds, 330 defaultLevels, defaultDarkeningThresholds, 331 potentialOldBrightnessScale, resources); 332 333 float brighteningMinThreshold = 334 brighteningThresholds != null && brighteningThresholds.getMinimum() != null 335 ? brighteningThresholds.getMinimum().floatValue() : 0f; 336 float darkeningMinThreshold = 337 darkeningThresholds != null && darkeningThresholds.getMinimum() != null 338 ? darkeningThresholds.getMinimum().floatValue() : 0f; 339 340 return new HysteresisLevels( 341 brighteningPair.second, 342 darkeningPair.second, 343 brighteningPair.first, 344 darkeningPair.first, 345 darkeningMinThreshold, 346 brighteningMinThreshold 347 ); 348 } 349 350 // Returns two float arrays, one of the brightness levels and one of the corresponding threshold 351 // percentages for brightness levels at or above the lux value. 352 // Historically, config.xml would have an array for brightness levels that was 1 shorter than 353 // the levels array. Now we prepend a 0 to this array so they can be treated the same in the 354 // rest of the framework. Values were also defined in different units (permille vs percent). getBrightnessLevelAndPercentage( @ullable BrightnessThresholds thresholds, int configFallbackThreshold, int configFallbackPermille, float[] defaultLevels, float[] defaultPercentage, boolean potentialOldBrightnessScale, @Nullable Resources resources)355 private static Pair<float[], float[]> getBrightnessLevelAndPercentage( 356 @Nullable BrightnessThresholds thresholds, 357 int configFallbackThreshold, int configFallbackPermille, 358 float[] defaultLevels, float[] defaultPercentage, boolean potentialOldBrightnessScale, 359 @Nullable Resources resources) { 360 if (thresholds != null 361 && thresholds.getBrightnessThresholdPoints() != null 362 && !thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint() 363 .isEmpty()) { 364 365 // The level and percentages arrays are equal length in the ddc (new system) 366 List<ThresholdPoint> points = 367 thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint(); 368 final int size = points.size(); 369 370 float[] thresholdLevels = new float[size]; 371 float[] thresholdPercentages = new float[size]; 372 373 int i = 0; 374 for (ThresholdPoint point : points) { 375 thresholdLevels[i] = point.getThreshold().floatValue(); 376 thresholdPercentages[i] = point.getPercentage().floatValue(); 377 i++; 378 } 379 return new Pair<>(thresholdLevels, thresholdPercentages); 380 } else if (resources != null) { 381 // The level and percentages arrays are unequal length in config.xml (old system) 382 // We prefix the array with a 0 value to ensure they can be handled consistently 383 // with the new system. 384 385 // Load levels array 386 int[] configThresholdArray = resources.getIntArray(configFallbackThreshold); 387 int configThresholdsSize; 388 // null check is not needed here, however it test we are mocking resources that might 389 // return null 390 if (configThresholdArray == null || configThresholdArray.length == 0) { 391 configThresholdsSize = 1; 392 } else { 393 configThresholdsSize = configThresholdArray.length + 1; 394 } 395 396 // Load percentage array 397 int[] configPermille = resources.getIntArray(configFallbackPermille); 398 399 // Ensure lengths match up 400 // null check is not needed here, however it test we are mocking resources that might 401 // return null 402 boolean emptyArray = configPermille == null || configPermille.length == 0; 403 if (emptyArray && configThresholdsSize == 1) { 404 return new Pair<>(defaultLevels, defaultPercentage); 405 } 406 if (emptyArray || configPermille.length != configThresholdsSize) { 407 throw new IllegalArgumentException( 408 "Brightness threshold arrays do not align in length"); 409 } 410 411 // Calculate levels array 412 float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize]; 413 // Start at 1, so that 0 index value is 0.0f (default) 414 for (int i = 1; i < configThresholdsSize; i++) { 415 configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1]; 416 } 417 if (potentialOldBrightnessScale) { 418 configThresholdWithZeroPrefixed = 419 constraintInRangeIfNeeded(configThresholdWithZeroPrefixed); 420 } 421 422 // Calculate percentages array 423 float[] configPercentage = new float[configThresholdsSize]; 424 for (int i = 0; i < configPermille.length; i++) { 425 configPercentage[i] = configPermille[i] / 10.0f; 426 } 427 return new Pair<>(configThresholdWithZeroPrefixed, configPercentage); 428 } else { 429 return new Pair<>(defaultLevels, defaultPercentage); 430 } 431 } 432 433 /** 434 * This check is due to historical reasons, where screen thresholdLevels used to be 435 * integer values in the range of [0-255], but then was changed to be float values from [0,1]. 436 * To accommodate both the possibilities, we first check if all the thresholdLevels are in 437 * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale. 438 */ constraintInRangeIfNeeded(float[] thresholdLevels)439 private static float[] constraintInRangeIfNeeded(float[] thresholdLevels) { 440 if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f, 441 /* maxValueInclusive= */ 1.0f)) { 442 return thresholdLevels; 443 } 444 445 Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale"); 446 float[] thresholdLevelsScaled = new float[thresholdLevels.length]; 447 for (int index = 0; thresholdLevels.length > index; ++index) { 448 thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f; 449 } 450 return thresholdLevelsScaled; 451 } 452 isAllInRange(float[] configArray, float minValueInclusive, float maxValueInclusive)453 private static boolean isAllInRange(float[] configArray, float minValueInclusive, 454 float maxValueInclusive) { 455 for (float v : configArray) { 456 if (v < minValueInclusive || v > maxValueInclusive) { 457 return false; 458 } 459 } 460 return true; 461 } 462 463 } 464