1 /*
2  * Copyright 2018 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.os.Parcel;
24 import android.os.Parcelable;
25 
26 import com.android.internal.util.Preconditions;
27 
28 import java.time.LocalDate;
29 import java.util.Arrays;
30 import java.util.Objects;
31 
32 /**
33  * AmbientBrightnessDayStats stores and manipulates brightness stats over a single day.
34  * {@see DisplayManager.getAmbientBrightnessStats()}
35  *
36  * @hide
37  */
38 @SystemApi
39 @TestApi
40 public final class AmbientBrightnessDayStats implements Parcelable {
41 
42     /** The localdate for which brightness stats are being tracked */
43     private final LocalDate mLocalDate;
44 
45     /** Ambient brightness values for creating bucket boundaries from */
46     private final float[] mBucketBoundaries;
47 
48     /** Stats of how much time (in seconds) was spent in each of the buckets */
49     private final float[] mStats;
50 
51     /**
52      * Initialize day stats from the given state. The time spent in each of the bucket is
53      * initialized to 0.
54      *
55      * @param localDate        The date for which stats are being tracked
56      * @param bucketBoundaries Bucket boundaries used from creating the buckets from
57      * @hide
58      */
AmbientBrightnessDayStats(@onNull LocalDate localDate, @NonNull float[] bucketBoundaries)59     public AmbientBrightnessDayStats(@NonNull LocalDate localDate,
60             @NonNull float[] bucketBoundaries) {
61         this(localDate, bucketBoundaries, null);
62     }
63 
64     /**
65      * Initialize day stats from the given state
66      *
67      * @param localDate        The date for which stats are being tracked
68      * @param bucketBoundaries Bucket boundaries used from creating the buckets from
69      * @param stats            Time spent in each of the buckets (in seconds)
70      * @hide
71      */
AmbientBrightnessDayStats(@onNull LocalDate localDate, @NonNull float[] bucketBoundaries, float[] stats)72     public AmbientBrightnessDayStats(@NonNull LocalDate localDate,
73             @NonNull float[] bucketBoundaries, float[] stats) {
74         Objects.requireNonNull(localDate);
75         Objects.requireNonNull(bucketBoundaries);
76         Preconditions.checkArrayElementsInRange(bucketBoundaries, 0, Float.MAX_VALUE,
77                 "bucketBoundaries");
78         if (bucketBoundaries.length < 1) {
79             throw new IllegalArgumentException("Bucket boundaries must contain at least 1 value");
80         }
81         checkSorted(bucketBoundaries);
82         if (stats == null) {
83             stats = new float[bucketBoundaries.length];
84         } else {
85             Preconditions.checkArrayElementsInRange(stats, 0, Float.MAX_VALUE, "stats");
86             if (bucketBoundaries.length != stats.length) {
87                 throw new IllegalArgumentException(
88                         "Bucket boundaries and stats must be of same size.");
89             }
90         }
91         mLocalDate = localDate;
92         mBucketBoundaries = bucketBoundaries;
93         mStats = stats;
94     }
95 
96     /**
97      * @return The {@link LocalDate} for which brightness stats are being tracked.
98      */
getLocalDate()99     public LocalDate getLocalDate() {
100         return mLocalDate;
101     }
102 
103     /**
104      * @return Aggregated stats of time spent (in seconds) in various buckets.
105      */
getStats()106     public float[] getStats() {
107         return mStats;
108     }
109 
110     /**
111      * Returns the bucket boundaries (in lux) used for creating buckets. For eg., if the bucket
112      * boundaries array is {b1, b2, b3}, the buckets will be [b1, b2), [b2, b3), [b3, inf).
113      *
114      * @return The list of bucket boundaries.
115      */
getBucketBoundaries()116     public float[] getBucketBoundaries() {
117         return mBucketBoundaries;
118     }
119 
AmbientBrightnessDayStats(Parcel source)120     private AmbientBrightnessDayStats(Parcel source) {
121         mLocalDate = LocalDate.parse(source.readString());
122         mBucketBoundaries = source.createFloatArray();
123         mStats = source.createFloatArray();
124     }
125 
126     public static final @android.annotation.NonNull Creator<AmbientBrightnessDayStats> CREATOR =
127             new Creator<AmbientBrightnessDayStats>() {
128 
129                 @Override
130                 public AmbientBrightnessDayStats createFromParcel(Parcel source) {
131                     return new AmbientBrightnessDayStats(source);
132                 }
133 
134                 @Override
135                 public AmbientBrightnessDayStats[] newArray(int size) {
136                     return new AmbientBrightnessDayStats[size];
137                 }
138             };
139 
140     @Override
equals(@ullable Object obj)141     public boolean equals(@Nullable Object obj) {
142         if (this == obj) {
143             return true;
144         }
145         if (obj == null) {
146             return false;
147         }
148         if (getClass() != obj.getClass()) {
149             return false;
150         }
151         AmbientBrightnessDayStats other = (AmbientBrightnessDayStats) obj;
152         return mLocalDate.equals(other.mLocalDate) && Arrays.equals(mBucketBoundaries,
153                 other.mBucketBoundaries) && Arrays.equals(mStats, other.mStats);
154     }
155 
156     @Override
hashCode()157     public int hashCode() {
158         final int prime = 31;
159         int result = 1;
160         result = result * prime + mLocalDate.hashCode();
161         result = result * prime + Arrays.hashCode(mBucketBoundaries);
162         result = result * prime + Arrays.hashCode(mStats);
163         return result;
164     }
165 
166     @NonNull
167     @Override
toString()168     public String toString() {
169         StringBuilder bucketBoundariesString = new StringBuilder();
170         StringBuilder statsString = new StringBuilder();
171         for (int i = 0; i < mBucketBoundaries.length; i++) {
172             if (i != 0) {
173                 bucketBoundariesString.append(", ");
174                 statsString.append(", ");
175             }
176             bucketBoundariesString.append(mBucketBoundaries[i]);
177             statsString.append(mStats[i]);
178         }
179         return new StringBuilder()
180                 .append(mLocalDate).append(" ")
181                 .append("{").append(bucketBoundariesString).append("} ")
182                 .append("{").append(statsString).append("}").toString();
183     }
184 
185     @Override
describeContents()186     public int describeContents() {
187         return 0;
188     }
189 
190     @Override
writeToParcel(Parcel dest, int flags)191     public void writeToParcel(Parcel dest, int flags) {
192         dest.writeString(mLocalDate.toString());
193         dest.writeFloatArray(mBucketBoundaries);
194         dest.writeFloatArray(mStats);
195     }
196 
197     /**
198      * Updates the stats by incrementing the time spent for the appropriate bucket based on ambient
199      * brightness reading.
200      *
201      * @param ambientBrightness Ambient brightness reading (in lux)
202      * @param durationSec       Time spent with the given reading (in seconds)
203      * @hide
204      */
log(float ambientBrightness, float durationSec)205     public void log(float ambientBrightness, float durationSec) {
206         int bucketIndex = getBucketIndex(ambientBrightness);
207         if (bucketIndex >= 0) {
208             mStats[bucketIndex] += durationSec;
209         }
210     }
211 
getBucketIndex(float ambientBrightness)212     private int getBucketIndex(float ambientBrightness) {
213         if (ambientBrightness < mBucketBoundaries[0]) {
214             return -1;
215         }
216         int low = 0;
217         int high = mBucketBoundaries.length - 1;
218         while (low < high) {
219             int mid = (low + high) / 2;
220             if (mBucketBoundaries[mid] <= ambientBrightness
221                     && ambientBrightness < mBucketBoundaries[mid + 1]) {
222                 return mid;
223             } else if (mBucketBoundaries[mid] < ambientBrightness) {
224                 low = mid + 1;
225             } else if (mBucketBoundaries[mid] > ambientBrightness) {
226                 high = mid - 1;
227             }
228         }
229         return low;
230     }
231 
checkSorted(float[] values)232     private static void checkSorted(float[] values) {
233         if (values.length <= 1) {
234             return;
235         }
236         float prevValue = values[0];
237         for (int i = 1; i < values.length; i++) {
238             Preconditions.checkState(prevValue < values[i]);
239             prevValue = values[i];
240         }
241         return;
242     }
243 }
244