• 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.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.annotation.TestApi;
23 import android.content.pm.ApplicationInfo;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.util.Pair;
27 
28 import com.android.internal.util.Preconditions;
29 import com.android.internal.util.XmlUtils;
30 
31 import org.xmlpull.v1.XmlPullParser;
32 import org.xmlpull.v1.XmlPullParserException;
33 import org.xmlpull.v1.XmlSerializer;
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 @TestApi
47 public final class BrightnessConfiguration implements Parcelable {
48     private static final String TAG_BRIGHTNESS_CURVE = "brightness-curve";
49     private static final String TAG_BRIGHTNESS_POINT = "brightness-point";
50     private static final String TAG_BRIGHTNESS_CORRECTIONS = "brightness-corrections";
51     private static final String TAG_BRIGHTNESS_CORRECTION = "brightness-correction";
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 
58     private final float[] mLux;
59     private final float[] mNits;
60     private final Map<String, BrightnessCorrection> mCorrectionsByPackageName;
61     private final Map<Integer, BrightnessCorrection> mCorrectionsByCategory;
62     private final String mDescription;
63 
BrightnessConfiguration(float[] lux, float[] nits, Map<String, BrightnessCorrection> correctionsByPackageName, Map<Integer, BrightnessCorrection> correctionsByCategory, String description)64     private BrightnessConfiguration(float[] lux, float[] nits,
65             Map<String, BrightnessCorrection> correctionsByPackageName,
66             Map<Integer, BrightnessCorrection> correctionsByCategory, String description) {
67         mLux = lux;
68         mNits = nits;
69         mCorrectionsByPackageName = correctionsByPackageName;
70         mCorrectionsByCategory = correctionsByCategory;
71         mDescription = description;
72     }
73 
74     /**
75      * Gets the base brightness as curve.
76      *
77      * The curve is returned as a pair of float arrays, the first representing all of the lux
78      * points of the brightness curve and the second representing all of the nits values of the
79      * brightness curve.
80      *
81      * @return the control points for the brightness curve.
82      */
getCurve()83     public Pair<float[], float[]> getCurve() {
84         return Pair.create(Arrays.copyOf(mLux, mLux.length), Arrays.copyOf(mNits, mNits.length));
85     }
86 
87     /**
88      * Returns a brightness correction by app, or null.
89      *
90      * @param packageName
91      *      The app's package name.
92      *
93      * @return The matching brightness correction, or null.
94      *
95      */
96     @Nullable
getCorrectionByPackageName(@onNull String packageName)97     public BrightnessCorrection getCorrectionByPackageName(@NonNull String packageName) {
98         return mCorrectionsByPackageName.get(packageName);
99     }
100 
101     /**
102      * Returns a brightness correction by app category, or null.
103      *
104      * @param category
105      *      The app category.
106      *
107      * @return The matching brightness correction, or null.
108      */
109     @Nullable
getCorrectionByCategory(@pplicationInfo.Category int category)110     public BrightnessCorrection getCorrectionByCategory(@ApplicationInfo.Category int category) {
111         return mCorrectionsByCategory.get(category);
112     }
113 
114     /**
115      * Returns description string.
116      * @hide
117      */
getDescription()118     public String getDescription() {
119         return mDescription;
120     }
121 
122     @Override
writeToParcel(Parcel dest, int flags)123     public void writeToParcel(Parcel dest, int flags) {
124         dest.writeFloatArray(mLux);
125         dest.writeFloatArray(mNits);
126         dest.writeInt(mCorrectionsByPackageName.size());
127         for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) {
128             final String packageName = entry.getKey();
129             final BrightnessCorrection correction = entry.getValue();
130             dest.writeString(packageName);
131             correction.writeToParcel(dest, flags);
132         }
133         dest.writeInt(mCorrectionsByCategory.size());
134         for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) {
135             final int category = entry.getKey();
136             final BrightnessCorrection correction = entry.getValue();
137             dest.writeInt(category);
138             correction.writeToParcel(dest, flags);
139         }
140         dest.writeString(mDescription);
141     }
142 
143     @Override
describeContents()144     public int describeContents() {
145         return 0;
146     }
147 
148     @Override
toString()149     public String toString() {
150         StringBuilder sb = new StringBuilder("BrightnessConfiguration{[");
151         final int size = mLux.length;
152         for (int i = 0; i < size; i++) {
153             if (i != 0) {
154                 sb.append(", ");
155             }
156             sb.append("(").append(mLux[i]).append(", ").append(mNits[i]).append(")");
157         }
158         sb.append("], {");
159         for (Entry<String, BrightnessCorrection> entry : mCorrectionsByPackageName.entrySet()) {
160             sb.append("'" + entry.getKey() + "': " + entry.getValue() + ", ");
161         }
162         for (Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) {
163             sb.append(entry.getKey() + ": " + entry.getValue() + ", ");
164         }
165         sb.append("}, '");
166         if (mDescription != null) {
167             sb.append(mDescription);
168         }
169         sb.append("'}");
170         return sb.toString();
171     }
172 
173     @Override
hashCode()174     public int hashCode() {
175         int result = 1;
176         result = result * 31 + Arrays.hashCode(mLux);
177         result = result * 31 + Arrays.hashCode(mNits);
178         result = result * 31 + mCorrectionsByPackageName.hashCode();
179         result = result * 31 + mCorrectionsByCategory.hashCode();
180         if (mDescription != null) {
181             result = result * 31 + mDescription.hashCode();
182         }
183         return result;
184     }
185 
186     @Override
equals(Object o)187     public boolean equals(Object o) {
188         if (o == this) {
189             return true;
190         }
191         if (!(o instanceof BrightnessConfiguration)) {
192             return false;
193         }
194         final BrightnessConfiguration other = (BrightnessConfiguration) o;
195         return Arrays.equals(mLux, other.mLux) && Arrays.equals(mNits, other.mNits)
196                 && mCorrectionsByPackageName.equals(other.mCorrectionsByPackageName)
197                 && mCorrectionsByCategory.equals(other.mCorrectionsByCategory)
198                 && Objects.equals(mDescription, other.mDescription);
199     }
200 
201     public static final @android.annotation.NonNull Creator<BrightnessConfiguration> CREATOR =
202             new Creator<BrightnessConfiguration>() {
203         public BrightnessConfiguration createFromParcel(Parcel in) {
204             float[] lux = in.createFloatArray();
205             float[] nits = in.createFloatArray();
206             Builder builder = new Builder(lux, nits);
207 
208             int n = in.readInt();
209             for (int i = 0; i < n; i++) {
210                 final String packageName = in.readString();
211                 final BrightnessCorrection correction =
212                         BrightnessCorrection.CREATOR.createFromParcel(in);
213                 builder.addCorrectionByPackageName(packageName, correction);
214             }
215 
216             n = in.readInt();
217             for (int i = 0; i < n; i++) {
218                 final int category = in.readInt();
219                 final BrightnessCorrection correction =
220                         BrightnessCorrection.CREATOR.createFromParcel(in);
221                 builder.addCorrectionByCategory(category, correction);
222             }
223 
224             final String description = in.readString();
225             builder.setDescription(description);
226             return builder.build();
227         }
228 
229         public BrightnessConfiguration[] newArray(int size) {
230             return new BrightnessConfiguration[size];
231         }
232     };
233 
234     /**
235      * Writes the configuration to an XML serializer.
236      *
237      * @param serializer
238      *      The XML serializer.
239      *
240      * @hide
241      */
saveToXml(@onNull XmlSerializer serializer)242     public void saveToXml(@NonNull XmlSerializer serializer) throws IOException {
243         serializer.startTag(null, TAG_BRIGHTNESS_CURVE);
244         if (mDescription != null) {
245             serializer.attribute(null, ATTR_DESCRIPTION, mDescription);
246         }
247         for (int i = 0; i < mLux.length; i++) {
248             serializer.startTag(null, TAG_BRIGHTNESS_POINT);
249             serializer.attribute(null, ATTR_LUX, Float.toString(mLux[i]));
250             serializer.attribute(null, ATTR_NITS, Float.toString(mNits[i]));
251             serializer.endTag(null, TAG_BRIGHTNESS_POINT);
252         }
253         serializer.endTag(null, TAG_BRIGHTNESS_CURVE);
254         serializer.startTag(null, TAG_BRIGHTNESS_CORRECTIONS);
255         for (Map.Entry<String, BrightnessCorrection> entry :
256                 mCorrectionsByPackageName.entrySet()) {
257             final String packageName = entry.getKey();
258             final BrightnessCorrection correction = entry.getValue();
259             serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION);
260             serializer.attribute(null, ATTR_PACKAGE_NAME, packageName);
261             correction.saveToXml(serializer);
262             serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION);
263         }
264         for (Map.Entry<Integer, BrightnessCorrection> entry : mCorrectionsByCategory.entrySet()) {
265             final int category = entry.getKey();
266             final BrightnessCorrection correction = entry.getValue();
267             serializer.startTag(null, TAG_BRIGHTNESS_CORRECTION);
268             serializer.attribute(null, ATTR_CATEGORY, Integer.toString(category));
269             correction.saveToXml(serializer);
270             serializer.endTag(null, TAG_BRIGHTNESS_CORRECTION);
271         }
272         serializer.endTag(null, TAG_BRIGHTNESS_CORRECTIONS);
273     }
274 
275     /**
276      * Read a configuration from an XML parser.
277      *
278      * @param parser
279      *      The XML parser.
280      *
281      * @throws IOException
282      *      The parser failed to read the XML file.
283      * @throws XmlPullParserException
284      *      The parser failed to parse the XML file.
285      *
286      * @hide
287      */
loadFromXml(@onNull XmlPullParser parser)288     public static BrightnessConfiguration loadFromXml(@NonNull XmlPullParser parser)
289             throws IOException, XmlPullParserException {
290         String description = null;
291         List<Float> luxList = new ArrayList<>();
292         List<Float> nitsList = new ArrayList<>();
293         Map<String, BrightnessCorrection> correctionsByPackageName = new HashMap<>();
294         Map<Integer, BrightnessCorrection> correctionsByCategory = new HashMap<>();
295         final int configDepth = parser.getDepth();
296         while (XmlUtils.nextElementWithin(parser, configDepth)) {
297             if (TAG_BRIGHTNESS_CURVE.equals(parser.getName())) {
298                 description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
299                 final int curveDepth = parser.getDepth();
300                 while (XmlUtils.nextElementWithin(parser, curveDepth)) {
301                     if (!TAG_BRIGHTNESS_POINT.equals(parser.getName())) {
302                         continue;
303                     }
304                     final float lux = loadFloatFromXml(parser, ATTR_LUX);
305                     final float nits = loadFloatFromXml(parser, ATTR_NITS);
306                     luxList.add(lux);
307                     nitsList.add(nits);
308                 }
309             }
310             if (TAG_BRIGHTNESS_CORRECTIONS.equals(parser.getName())) {
311                 final int correctionsDepth = parser.getDepth();
312                 while (XmlUtils.nextElementWithin(parser, correctionsDepth)) {
313                     if (!TAG_BRIGHTNESS_CORRECTION.equals(parser.getName())) {
314                         continue;
315                     }
316                     final String packageName = parser.getAttributeValue(null, ATTR_PACKAGE_NAME);
317                     final String categoryText = parser.getAttributeValue(null, ATTR_CATEGORY);
318                     BrightnessCorrection correction = BrightnessCorrection.loadFromXml(parser);
319                     if (packageName != null) {
320                         correctionsByPackageName.put(packageName, correction);
321                     } else if (categoryText != null) {
322                         try {
323                             final int category = Integer.parseInt(categoryText);
324                             correctionsByCategory.put(category, correction);
325                         } catch (NullPointerException | NumberFormatException e) {
326                             continue;
327                         }
328                     }
329                 }
330             }
331         }
332         final int n = luxList.size();
333         float[] lux = new float[n];
334         float[] nits = new float[n];
335         for (int i = 0; i < n; i++) {
336             lux[i] = luxList.get(i);
337             nits[i] = nitsList.get(i);
338         }
339         final BrightnessConfiguration.Builder builder = new BrightnessConfiguration.Builder(lux,
340                 nits);
341         builder.setDescription(description);
342         for (Map.Entry<String, BrightnessCorrection> entry : correctionsByPackageName.entrySet()) {
343             final String packageName = entry.getKey();
344             final BrightnessCorrection correction = entry.getValue();
345             builder.addCorrectionByPackageName(packageName, correction);
346         }
347         for (Map.Entry<Integer, BrightnessCorrection> entry : correctionsByCategory.entrySet()) {
348             final int category = entry.getKey();
349             final BrightnessCorrection correction = entry.getValue();
350             builder.addCorrectionByCategory(category, correction);
351         }
352         return builder.build();
353     }
354 
loadFloatFromXml(XmlPullParser parser, String attribute)355     private static float loadFloatFromXml(XmlPullParser parser, String attribute) {
356         final String string = parser.getAttributeValue(null, attribute);
357         try {
358             return Float.parseFloat(string);
359         } catch (NullPointerException | NumberFormatException e) {
360             return Float.NaN;
361         }
362     }
363 
364     /**
365      * A builder class for {@link BrightnessConfiguration}s.
366      */
367     public static class Builder {
368         private static final int MAX_CORRECTIONS_BY_PACKAGE_NAME = 20;
369         private static final int MAX_CORRECTIONS_BY_CATEGORY = 20;
370 
371         private float[] mCurveLux;
372         private float[] mCurveNits;
373         private Map<String, BrightnessCorrection> mCorrectionsByPackageName;
374         private Map<Integer, BrightnessCorrection> mCorrectionsByCategory;
375         private String mDescription;
376 
377         /**
378          * Constructs the builder with the control points for the brightness curve.
379          *
380          * Brightness curves must have strictly increasing ambient brightness values in lux and
381          * monotonically increasing display brightness values in nits. In addition, the initial
382          * control point must be 0 lux.
383          *
384          * @throws IllegalArgumentException if the initial control point is not at 0 lux.
385          * @throws IllegalArgumentException if the lux levels are not strictly increasing.
386          * @throws IllegalArgumentException if the nit levels are not monotonically increasing.
387          */
Builder(float[] lux, float[] nits)388         public Builder(float[] lux, float[] nits) {
389             Preconditions.checkNotNull(lux);
390             Preconditions.checkNotNull(nits);
391             if (lux.length == 0 || nits.length == 0) {
392                 throw new IllegalArgumentException("Lux and nits arrays must not be empty");
393             }
394             if (lux.length != nits.length) {
395                 throw new IllegalArgumentException("Lux and nits arrays must be the same length");
396             }
397             if (lux[0] != 0) {
398                 throw new IllegalArgumentException("Initial control point must be for 0 lux");
399             }
400             Preconditions.checkArrayElementsInRange(lux, 0, Float.MAX_VALUE, "lux");
401             Preconditions.checkArrayElementsInRange(nits, 0, Float.MAX_VALUE, "nits");
402             checkMonotonic(lux, true /*strictly increasing*/, "lux");
403             checkMonotonic(nits, false /*strictly increasing*/, "nits");
404             mCurveLux = lux;
405             mCurveNits = nits;
406             mCorrectionsByPackageName = new HashMap<>();
407             mCorrectionsByCategory = new HashMap<>();
408         }
409 
410         /**
411          * Returns the maximum number of corrections by package name allowed.
412          *
413          * @return The maximum number of corrections by package name allowed.
414          *
415          */
getMaxCorrectionsByPackageName()416         public int getMaxCorrectionsByPackageName() {
417             return MAX_CORRECTIONS_BY_PACKAGE_NAME;
418         }
419 
420         /**
421          * Returns the maximum number of corrections by category allowed.
422          *
423          * @return The maximum number of corrections by category allowed.
424          *
425          */
getMaxCorrectionsByCategory()426         public int getMaxCorrectionsByCategory() {
427             return MAX_CORRECTIONS_BY_CATEGORY;
428         }
429 
430         /**
431          * Add a brightness correction by app package name.
432          * This correction is applied whenever an app with this package name has the top activity
433          * of the focused stack.
434          *
435          * @param packageName
436          *      The app's package name.
437          * @param correction
438          *      The brightness correction.
439          *
440          * @return The builder.
441          *
442          * @throws IllegalArgumentExceptions
443          *      Maximum number of corrections by package name exceeded (see
444          *      {@link #getMaxCorrectionsByPackageName}).
445          *
446          */
447         @NonNull
addCorrectionByPackageName(@onNull String packageName, @NonNull BrightnessCorrection correction)448         public Builder addCorrectionByPackageName(@NonNull String packageName,
449                 @NonNull BrightnessCorrection correction) {
450             Objects.requireNonNull(packageName, "packageName must not be null");
451             Objects.requireNonNull(correction, "correction must not be null");
452             if (mCorrectionsByPackageName.size() >= getMaxCorrectionsByPackageName()) {
453                 throw new IllegalArgumentException("Too many corrections by package name");
454             }
455             mCorrectionsByPackageName.put(packageName, correction);
456             return this;
457         }
458 
459         /**
460          * Add a brightness correction by app category.
461          * This correction is applied whenever an app with this category has the top activity of
462          * the focused stack, and only if a correction by package name has not been applied.
463          *
464          * @param category
465          *      The {@link android.content.pm.ApplicationInfo#category app category}.
466          * @param correction
467          *      The brightness correction.
468          *
469          * @return The builder.
470          *
471          * @throws IllegalArgumentException
472          *      Maximum number of corrections by category exceeded (see
473          *      {@link #getMaxCorrectionsByCategory}).
474          *
475          */
476         @NonNull
addCorrectionByCategory(@pplicationInfo.Category int category, @NonNull BrightnessCorrection correction)477         public Builder addCorrectionByCategory(@ApplicationInfo.Category int category,
478                 @NonNull BrightnessCorrection correction) {
479             Objects.requireNonNull(correction, "correction must not be null");
480             if (mCorrectionsByCategory.size() >= getMaxCorrectionsByCategory()) {
481                 throw new IllegalArgumentException("Too many corrections by category");
482             }
483             mCorrectionsByCategory.put(category, correction);
484             return this;
485         }
486 
487         /**
488          * Set description of the brightness curve.
489          *
490          * @param description brief text describing the curve pushed. It maybe truncated
491          *                    and will not be displayed in the UI
492          */
493         @NonNull
setDescription(@ullable String description)494         public Builder setDescription(@Nullable String description) {
495             mDescription = description;
496             return this;
497         }
498 
499         /**
500          * Builds the {@link BrightnessConfiguration}.
501          */
502         @NonNull
build()503         public BrightnessConfiguration build() {
504             if (mCurveLux == null || mCurveNits == null) {
505                 throw new IllegalStateException("A curve must be set!");
506             }
507             return new BrightnessConfiguration(mCurveLux, mCurveNits, mCorrectionsByPackageName,
508                     mCorrectionsByCategory, mDescription);
509         }
510 
checkMonotonic(float[] vals, boolean strictlyIncreasing, String name)511         private static void checkMonotonic(float[] vals, boolean strictlyIncreasing, String name) {
512             if (vals.length <= 1) {
513                 return;
514             }
515             float prev = vals[0];
516             for (int i = 1; i < vals.length; i++) {
517                 if (prev > vals[i] || prev == vals[i] && strictlyIncreasing) {
518                     String condition = strictlyIncreasing ? "strictly increasing" : "monotonic";
519                     throw new IllegalArgumentException(name + " values must be " + condition);
520                 }
521                 prev = vals[i];
522             }
523         }
524     }
525 }
526