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