content: /*
   * 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.os;
  
  import android.annotation.SystemApi;
  import android.util.Slog;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * Container for statsd dimension value information, corresponding to a
   * stats_log.proto's DimensionValue.
   *
   * This consists of a field (an int representing a statsd atom field)
   * and a value (which may be one of a number of types).
   *
   * <p>
   * Only a single value is held, and it is necessarily one of the following types:
   * {@link String}, int, long, boolean, float,
   * or tuple (i.e. {@link List} of {@code StatsDimensionsValue}).
   *
   * The type of value held can be retrieved using {@link #getValueType()}, which returns one of the
   * following ints, depending on the type of value:
   * <ul>
   *  <li>{@link #STRING_VALUE_TYPE}</li>
   *  <li>{@link #INT_VALUE_TYPE}</li>
   *  <li>{@link #LONG_VALUE_TYPE}</li>
   *  <li>{@link #BOOLEAN_VALUE_TYPE}</li>
   *  <li>{@link #FLOAT_VALUE_TYPE}</li>
   *  <li>{@link #TUPLE_VALUE_TYPE}</li>
   * </ul>
   * Alternatively, this can be determined using {@link #isValueType(int)} with one of these constants
   * as a parameter.
   * The value itself can be retrieved using the correct get...Value() function for its type.
   *
   * <p>
   * The field is always an int, and always exists; it can be obtained using {@link #getField()}.
   *
   *
   * @hide
   */
  @SystemApi
  public final class StatsDimensionsValue implements Parcelable {
      private static final String TAG = "StatsDimensionsValue";
  
      // Values of the value type correspond to stats_log.proto's DimensionValue fields.
      // Keep constants in sync with services/include/android/os/StatsDimensionsValue.h.
      /** Indicates that this holds a String. */
      public static final int STRING_VALUE_TYPE = 2;
      /** Indicates that this holds an int. */
      public static final int INT_VALUE_TYPE = 3;
      /** Indicates that this holds a long. */
      public static final int LONG_VALUE_TYPE = 4;
      /** Indicates that this holds a boolean. */
      public static final int BOOLEAN_VALUE_TYPE = 5;
      /** Indicates that this holds a float. */
      public static final int FLOAT_VALUE_TYPE = 6;
      /** Indicates that this holds a List of StatsDimensionsValues. */
      public static final int TUPLE_VALUE_TYPE = 7;
  
      /** Value of a stats_log.proto DimensionsValue.field. */
      private final int mField;
  
      /** Type of stats_log.proto DimensionsValue.value, according to the VALUE_TYPEs above. */
      private final int mValueType;
  
      /**
       * Value of a stats_log.proto DimensionsValue.value.
       * String, Integer, Long, Boolean, Float, or StatsDimensionsValue[].
       */
      private final Object mValue; // immutable or array of immutables
  
      /**
       * Creates a {@code StatsDimensionValue} from a parcel.
       *
       * @hide
       */
      public StatsDimensionsValue(Parcel in) {
          mField = in.readInt();
          mValueType = in.readInt();
          mValue = readValueFromParcel(mValueType, in);
      }
  
      /**
       * Return the field, i.e. the tag of a statsd atom.
       *
       * @return the field
       */
      public int getField() {
          return mField;
      }
  
      /**
       * Retrieve the String held, if any.
       *
       * @return the {@link String} held if {@link #getValueType()} == {@link #STRING_VALUE_TYPE},
       *         null otherwise
       */
      public String getStringValue() {
          try {
              if (mValueType == STRING_VALUE_TYPE) return (String) mValue;
          } catch (ClassCastException e) {
              Slog.w(TAG, "Failed to successfully get value", e);
          }
          return null;
      }
  
      /**
       * Retrieve the int held, if any.
       *
       * @return the int held if {@link #getValueType()} == {@link #INT_VALUE_TYPE}, 0 otherwise
       */
      public int getIntValue() {
          try {
              if (mValueType == INT_VALUE_TYPE) return (Integer) mValue;
          } catch (ClassCastException e) {
              Slog.w(TAG, "Failed to successfully get value", e);
          }
          return 0;
      }
  
      /**
       * Retrieve the long held, if any.
       *
       * @return the long held if {@link #getValueType()} == {@link #LONG_VALUE_TYPE}, 0 otherwise
       */
      public long getLongValue() {
          try {
              if (mValueType == LONG_VALUE_TYPE) return (Long) mValue;
          } catch (ClassCastException e) {
              Slog.w(TAG, "Failed to successfully get value", e);
          }
          return 0;
      }
  
      /**
       * Retrieve the boolean held, if any.
       *
       * @return the boolean held if {@link #getValueType()} == {@link #BOOLEAN_VALUE_TYPE},
       *         false otherwise
       */
      public boolean getBooleanValue() {
          try {
              if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue;
          } catch (ClassCastException e) {
              Slog.w(TAG, "Failed to successfully get value", e);
          }
          return false;
      }
  
      /**
       * Retrieve the float held, if any.
       *
       * @return the float held if {@link #getValueType()} == {@link #FLOAT_VALUE_TYPE}, 0 otherwise
       */
      public float getFloatValue() {
          try {
              if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue;
          } catch (ClassCastException e) {
              Slog.w(TAG, "Failed to successfully get value", e);
          }
          return 0;
      }
  
      /**
       * Retrieve the tuple, in the form of a {@link List} of {@link StatsDimensionsValue}, held,
       * if any.
       *
       * @return the {@link List} of {@link StatsDimensionsValue} held
       *         if {@link #getValueType()} == {@link #TUPLE_VALUE_TYPE},
       *         null otherwise
       */
      public List<StatsDimensionsValue> getTupleValueList() {
          if (mValueType != TUPLE_VALUE_TYPE) {
              return null;
          }
          try {
              StatsDimensionsValue[] orig = (StatsDimensionsValue[]) mValue;
              List<StatsDimensionsValue> copy = new ArrayList<>(orig.length);
              // Shallow copy since StatsDimensionsValue is immutable anyway
              for (int i = 0; i < orig.length; i++) {
                  copy.add(orig[i]);
              }
              return copy;
          } catch (ClassCastException e) {
              Slog.w(TAG, "Failed to successfully get value", e);
              return null;
          }
      }
  
      /**
       * Returns the constant representing the type of value stored, namely one of
       * <ul>
       *   <li>{@link #STRING_VALUE_TYPE}</li>
       *   <li>{@link #INT_VALUE_TYPE}</li>
       *   <li>{@link #LONG_VALUE_TYPE}</li>
       *   <li>{@link #BOOLEAN_VALUE_TYPE}</li>
       *   <li>{@link #FLOAT_VALUE_TYPE}</li>
       *   <li>{@link #TUPLE_VALUE_TYPE}</li>
       * </ul>
       *
       * @return the constant representing the type of value stored
       */
      public int getValueType() {
          return mValueType;
      }
  
      /**
       * Returns whether the type of value stored is equal to the given type.
       *
       * @param valueType int representing the type of value stored, as used in {@link #getValueType}
       * @return true if {@link #getValueType()} is equal to {@code valueType}.
       */
      public boolean isValueType(int valueType) {
          return mValueType == valueType;
      }
  
      /**
       * Returns a String representing the information in this StatsDimensionValue.
       * No guarantees are made about the format of this String.
       *
       * @return String representation
       *
       * @hide
       */
      // Follows the format of statsd's dimension.h toString.
      public String toString() {
          try {
              StringBuilder sb = new StringBuilder();
              sb.append(mField);
              sb.append(":");
              if (mValueType == TUPLE_VALUE_TYPE) {
                  sb.append("{");
                  StatsDimensionsValue[] sbvs = (StatsDimensionsValue[]) mValue;
                  for (int i = 0; i < sbvs.length; i++) {
                      sb.append(sbvs[i].toString());
                      sb.append("|");
                  }
                  sb.append("}");
              } else {
                  sb.append(mValue.toString());
              }
              return sb.toString();
          } catch (ClassCastException e) {
              Slog.w(TAG, "Failed to successfully get value", e);
          }
          return "";
      }
  
      /**
       * Parcelable Creator for StatsDimensionsValue.
       */
      public static final @android.annotation.NonNull Parcelable.Creator<StatsDimensionsValue> CREATOR = new
              Parcelable.Creator<StatsDimensionsValue>() {
                  public StatsDimensionsValue createFromParcel(Parcel in) {
                      return new StatsDimensionsValue(in);
                  }
  
                  public StatsDimensionsValue[] newArray(int size) {
                      return new StatsDimensionsValue[size];
                  }
              };
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel out, int flags) {
          out.writeInt(mField);
          out.writeInt(mValueType);
          writeValueToParcel(mValueType, mValue, out, flags);
      }
  
      /** Writes mValue to a parcel. Returns true if succeeds. */
      private static boolean writeValueToParcel(int valueType, Object value, Parcel out, int flags) {
          try {
              switch (valueType) {
                  case STRING_VALUE_TYPE:
                      out.writeString((String) value);
                      return true;
                  case INT_VALUE_TYPE:
                      out.writeInt((Integer) value);
                      return true;
                  case LONG_VALUE_TYPE:
                      out.writeLong((Long) value);
                      return true;
                  case BOOLEAN_VALUE_TYPE:
                      out.writeBoolean((Boolean) value);
                      return true;
                  case FLOAT_VALUE_TYPE:
                      out.writeFloat((Float) value);
                      return true;
                  case TUPLE_VALUE_TYPE: {
                      StatsDimensionsValue[] values = (StatsDimensionsValue[]) value;
                      out.writeInt(values.length);
                      for (int i = 0; i < values.length; i++) {
                          values[i].writeToParcel(out, flags);
                      }
                      return true;
                  }
                  default:
                      Slog.w(TAG, "readValue of an impossible type " + valueType);
                      return false;
              }
          } catch (ClassCastException e) {
              Slog.w(TAG, "writeValue cast failed", e);
              return false;
          }
      }
  
      /** Reads mValue from a parcel. */
      private static Object readValueFromParcel(int valueType, Parcel parcel) {
          switch (valueType) {
              case STRING_VALUE_TYPE:
                  return parcel.readString();
              case INT_VALUE_TYPE:
                  return parcel.readInt();
              case LONG_VALUE_TYPE:
                  return parcel.readLong();
              case BOOLEAN_VALUE_TYPE:
                  return parcel.readBoolean();
              case FLOAT_VALUE_TYPE:
                  return parcel.readFloat();
              case TUPLE_VALUE_TYPE: {
                  final int sz = parcel.readInt();
                  StatsDimensionsValue[] values = new StatsDimensionsValue[sz];
                  for (int i = 0; i < sz; i++) {
                      values[i] = new StatsDimensionsValue(parcel);
                  }
                  return values;
              }
              default:
                  Slog.w(TAG, "readValue of an impossible type " + valueType);
                  return null;
          }
      }
  }
  