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