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