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