• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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