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 android.hardware.display; 18 19 import android.annotation.FloatRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.SystemApi; 23 import android.content.pm.ApplicationInfo; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.util.Pair; 27 import android.util.TypedXmlPullParser; 28 import android.util.TypedXmlSerializer; 29 30 import com.android.internal.util.Preconditions; 31 import com.android.internal.util.XmlUtils; 32 33 import org.xmlpull.v1.XmlPullParserException; 34 35 import java.io.IOException; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Map.Entry; 42 import java.util.Objects; 43 44 /** @hide */ 45 @SystemApi 46 public final class BrightnessConfiguration implements Parcelable { 47 private static final String TAG_BRIGHTNESS_CURVE = "brightness-curve"; 48 private static final String TAG_BRIGHTNESS_POINT = "brightness-point"; 49 private static final String TAG_BRIGHTNESS_CORRECTIONS = "brightness-corrections"; 50 private static final String TAG_BRIGHTNESS_CORRECTION = "brightness-correction"; 51 private static final String TAG_BRIGHTNESS_PARAMS = "brightness-params"; 52 private static final String ATTR_LUX = "lux"; 53 private static final String ATTR_NITS = "nits"; 54 private static final String ATTR_DESCRIPTION = "description"; 55 private static final String ATTR_PACKAGE_NAME = "package-name"; 56 private static final String ATTR_CATEGORY = "category"; 57 private static final String ATTR_COLLECT_COLOR = "collect-color"; 58 private static final String ATTR_MODEL_TIMEOUT = "model-timeout"; 59 private static final String ATTR_MODEL_LOWER_BOUND = "model-lower-bound"; 60 private static final String ATTR_MODEL_UPPER_BOUND = "model-upper-bound"; 61 /** 62 * Returned from {@link #getShortTermModelTimeoutMillis()} if no timeout has been set. 63 * In this case the device will use the default timeout available in the 64 * {@link BrightnessConfiguration} returned from 65 * {@link DisplayManager#getDefaultBrightnessConfiguration()}. 66 */ 67 public static final long SHORT_TERM_TIMEOUT_UNSET = -1; 68 69 private final float[] mLux; 70 private final float[] mNits; 71 private final Map<String, BrightnessCorrection> mCorrectionsByPackageName; 72 private final Map<Integer, BrightnessCorrection> mCorrectionsByCategory; 73 private final String mDescription; 74 private final boolean mShouldCollectColorSamples; 75 private final long mShortTermModelTimeout; 76 private final float mShortTermModelLowerLuxMultiplier; 77 private final float mShortTermModelUpperLuxMultiplier; 78 BrightnessConfiguration(float[] lux, float[] nits, Map<String, BrightnessCorrection> correctionsByPackageName, Map<Integer, BrightnessCorrection> correctionsByCategory, String description, boolean shouldCollectColorSamples, long shortTermModelTimeout, float shortTermModelLowerLuxMultiplier, float shortTermModelUpperLuxMultiplier)79 private BrightnessConfiguration(float[] lux, float[] nits, 80 Map<String, BrightnessCorrection> correctionsByPackageName, 81 Map<Integer, BrightnessCorrection> correctionsByCategory, String description, 82 boolean shouldCollectColorSamples, 83 long shortTermModelTimeout, 84 float shortTermModelLowerLuxMultiplier, 85 float shortTermModelUpperLuxMultiplier) { 86 mLux = lux; 87 mNits = nits; 88 mCorrectionsByPackageName = correctionsByPackageName; 89 mCorrectionsByCategory = correctionsByCategory; 90 mDescription = description; 91 mShouldCollectColorSamples = shouldCollectColorSamples; 92 mShortTermModelTimeout = shortTermModelTimeout; 93 mShortTermModelLowerLuxMultiplier = shortTermModelLowerLuxMultiplier; 94 mShortTermModelUpperLuxMultiplier = shortTermModelUpperLuxMultiplier; 95 } 96 97 /** 98 * Gets the base brightness as curve. 99 * 100 * The curve is returned as a pair of float arrays, the first representing all of the lux 101 * points of the brightness curve and the second representing all of the nits values of the 102 * brightness curve. 103 * 104 * @return the control points for the brightness curve. 105 */ getCurve()106 public Pair<float[], float[]> getCurve() { 107 return Pair.create(Arrays.copyOf(mLux, mLux.length), Arrays.copyOf(mNits, mNits.length)); 108 } 109 110 /** 111 * Returns a brightness correction by app, or null. 112 * 113 * @param packageName 114 * The app's package name. 115 * 116 * @return The matching brightness correction, or null. 117 * 118 */ 119 @Nullable getCorrectionByPackageName(@onNull String packageName)120 public BrightnessCorrection getCorrectionByPackageName(@NonNull String packageName) { 121 return mCorrectionsByPackageName.get(packageName); 122 } 123 124 /** 125 * Returns a brightness correction by app category, or null. 126 * 127 * @param category 128 * The app category. 129 * 130 * @return The matching brightness correction, or null. 131 */ 132 @Nullable getCorrectionByCategory(@pplicationInfo.Category int category)133 public BrightnessCorrection getCorrectionByCategory(@ApplicationInfo.Category int category) { 134 return mCorrectionsByCategory.get(category); 135 } 136 137 /** 138 * Returns description string. 139 * @hide 140 */ getDescription()141 public String getDescription() { 142 return mDescription; 143 } 144 145 /** 146 * Returns whether color samples should be collected in 147 * {@link BrightnessChangeEvent#colorValueBuckets}. 148 */ shouldCollectColorSamples()149 public boolean shouldCollectColorSamples() { 150 return mShouldCollectColorSamples; 151 } 152 153 /** 154 * Returns the timeout for the short term model in milliseconds. 155 * 156 * If the screen is inactive for this timeout then the short term model 157 * will check the lux range defined by {@link #getShortTermModelLowerLuxMultiplier()} and 158 * {@link #getShortTermModelUpperLuxMultiplier()} to decide whether to keep any adjustment 159 * the user has made to adaptive brightness. 160 */ getShortTermModelTimeoutMillis()161 public long getShortTermModelTimeoutMillis() { 162 return mShortTermModelTimeout; 163 } 164 165 /** 166 * Returns the multiplier used to calculate the upper bound for which 167 * a users adaptive brightness is considered valid. 168 * 169 * For example if a user changes the brightness when the ambient light level 170 * is 100 lux, the adjustment will be kept if the current ambient light level 171 * is {@code <= 100 + (100 * getShortTermModelUpperLuxMultiplier())}. 172 */ getShortTermModelUpperLuxMultiplier()173 public float getShortTermModelUpperLuxMultiplier() { 174 return mShortTermModelUpperLuxMultiplier; 175 } 176 177 /** 178 * Returns the multiplier used to calculate the lower bound for which 179 * a users adaptive brightness is considered valid. 180 * 181 * For example if a user changes the brightness when the ambient light level 182 * is 100 lux, the adjustment will be kept if the current ambient light level 183 * is {@code >= 100 - (100 * getShortTermModelLowerLuxMultiplier())}. 184 */ getShortTermModelLowerLuxMultiplier()185 public float getShortTermModelLowerLuxMultiplier() { 186 return mShortTermModelLowerLuxMultiplier; 187 } 188 189 @Override writeToParcel(Parcel dest, int flags)190 public void writeToParcel(Parcel dest, int flags) { 191 dest.writeFloatArray(mLux); 192 dest.writeFloatArray(mNits); 193 dest.writeInt(mCorrectionsByPackageName.size()); 194 for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) { 195 final String packageName = entry.getKey(); 196 final BrightnessCorrection correction = entry.getValue(); 197 dest.writeString(packageName); 198 correction.writeToParcel(dest, flags); 199 } 200 dest.writeInt(mCorrectionsByCategory.size()); 201 for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) { 202 final int category = entry.getKey(); 203 final BrightnessCorrection correction = entry.getValue(); 204 dest.writeInt(category); 205 correction.writeToParcel(dest, flags); 206 } 207 dest.writeString(mDescription); 208 dest.writeBoolean(mShouldCollectColorSamples); 209 dest.writeLong(mShortTermModelTimeout); 210 dest.writeFloat(mShortTermModelLowerLuxMultiplier); 211 dest.writeFloat(mShortTermModelUpperLuxMultiplier); 212 } 213 214 @Override describeContents()215 public int describeContents() { 216 return 0; 217 } 218 219 @NonNull 220 @Override toString()221 public String toString() { 222 StringBuilder sb = new StringBuilder("BrightnessConfiguration{["); 223 final int size = mLux.length; 224 for (int i = 0; i < size; i++) { 225 if (i != 0) { 226 sb.append(", "); 227 } 228 sb.append("(").append(mLux[i]).append(", ").append(mNits[i]).append(")"); 229 } 230 sb.append("], {"); 231 for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) { 232 sb.append("'" + entry.getKey() + "': " + entry.getValue() + ", "); 233 } 234 for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) { 235 sb.append(entry.getKey() + ": " + entry.getValue() + ", "); 236 } 237 sb.append("}, '"); 238 if (mDescription != null) { 239 sb.append(mDescription); 240 } 241 sb.append(", shouldCollectColorSamples = " + mShouldCollectColorSamples); 242 if (mShortTermModelTimeout >= 0) { 243 sb.append(", shortTermModelTimeout = " + mShortTermModelTimeout); 244 } 245 if (!Float.isNaN(mShortTermModelLowerLuxMultiplier)) { 246 sb.append(", shortTermModelLowerLuxMultiplier = " + mShortTermModelLowerLuxMultiplier); 247 } 248 if (!Float.isNaN(mShortTermModelLowerLuxMultiplier)) { 249 sb.append(", shortTermModelUpperLuxMultiplier = " + mShortTermModelUpperLuxMultiplier); 250 } 251 sb.append("'}"); 252 return sb.toString(); 253 } 254 255 @Override hashCode()256 public int hashCode() { 257 int result = 1; 258 result = result * 31 + Arrays.hashCode(mLux); 259 result = result * 31 + Arrays.hashCode(mNits); 260 result = result * 31 + mCorrectionsByPackageName.hashCode(); 261 result = result * 31 + mCorrectionsByCategory.hashCode(); 262 if (mDescription != null) { 263 result = result * 31 + mDescription.hashCode(); 264 } 265 result = result * 31 + Boolean.hashCode(mShouldCollectColorSamples); 266 result = result * 31 + Long.hashCode(mShortTermModelTimeout); 267 result = result * 31 + Float.hashCode(mShortTermModelLowerLuxMultiplier); 268 result = result * 31 + Float.hashCode(mShortTermModelUpperLuxMultiplier); 269 return result; 270 } 271 272 @Override equals(@ullable Object o)273 public boolean equals(@Nullable Object o) { 274 if (o == this) { 275 return true; 276 } 277 if (!(o instanceof BrightnessConfiguration)) { 278 return false; 279 } 280 final BrightnessConfiguration other = (BrightnessConfiguration) o; 281 return Arrays.equals(mLux, other.mLux) && Arrays.equals(mNits, other.mNits) 282 && mCorrectionsByPackageName.equals(other.mCorrectionsByPackageName) 283 && mCorrectionsByCategory.equals(other.mCorrectionsByCategory) 284 && Objects.equals(mDescription, other.mDescription) 285 && mShouldCollectColorSamples == other.mShouldCollectColorSamples 286 && mShortTermModelTimeout == other.mShortTermModelTimeout 287 && checkFloatEquals(mShortTermModelLowerLuxMultiplier, 288 other.mShortTermModelLowerLuxMultiplier) 289 && checkFloatEquals(mShortTermModelUpperLuxMultiplier, 290 other.mShortTermModelUpperLuxMultiplier); 291 } 292 checkFloatEquals(float one, float two)293 private boolean checkFloatEquals(float one, float two) { 294 if (Float.isNaN(one) && Float.isNaN(two)) { 295 return true; 296 } 297 return one == two; 298 } 299 300 public static final @android.annotation.NonNull Creator<BrightnessConfiguration> CREATOR = 301 new Creator<BrightnessConfiguration>() { 302 public BrightnessConfiguration createFromParcel(Parcel in) { 303 float[] lux = in.createFloatArray(); 304 float[] nits = in.createFloatArray(); 305 Builder builder = new Builder(lux, nits); 306 307 int n = in.readInt(); 308 for (int i = 0; i < n; i++) { 309 final String packageName = in.readString(); 310 final BrightnessCorrection correction = 311 BrightnessCorrection.CREATOR.createFromParcel(in); 312 builder.addCorrectionByPackageName(packageName, correction); 313 } 314 315 n = in.readInt(); 316 for (int i = 0; i < n; i++) { 317 final int category = in.readInt(); 318 final BrightnessCorrection correction = 319 BrightnessCorrection.CREATOR.createFromParcel(in); 320 builder.addCorrectionByCategory(category, correction); 321 } 322 323 final String description = in.readString(); 324 builder.setDescription(description); 325 final boolean shouldCollectColorSamples = in.readBoolean(); 326 builder.setShouldCollectColorSamples(shouldCollectColorSamples); 327 builder.setShortTermModelTimeoutMillis(in.readLong()); 328 builder.setShortTermModelLowerLuxMultiplier(in.readFloat()); 329 builder.setShortTermModelUpperLuxMultiplier(in.readFloat()); 330 return builder.build(); 331 } 332 333 public BrightnessConfiguration[] newArray(int size) { 334 return new BrightnessConfiguration[size]; 335 } 336 }; 337 338 /** 339 * Writes the configuration to an XML serializer. 340 * 341 * @param serializer 342 * The XML serializer. 343 * 344 * @hide 345 */ saveToXml(@onNull TypedXmlSerializer serializer)346 public void saveToXml(@NonNull TypedXmlSerializer serializer) throws IOException { 347 serializer.startTag(null, TAG_BRIGHTNESS_CURVE); 348 if (mDescription != null) { 349 serializer.attribute(null, ATTR_DESCRIPTION, mDescription); 350 } 351 for (int i = 0; i < mLux.length; i++) { 352 serializer.startTag(null, TAG_BRIGHTNESS_POINT); 353 serializer.attributeFloat(null, ATTR_LUX, mLux[i]); 354 serializer.attributeFloat(null, ATTR_NITS, mNits[i]); 355 serializer.endTag(null, TAG_BRIGHTNESS_POINT); 356 } 357 serializer.endTag(null, TAG_BRIGHTNESS_CURVE); 358 359 serializer.startTag(null, TAG_BRIGHTNESS_CORRECTIONS); 360 for (Map.Entry<String, BrightnessCorrection> entry : 361 mCorrectionsByPackageName.entrySet()) { 362 final String packageName = entry.getKey(); 363 final BrightnessCorrection correction = entry.getValue(); 364 serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION); 365 serializer.attribute(null, ATTR_PACKAGE_NAME, packageName); 366 correction.saveToXml(serializer); 367 serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION); 368 } 369 for (Map.Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) { 370 final int category = entry.getKey(); 371 final BrightnessCorrection correction = entry.getValue(); 372 serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION); 373 serializer.attributeInt(null, ATTR_CATEGORY, category); 374 correction.saveToXml(serializer); 375 serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION); 376 } 377 serializer.endTag(null, TAG_BRIGHTNESS_CORRECTIONS); 378 379 serializer.startTag(null, TAG_BRIGHTNESS_PARAMS); 380 if (mShouldCollectColorSamples) { 381 serializer.attributeBoolean(null, ATTR_COLLECT_COLOR, true); 382 } 383 if (mShortTermModelTimeout >= 0) { 384 serializer.attributeLong(null, ATTR_MODEL_TIMEOUT, mShortTermModelTimeout); 385 } 386 if (!Float.isNaN(mShortTermModelLowerLuxMultiplier)) { 387 serializer.attributeFloat(null, ATTR_MODEL_LOWER_BOUND, 388 mShortTermModelLowerLuxMultiplier); 389 } 390 if (!Float.isNaN(mShortTermModelUpperLuxMultiplier)) { 391 serializer.attributeFloat(null, ATTR_MODEL_UPPER_BOUND, 392 mShortTermModelUpperLuxMultiplier); 393 } 394 serializer.endTag(null, TAG_BRIGHTNESS_PARAMS); 395 } 396 397 /** 398 * Read a configuration from an XML parser. 399 * 400 * @param parser 401 * The XML parser. 402 * 403 * @throws IOException 404 * The parser failed to read the XML file. 405 * @throws XmlPullParserException 406 * The parser failed to parse the XML file. 407 * 408 * @hide 409 */ loadFromXml(@onNull TypedXmlPullParser parser)410 public static BrightnessConfiguration loadFromXml(@NonNull TypedXmlPullParser parser) 411 throws IOException, XmlPullParserException { 412 String description = null; 413 List<Float> luxList = new ArrayList<>(); 414 List<Float> nitsList = new ArrayList<>(); 415 Map<String, BrightnessCorrection> correctionsByPackageName = new HashMap<>(); 416 Map<Integer, BrightnessCorrection> correctionsByCategory = new HashMap<>(); 417 boolean shouldCollectColorSamples = false; 418 long shortTermModelTimeout = SHORT_TERM_TIMEOUT_UNSET; 419 float shortTermModelLowerLuxMultiplier = Float.NaN; 420 float shortTermModelUpperLuxMultiplier = Float.NaN; 421 final int configDepth = parser.getDepth(); 422 while (XmlUtils.nextElementWithin(parser, configDepth)) { 423 if (TAG_BRIGHTNESS_CURVE.equals(parser.getName())) { 424 description = parser.getAttributeValue(null, ATTR_DESCRIPTION); 425 final int curveDepth = parser.getDepth(); 426 while (XmlUtils.nextElementWithin(parser, curveDepth)) { 427 if (!TAG_BRIGHTNESS_POINT.equals(parser.getName())) { 428 continue; 429 } 430 final float lux = loadFloatFromXml(parser, ATTR_LUX); 431 final float nits = loadFloatFromXml(parser, ATTR_NITS); 432 luxList.add(lux); 433 nitsList.add(nits); 434 } 435 } else if (TAG_BRIGHTNESS_CORRECTIONS.equals(parser.getName())) { 436 final int correctionsDepth = parser.getDepth(); 437 while (XmlUtils.nextElementWithin(parser, correctionsDepth)) { 438 if (!TAG_BRIGHTNESS_CORRECTION.equals(parser.getName())) { 439 continue; 440 } 441 final String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME); 442 final int category = parser.getAttributeInt(null, ATTR_CATEGORY, -1); 443 BrightnessCorrection correction = BrightnessCorrection.loadFromXml(parser); 444 if (packageName != null) { 445 correctionsByPackageName.put(packageName, correction); 446 } else if (category != -1) { 447 correctionsByCategory.put(category, correction); 448 } 449 } 450 } else if (TAG_BRIGHTNESS_PARAMS.equals(parser.getName())) { 451 shouldCollectColorSamples = 452 parser.getAttributeBoolean(null, ATTR_COLLECT_COLOR, false); 453 Long timeout = loadLongFromXml(parser, ATTR_MODEL_TIMEOUT); 454 if (timeout != null) { 455 shortTermModelTimeout = timeout; 456 } 457 shortTermModelLowerLuxMultiplier = loadFloatFromXml(parser, ATTR_MODEL_LOWER_BOUND); 458 shortTermModelUpperLuxMultiplier = loadFloatFromXml(parser, ATTR_MODEL_UPPER_BOUND); 459 } 460 } 461 final int n = luxList.size(); 462 float[] lux = new float[n]; 463 float[] nits = new float[n]; 464 for (int i = 0; i < n; i++) { 465 lux[i] = luxList.get(i); 466 nits[i] = nitsList.get(i); 467 } 468 final BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(lux, 469 nits); 470 builder.setDescription(description); 471 for (Map.Entry<String, BrightnessCorrection> entry : correctionsByPackageName.entrySet()) { 472 final String packageName = entry.getKey(); 473 final BrightnessCorrection correction = entry.getValue(); 474 builder.addCorrectionByPackageName(packageName, correction); 475 } 476 for (Map.Entry<Integer, BrightnessCorrection> entry : correctionsByCategory.entrySet()) { 477 final int category = entry.getKey(); 478 final BrightnessCorrection correction = entry.getValue(); 479 builder.addCorrectionByCategory(category, correction); 480 } 481 builder.setShouldCollectColorSamples(shouldCollectColorSamples); 482 builder.setShortTermModelTimeoutMillis(shortTermModelTimeout); 483 builder.setShortTermModelLowerLuxMultiplier(shortTermModelLowerLuxMultiplier); 484 builder.setShortTermModelUpperLuxMultiplier(shortTermModelUpperLuxMultiplier); 485 return builder.build(); 486 } 487 loadFloatFromXml(TypedXmlPullParser parser, String attribute)488 private static float loadFloatFromXml(TypedXmlPullParser parser, String attribute) { 489 return parser.getAttributeFloat(null, attribute, Float.NaN); 490 } 491 loadLongFromXml(TypedXmlPullParser parser, String attribute)492 private static Long loadLongFromXml(TypedXmlPullParser parser, String attribute) { 493 try { 494 return parser.getAttributeLong(null, attribute); 495 } catch (Exception e) { 496 return null; 497 } 498 } 499 500 /** 501 * A builder class for {@link BrightnessConfiguration}s. 502 */ 503 public static class Builder { 504 private static final int MAX_CORRECTIONS_BY_PACKAGE_NAME = 20; 505 private static final int MAX_CORRECTIONS_BY_CATEGORY = 20; 506 507 private float[] mCurveLux; 508 private float[] mCurveNits; 509 private Map<String, BrightnessCorrection> mCorrectionsByPackageName; 510 private Map<Integer, BrightnessCorrection> mCorrectionsByCategory; 511 private String mDescription; 512 private boolean mShouldCollectColorSamples; 513 private long mShortTermModelTimeout = SHORT_TERM_TIMEOUT_UNSET; 514 private float mShortTermModelLowerLuxMultiplier = Float.NaN; 515 private float mShortTermModelUpperLuxMultiplier = Float.NaN; 516 517 /** 518 * Constructs the builder with the control points for the brightness curve. 519 * 520 * Brightness curves must have strictly increasing ambient brightness values in lux and 521 * monotonically increasing display brightness values in nits. In addition, the initial 522 * control point must be 0 lux. 523 * 524 * @throws IllegalArgumentException if the initial control point is not at 0 lux. 525 * @throws IllegalArgumentException if the lux levels are not strictly increasing. 526 * @throws IllegalArgumentException if the nit levels are not monotonically increasing. 527 */ Builder(float[] lux, float[] nits)528 public Builder(float[] lux, float[] nits) { 529 Objects.requireNonNull(lux); 530 Objects.requireNonNull(nits); 531 if (lux.length == 0 || nits.length == 0) { 532 throw new IllegalArgumentException("Lux and nits arrays must not be empty"); 533 } 534 if (lux.length != nits.length) { 535 throw new IllegalArgumentException("Lux and nits arrays must be the same length"); 536 } 537 if (lux[0] != 0) { 538 throw new IllegalArgumentException("Initial control point must be for 0 lux"); 539 } 540 Preconditions.checkArrayElementsInRange(lux, 0, Float.MAX_VALUE, "lux"); 541 Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits"); 542 checkMonotonic(lux, true /*strictly increasing*/, "lux"); 543 checkMonotonic(nits, false /*strictly increasing*/, "nits"); 544 mCurveLux = lux; 545 mCurveNits = nits; 546 mCorrectionsByPackageName = new HashMap<>(); 547 mCorrectionsByCategory = new HashMap<>(); 548 } 549 550 /** 551 * Returns the maximum number of corrections by package name allowed. 552 * 553 * @return The maximum number of corrections by package name allowed. 554 * 555 */ getMaxCorrectionsByPackageName()556 public int getMaxCorrectionsByPackageName() { 557 return MAX_CORRECTIONS_BY_PACKAGE_NAME; 558 } 559 560 /** 561 * Returns the maximum number of corrections by category allowed. 562 * 563 * @return The maximum number of corrections by category allowed. 564 * 565 */ getMaxCorrectionsByCategory()566 public int getMaxCorrectionsByCategory() { 567 return MAX_CORRECTIONS_BY_CATEGORY; 568 } 569 570 /** 571 * Add a brightness correction by app package name. 572 * This correction is applied whenever an app with this package name has the top activity 573 * of the focused stack. 574 * 575 * @param packageName 576 * The app's package name. 577 * @param correction 578 * The brightness correction. 579 * 580 * @return The builder. 581 * 582 * @throws IllegalArgumentExceptions 583 * Maximum number of corrections by package name exceeded (see 584 * {@link #getMaxCorrectionsByPackageName}). 585 * 586 */ 587 @NonNull addCorrectionByPackageName(@onNull String packageName, @NonNull BrightnessCorrection correction)588 public Builder addCorrectionByPackageName(@NonNull String packageName, 589 @NonNull BrightnessCorrection correction) { 590 Objects.requireNonNull(packageName, "packageName must not be null"); 591 Objects.requireNonNull(correction, "correction must not be null"); 592 if (mCorrectionsByPackageName.size() >= getMaxCorrectionsByPackageName()) { 593 throw new IllegalArgumentException("Too many corrections by package name"); 594 } 595 mCorrectionsByPackageName.put(packageName, correction); 596 return this; 597 } 598 599 /** 600 * Add a brightness correction by app category. 601 * This correction is applied whenever an app with this category has the top activity of 602 * the focused stack, and only if a correction by package name has not been applied. 603 * 604 * @param category 605 * The {@link android.content.pm.ApplicationInfo#category app category}. 606 * @param correction 607 * The brightness correction. 608 * 609 * @return The builder. 610 * 611 * @throws IllegalArgumentException 612 * Maximum number of corrections by category exceeded (see 613 * {@link #getMaxCorrectionsByCategory}). 614 * 615 */ 616 @NonNull addCorrectionByCategory(@pplicationInfo.Category int category, @NonNull BrightnessCorrection correction)617 public Builder addCorrectionByCategory(@ApplicationInfo.Category int category, 618 @NonNull BrightnessCorrection correction) { 619 Objects.requireNonNull(correction, "correction must not be null"); 620 if (mCorrectionsByCategory.size() >= getMaxCorrectionsByCategory()) { 621 throw new IllegalArgumentException("Too many corrections by category"); 622 } 623 mCorrectionsByCategory.put(category, correction); 624 return this; 625 } 626 627 /** 628 * Set description of the brightness curve. 629 * 630 * @param description brief text describing the curve pushed. It maybe truncated 631 * and will not be displayed in the UI 632 */ 633 @NonNull setDescription(@ullable String description)634 public Builder setDescription(@Nullable String description) { 635 mDescription = description; 636 return this; 637 } 638 639 /** 640 * Control whether screen color samples should be returned in 641 * {@link BrightnessChangeEvent#colorValueBuckets} if supported by the device. 642 * 643 * @param shouldCollectColorSamples true if color samples should be collected. 644 * @return 645 */ 646 @NonNull setShouldCollectColorSamples(boolean shouldCollectColorSamples)647 public Builder setShouldCollectColorSamples(boolean shouldCollectColorSamples) { 648 mShouldCollectColorSamples = shouldCollectColorSamples; 649 return this; 650 } 651 652 /** 653 * Sets the timeout for the short term model in milliseconds. 654 * 655 * If the screen is inactive for this timeout then the short term model 656 * will check the lux range defined by {@link #setShortTermModelLowerLuxMultiplier(float))} 657 * and {@link #setShortTermModelUpperLuxMultiplier(float)} to decide whether to keep any 658 * adjustment the user has made to adaptive brightness. 659 */ 660 @NonNull setShortTermModelTimeoutMillis(long shortTermModelTimeoutMillis)661 public Builder setShortTermModelTimeoutMillis(long shortTermModelTimeoutMillis) { 662 mShortTermModelTimeout = shortTermModelTimeoutMillis; 663 return this; 664 } 665 666 /** 667 * Sets the multiplier used to calculate the upper bound for which 668 * a users adaptive brightness is considered valid. 669 * 670 * For example if a user changes the brightness when the ambient light level 671 * is 100 lux, the adjustment will be kept if the current ambient light level 672 * is {@code <= 100 + (100 * shortTermModelUpperLuxMultiplier)}. 673 * 674 * @throws IllegalArgumentException if shortTermModelUpperLuxMultiplier is negative. 675 */ 676 @NonNull setShortTermModelUpperLuxMultiplier( @loatRangefrom = 0.0f) float shortTermModelUpperLuxMultiplier)677 public Builder setShortTermModelUpperLuxMultiplier( 678 @FloatRange(from = 0.0f) float shortTermModelUpperLuxMultiplier) { 679 if (shortTermModelUpperLuxMultiplier < 0.0f) { 680 throw new IllegalArgumentException("Negative lux multiplier"); 681 } 682 mShortTermModelUpperLuxMultiplier = shortTermModelUpperLuxMultiplier; 683 return this; 684 } 685 686 /** 687 * Returns the multiplier used to calculate the lower bound for which 688 * a users adaptive brightness is considered valid. 689 * 690 * For example if a user changes the brightness when the ambient light level 691 * is 100 lux, the adjustment will be kept if the current ambient light level 692 * is {@code >= 100 - (100 * shortTermModelLowerLuxMultiplier)}. 693 * 694 * @throws IllegalArgumentException if shortTermModelUpperLuxMultiplier is negative. 695 */ 696 @NonNull setShortTermModelLowerLuxMultiplier( @loatRangefrom = 0.0f) float shortTermModelLowerLuxMultiplier)697 public Builder setShortTermModelLowerLuxMultiplier( 698 @FloatRange(from = 0.0f) float shortTermModelLowerLuxMultiplier) { 699 if (shortTermModelLowerLuxMultiplier < 0.0f) { 700 throw new IllegalArgumentException("Negative lux multiplier"); 701 } 702 mShortTermModelLowerLuxMultiplier = shortTermModelLowerLuxMultiplier; 703 return this; 704 } 705 706 /** 707 * Builds the {@link BrightnessConfiguration}. 708 */ 709 @NonNull build()710 public BrightnessConfiguration build() { 711 if (mCurveLux == null || mCurveNits == null) { 712 throw new IllegalStateException("A curve must be set!"); 713 } 714 return new BrightnessConfiguration(mCurveLux, mCurveNits, mCorrectionsByPackageName, 715 mCorrectionsByCategory, mDescription, mShouldCollectColorSamples, 716 mShortTermModelTimeout, mShortTermModelLowerLuxMultiplier, 717 mShortTermModelUpperLuxMultiplier); 718 } 719 checkMonotonic(float[] vals, boolean strictlyIncreasing, String name)720 private static void checkMonotonic(float[] vals, boolean strictlyIncreasing, String name) { 721 if (vals.length <= 1) { 722 return; 723 } 724 float prev = vals[0]; 725 for (int i = 1; i < vals.length; i++) { 726 if (prev > vals[i] || prev == vals[i] && strictlyIncreasing) { 727 String condition = strictlyIncreasing ? "strictly increasing" : "monotonic"; 728 throw new IllegalArgumentException(name + " values must be " + condition); 729 } 730 prev = vals[i]; 731 } 732 } 733 } 734 } 735