/* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.hardware.display; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.Preconditions; import java.time.LocalDate; import java.util.Arrays; import java.util.Objects; /** * AmbientBrightnessDayStats stores and manipulates brightness stats over a single day. * {@see DisplayManager.getAmbientBrightnessStats()} * * @hide */ @SystemApi public final class AmbientBrightnessDayStats implements Parcelable { /** The localdate for which brightness stats are being tracked */ private final LocalDate mLocalDate; /** Ambient brightness values for creating bucket boundaries from */ private final float[] mBucketBoundaries; /** Stats of how much time (in seconds) was spent in each of the buckets */ private final float[] mStats; /** * Initialize day stats from the given state. The time spent in each of the bucket is * initialized to 0. * * @param localDate The date for which stats are being tracked * @param bucketBoundaries Bucket boundaries used from creating the buckets from * @hide */ public AmbientBrightnessDayStats(@NonNull LocalDate localDate, @NonNull float[] bucketBoundaries) { this(localDate, bucketBoundaries, null); } /** * Initialize day stats from the given state * * @param localDate The date for which stats are being tracked * @param bucketBoundaries Bucket boundaries used from creating the buckets from * @param stats Time spent in each of the buckets (in seconds) * @hide */ public AmbientBrightnessDayStats(@NonNull LocalDate localDate, @NonNull float[] bucketBoundaries, float[] stats) { Objects.requireNonNull(localDate); Objects.requireNonNull(bucketBoundaries); Preconditions.checkArrayElementsInRange(bucketBoundaries, 0, Float.MAX_VALUE, "bucketBoundaries"); if (bucketBoundaries.length < 1) { throw new IllegalArgumentException("Bucket boundaries must contain at least 1 value"); } checkSorted(bucketBoundaries); if (stats == null) { stats = new float[bucketBoundaries.length]; } else { Preconditions.checkArrayElementsInRange(stats, 0, Float.MAX_VALUE, "stats"); if (bucketBoundaries.length != stats.length) { throw new IllegalArgumentException( "Bucket boundaries and stats must be of same size."); } } mLocalDate = localDate; mBucketBoundaries = bucketBoundaries; mStats = stats; } /** * @return The {@link LocalDate} for which brightness stats are being tracked. */ public LocalDate getLocalDate() { return mLocalDate; } /** * @return Aggregated stats of time spent (in seconds) in various buckets. */ public float[] getStats() { return mStats; } /** * Returns the bucket boundaries (in lux) used for creating buckets. For eg., if the bucket * boundaries array is {b1, b2, b3}, the buckets will be [b1, b2), [b2, b3), [b3, inf). * * @return The list of bucket boundaries. */ public float[] getBucketBoundaries() { return mBucketBoundaries; } private AmbientBrightnessDayStats(Parcel source) { mLocalDate = LocalDate.parse(source.readString()); mBucketBoundaries = source.createFloatArray(); mStats = source.createFloatArray(); } public static final @android.annotation.NonNull Creator CREATOR = new Creator() { @Override public AmbientBrightnessDayStats createFromParcel(Parcel source) { return new AmbientBrightnessDayStats(source); } @Override public AmbientBrightnessDayStats[] newArray(int size) { return new AmbientBrightnessDayStats[size]; } }; @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } AmbientBrightnessDayStats other = (AmbientBrightnessDayStats) obj; return mLocalDate.equals(other.mLocalDate) && Arrays.equals(mBucketBoundaries, other.mBucketBoundaries) && Arrays.equals(mStats, other.mStats); } @Override public int hashCode() { final int prime = 31; int result = 1; result = result * prime + mLocalDate.hashCode(); result = result * prime + Arrays.hashCode(mBucketBoundaries); result = result * prime + Arrays.hashCode(mStats); return result; } @NonNull @Override public String toString() { StringBuilder bucketBoundariesString = new StringBuilder(); StringBuilder statsString = new StringBuilder(); for (int i = 0; i < mBucketBoundaries.length; i++) { if (i != 0) { bucketBoundariesString.append(", "); statsString.append(", "); } bucketBoundariesString.append(mBucketBoundaries[i]); statsString.append(mStats[i]); } return new StringBuilder() .append(mLocalDate).append(" ") .append("{").append(bucketBoundariesString).append("} ") .append("{").append(statsString).append("}").toString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mLocalDate.toString()); dest.writeFloatArray(mBucketBoundaries); dest.writeFloatArray(mStats); } /** * Updates the stats by incrementing the time spent for the appropriate bucket based on ambient * brightness reading. * * @param ambientBrightness Ambient brightness reading (in lux) * @param durationSec Time spent with the given reading (in seconds) * @hide */ public void log(float ambientBrightness, float durationSec) { int bucketIndex = getBucketIndex(ambientBrightness); if (bucketIndex >= 0) { mStats[bucketIndex] += durationSec; } } private int getBucketIndex(float ambientBrightness) { if (ambientBrightness < mBucketBoundaries[0] || Float.isNaN(ambientBrightness)) { return -1; } int low = 0; int high = mBucketBoundaries.length - 1; while (low < high) { int mid = (low + high) / 2; if (mBucketBoundaries[mid] <= ambientBrightness && ambientBrightness < mBucketBoundaries[mid + 1]) { return mid; } else if (mBucketBoundaries[mid] < ambientBrightness) { low = mid + 1; } else if (mBucketBoundaries[mid] > ambientBrightness) { high = mid - 1; } } return low; } private static void checkSorted(float[] values) { if (values.length <= 1) { return; } float prevValue = values[0]; for (int i = 1; i < values.length; i++) { Preconditions.checkState(prevValue < values[i]); prevValue = values[i]; } return; } }