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