content: /*
   * Copyright (C) 2016 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.telephony;
  
  import android.annotation.SystemApi;
  import android.os.Parcel;
  import android.os.Parcelable;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  /**
   * Parcelable class to store Telephony histogram.
   * @hide
   */
  @SystemApi
  public final class TelephonyHistogram implements Parcelable {
      // Type of Telephony histogram Eg: RIL histogram will have all timing data associated with
      // RIL calls. Similarly we can have any other Telephony histogram.
      private final int mCategory;
  
      // Unique Id identifying a sample within particular category of histogram
      private final int mId;
  
      // Min time taken in ms
      private int mMinTimeMs;
  
      // Max time taken in ms
      private int mMaxTimeMs;
  
      // Average time taken in ms
      private int mAverageTimeMs;
  
      // Total count of samples
      private int mSampleCount;
  
      // Array storing time taken for first #RANGE_CALCULATION_COUNT samples of histogram.
      private int[] mInitialTimings;
  
      // Total number of time ranges expected (must be greater than 1)
      private final int mBucketCount;
  
      // Array storing endpoints of range buckets. Calculated based on values of minTime & maxTime
      // after totalTimeCount is #RANGE_CALCULATION_COUNT.
      private final int[] mBucketEndPoints;
  
      // Array storing counts for each time range starting from smallest value range
      private final int[] mBucketCounters;
  
      /**
       * Constant for Telephony category
       */
      public static final int TELEPHONY_CATEGORY_RIL = 1;
  
      // Count of Histogram samples after which time buckets are created.
      private static final int RANGE_CALCULATION_COUNT = 10;
  
  
      // Constant used to indicate #initialTimings is null while parceling
      private static final int ABSENT = 0;
  
      // Constant used to indicate #initialTimings is not null while parceling
      private static final int PRESENT = 1;
  
      // Throws exception if #totalBuckets is not greater than one.
      public TelephonyHistogram (int category, int id, int bucketCount) {
          if (bucketCount <= 1) {
              throw new IllegalArgumentException("Invalid number of buckets");
          }
          mCategory = category;
          mId = id;
          mMinTimeMs = Integer.MAX_VALUE;
          mMaxTimeMs = 0;
          mAverageTimeMs = 0;
          mSampleCount = 0;
          mInitialTimings = new int[RANGE_CALCULATION_COUNT];
          mBucketCount = bucketCount;
          mBucketEndPoints = new int[bucketCount - 1];
          mBucketCounters = new int[bucketCount];
      }
  
      public TelephonyHistogram(TelephonyHistogram th) {
          mCategory = th.getCategory();
          mId = th.getId();
          mMinTimeMs = th.getMinTime();
          mMaxTimeMs = th.getMaxTime();
          mAverageTimeMs = th.getAverageTime();
          mSampleCount = th.getSampleCount();
          mInitialTimings = th.getInitialTimings();
          mBucketCount = th.getBucketCount();
          mBucketEndPoints = th.getBucketEndPoints();
          mBucketCounters = th.getBucketCounters();
      }
  
      public int getCategory() {
          return mCategory;
      }
  
      public int getId() {
          return mId;
      }
  
      public int getMinTime() {
          return mMinTimeMs;
      }
  
      public int getMaxTime() {
          return mMaxTimeMs;
      }
  
      public int getAverageTime() {
          return mAverageTimeMs;
      }
  
      public int getSampleCount () {
          return mSampleCount;
      }
  
      private int[] getInitialTimings() {
          return mInitialTimings;
      }
  
      public int getBucketCount() {
          return mBucketCount;
      }
  
      public int[] getBucketEndPoints() {
          if (mSampleCount > 1 && mSampleCount < 10) {
              int[] tempEndPoints = new int[mBucketCount - 1];
              calculateBucketEndPoints(tempEndPoints);
              return tempEndPoints;
          } else {
              return getDeepCopyOfArray(mBucketEndPoints);
          }
      }
  
      public int[] getBucketCounters() {
          if (mSampleCount > 1 && mSampleCount < 10) {
              int[] tempEndPoints = new int[mBucketCount - 1];
              int[] tempBucketCounters = new int[mBucketCount];
              calculateBucketEndPoints(tempEndPoints);
              for (int j = 0; j < mSampleCount; j++) {
                  addToBucketCounter(tempEndPoints, tempBucketCounters, mInitialTimings[j]);
              }
              return tempBucketCounters;
          } else {
              return getDeepCopyOfArray(mBucketCounters);
          }
      }
  
      private int[] getDeepCopyOfArray(int[] array) {
          int[] clone = new int[array.length];
          System.arraycopy(array, 0, clone, 0, array.length);
          return clone;
      }
  
      private void addToBucketCounter(int[] bucketEndPoints, int[] bucketCounters, int time) {
          int i;
          for (i = 0; i < bucketEndPoints.length; i++) {
              if (time <= bucketEndPoints[i]) {
                  bucketCounters[i]++;
                  return;
              }
          }
          bucketCounters[i]++;
      }
  
      private void calculateBucketEndPoints(int[] bucketEndPoints) {
          for (int i = 1; i < mBucketCount; i++) {
              int endPt = mMinTimeMs + (i * (mMaxTimeMs - mMinTimeMs)) / mBucketCount;
              bucketEndPoints[i - 1] = endPt;
          }
      }
  
      // Add new value of time taken
      // This function updates minTime, maxTime, averageTime & totalTimeCount every time it is
      // called. initialTimings[] is updated if totalTimeCount <= #RANGE_CALCULATION_COUNT. When
      // totalTimeCount = RANGE_CALCULATION_COUNT, based on the min, max time & the number of buckets
      // expected, bucketEndPoints[] would be calculated. Then bucketCounters[] would be filled up
      // using values stored in initialTimings[]. Thereafter bucketCounters[] will always be updated.
      public void addTimeTaken(int time) {
          // Initialize all fields if its first entry or if integer overflow is going to occur while
          // trying to calculate averageTime
          if (mSampleCount == 0 || (mSampleCount == Integer.MAX_VALUE)) {
              if (mSampleCount == 0) {
                  mMinTimeMs = time;
                  mMaxTimeMs = time;
                  mAverageTimeMs = time;
              } else {
                  mInitialTimings = new int[RANGE_CALCULATION_COUNT];
              }
              mSampleCount = 1;
              Arrays.fill(mInitialTimings, 0);
              mInitialTimings[0] = time;
              Arrays.fill(mBucketEndPoints, 0);
              Arrays.fill(mBucketCounters, 0);
          } else {
              if (time < mMinTimeMs) {
                  mMinTimeMs = time;
              }
              if (time > mMaxTimeMs) {
                  mMaxTimeMs = time;
              }
              long totalTime = ((long)mAverageTimeMs) * mSampleCount + time;
              mAverageTimeMs = (int)(totalTime/++mSampleCount);
  
              if (mSampleCount < RANGE_CALCULATION_COUNT) {
                  mInitialTimings[mSampleCount - 1] = time;
              } else if (mSampleCount == RANGE_CALCULATION_COUNT) {
                  mInitialTimings[mSampleCount - 1] = time;
  
                  // Calculate bucket endpoints based on bucketCount expected
                  calculateBucketEndPoints(mBucketEndPoints);
  
                  // Use values stored in initialTimings[] to update bucketCounters
                  for (int j = 0; j < RANGE_CALCULATION_COUNT; j++) {
                      addToBucketCounter(mBucketEndPoints, mBucketCounters, mInitialTimings[j]);
                  }
                  mInitialTimings = null;
              } else {
                  addToBucketCounter(mBucketEndPoints, mBucketCounters, time);
              }
  
          }
      }
  
      public String toString() {
          String basic = " Histogram id = " + mId + " Time(ms): min = " + mMinTimeMs + " max = "
                  + mMaxTimeMs + " avg = " + mAverageTimeMs + " Count = " + mSampleCount;
          if (mSampleCount < RANGE_CALCULATION_COUNT) {
              return basic;
          } else {
              StringBuffer intervals = new StringBuffer(" Interval Endpoints:");
              for (int i = 0; i < mBucketEndPoints.length; i++) {
                  intervals.append(" " + mBucketEndPoints[i]);
              }
              intervals.append(" Interval counters:");
              for (int i = 0; i < mBucketCounters.length; i++) {
                  intervals.append(" " + mBucketCounters[i]);
              }
              return basic + intervals;
          }
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<TelephonyHistogram> CREATOR =
              new Parcelable.Creator<TelephonyHistogram> () {
  
                  @Override
                  public TelephonyHistogram createFromParcel(Parcel in) {
                      return new TelephonyHistogram(in);
                  }
  
                  @Override
                  public TelephonyHistogram[] newArray(int size) {
                      return new TelephonyHistogram[size];
                  }
              };
  
      public TelephonyHistogram(Parcel in) {
          mCategory = in.readInt();
          mId = in.readInt();
          mMinTimeMs = in.readInt();
          mMaxTimeMs = in.readInt();
          mAverageTimeMs = in.readInt();
          mSampleCount = in.readInt();
          if (in.readInt() == PRESENT) {
              mInitialTimings = new int[RANGE_CALCULATION_COUNT];
              in.readIntArray(mInitialTimings);
          }
          mBucketCount = in.readInt();
          mBucketEndPoints = new int[mBucketCount - 1];
          in.readIntArray(mBucketEndPoints);
          mBucketCounters = new int[mBucketCount];
          in.readIntArray(mBucketCounters);
      }
  
      public void writeToParcel(Parcel out, int flags) {
          out.writeInt(mCategory);
          out.writeInt(mId);
          out.writeInt(mMinTimeMs);
          out.writeInt(mMaxTimeMs);
          out.writeInt(mAverageTimeMs);
          out.writeInt(mSampleCount);
          if (mInitialTimings == null) {
              out.writeInt(ABSENT);
          } else {
              out.writeInt(PRESENT);
              out.writeIntArray(mInitialTimings);
          }
          out.writeInt(mBucketCount);
          out.writeIntArray(mBucketEndPoints);
          out.writeIntArray(mBucketCounters);
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  }
  