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 */
16package android.os;
17
18import android.annotation.SystemApi;
19import android.util.Slog;
20
21import java.util.ArrayList;
22import java.util.List;
23
24/**
25 * Container for statsd dimension value information, corresponding to a
26 * stats_log.proto's DimensionValue.
27 *
28 * This consists of a field (an int representing a statsd atom field)
29 * and a value (which may be one of a number of types).
30 *
31 * <p>
32 * Only a single value is held, and it is necessarily one of the following types:
33 * {@link String}, int, long, boolean, float,
34 * or tuple (i.e. {@link List} of {@code StatsDimensionsValue}).
35 *
36 * The type of value held can be retrieved using {@link #getValueType()}, which returns one of the
37 * following ints, depending on the type of value:
38 * <ul>
39 *  <li>{@link #STRING_VALUE_TYPE}</li>
40 *  <li>{@link #INT_VALUE_TYPE}</li>
41 *  <li>{@link #LONG_VALUE_TYPE}</li>
42 *  <li>{@link #BOOLEAN_VALUE_TYPE}</li>
43 *  <li>{@link #FLOAT_VALUE_TYPE}</li>
44 *  <li>{@link #TUPLE_VALUE_TYPE}</li>
45 * </ul>
46 * Alternatively, this can be determined using {@link #isValueType(int)} with one of these constants
47 * as a parameter.
48 * The value itself can be retrieved using the correct get...Value() function for its type.
49 *
50 * <p>
51 * The field is always an int, and always exists; it can be obtained using {@link #getField()}.
52 *
53 *
54 * @hide
55 */
56@SystemApi
57public final class StatsDimensionsValue implements Parcelable {
58    private static final String TAG = "StatsDimensionsValue";
59
60    // Values of the value type correspond to stats_log.proto's DimensionValue fields.
61    // Keep constants in sync with services/include/android/os/StatsDimensionsValue.h.
62    /** Indicates that this holds a String. */
63    public static final int STRING_VALUE_TYPE = 2;
64    /** Indicates that this holds an int. */
65    public static final int INT_VALUE_TYPE = 3;
66    /** Indicates that this holds a long. */
67    public static final int LONG_VALUE_TYPE = 4;
68    /** Indicates that this holds a boolean. */
69    public static final int BOOLEAN_VALUE_TYPE = 5;
70    /** Indicates that this holds a float. */
71    public static final int FLOAT_VALUE_TYPE = 6;
72    /** Indicates that this holds a List of StatsDimensionsValues. */
73    public static final int TUPLE_VALUE_TYPE = 7;
74
75    /** Value of a stats_log.proto DimensionsValue.field. */
76    private final int mField;
77
78    /** Type of stats_log.proto DimensionsValue.value, according to the VALUE_TYPEs above. */
79    private final int mValueType;
80
81    /**
82     * Value of a stats_log.proto DimensionsValue.value.
83     * String, Integer, Long, Boolean, Float, or StatsDimensionsValue[].
84     */
85    private final Object mValue; // immutable or array of immutables
86
87    /**
88     * Creates a {@code StatsDimensionValue} from a parcel.
89     *
90     * @hide
91     */
92    public StatsDimensionsValue(Parcel in) {
93        mField = in.readInt();
94        mValueType = in.readInt();
95        mValue = readValueFromParcel(mValueType, in);
96    }
97
98    /**
99     * Return the field, i.e. the tag of a statsd atom.
100     *
101     * @return the field
102     */
103    public int getField() {
104        return mField;
105    }
106
107    /**
108     * Retrieve the String held, if any.
109     *
110     * @return the {@link String} held if {@link #getValueType()} == {@link #STRING_VALUE_TYPE},
111     *         null otherwise
112     */
113    public String getStringValue() {
114        try {
115            if (mValueType == STRING_VALUE_TYPE) return (String) mValue;
116        } catch (ClassCastException e) {
117            Slog.w(TAG, "Failed to successfully get value", e);
118        }
119        return null;
120    }
121
122    /**
123     * Retrieve the int held, if any.
124     *
125     * @return the int held if {@link #getValueType()} == {@link #INT_VALUE_TYPE}, 0 otherwise
126     */
127    public int getIntValue() {
128        try {
129            if (mValueType == INT_VALUE_TYPE) return (Integer) mValue;
130        } catch (ClassCastException e) {
131            Slog.w(TAG, "Failed to successfully get value", e);
132        }
133        return 0;
134    }
135
136    /**
137     * Retrieve the long held, if any.
138     *
139     * @return the long held if {@link #getValueType()} == {@link #LONG_VALUE_TYPE}, 0 otherwise
140     */
141    public long getLongValue() {
142        try {
143            if (mValueType == LONG_VALUE_TYPE) return (Long) mValue;
144        } catch (ClassCastException e) {
145            Slog.w(TAG, "Failed to successfully get value", e);
146        }
147        return 0;
148    }
149
150    /**
151     * Retrieve the boolean held, if any.
152     *
153     * @return the boolean held if {@link #getValueType()} == {@link #BOOLEAN_VALUE_TYPE},
154     *         false otherwise
155     */
156    public boolean getBooleanValue() {
157        try {
158            if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue;
159        } catch (ClassCastException e) {
160            Slog.w(TAG, "Failed to successfully get value", e);
161        }
162        return false;
163    }
164
165    /**
166     * Retrieve the float held, if any.
167     *
168     * @return the float held if {@link #getValueType()} == {@link #FLOAT_VALUE_TYPE}, 0 otherwise
169     */
170    public float getFloatValue() {
171        try {
172            if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue;
173        } catch (ClassCastException e) {
174            Slog.w(TAG, "Failed to successfully get value", e);
175        }
176        return 0;
177    }
178
179    /**
180     * Retrieve the tuple, in the form of a {@link List} of {@link StatsDimensionsValue}, held,
181     * if any.
182     *
183     * @return the {@link List} of {@link StatsDimensionsValue} held
184     *         if {@link #getValueType()} == {@link #TUPLE_VALUE_TYPE},
185     *         null otherwise
186     */
187    public List<StatsDimensionsValue> getTupleValueList() {
188        if (mValueType != TUPLE_VALUE_TYPE) {
189            return null;
190        }
191        try {
192            StatsDimensionsValue[] orig = (StatsDimensionsValue[]) mValue;
193            List<StatsDimensionsValue> copy = new ArrayList<>(orig.length);
194            // Shallow copy since StatsDimensionsValue is immutable anyway
195            for (int i = 0; i < orig.length; i++) {
196                copy.add(orig[i]);
197            }
198            return copy;
199        } catch (ClassCastException e) {
200            Slog.w(TAG, "Failed to successfully get value", e);
201            return null;
202        }
203    }
204
205    /**
206     * Returns the constant representing the type of value stored, namely one of
207     * <ul>
208     *   <li>{@link #STRING_VALUE_TYPE}</li>
209     *   <li>{@link #INT_VALUE_TYPE}</li>
210     *   <li>{@link #LONG_VALUE_TYPE}</li>
211     *   <li>{@link #BOOLEAN_VALUE_TYPE}</li>
212     *   <li>{@link #FLOAT_VALUE_TYPE}</li>
213     *   <li>{@link #TUPLE_VALUE_TYPE}</li>
214     * </ul>
215     *
216     * @return the constant representing the type of value stored
217     */
218    public int getValueType() {
219        return mValueType;
220    }
221
222    /**
223     * Returns whether the type of value stored is equal to the given type.
224     *
225     * @param valueType int representing the type of value stored, as used in {@link #getValueType}
226     * @return true if {@link #getValueType()} is equal to {@code valueType}.
227     */
228    public boolean isValueType(int valueType) {
229        return mValueType == valueType;
230    }
231
232    /**
233     * Returns a String representing the information in this StatsDimensionValue.
234     * No guarantees are made about the format of this String.
235     *
236     * @return String representation
237     *
238     * @hide
239     */
240    // Follows the format of statsd's dimension.h toString.
241    public String toString() {
242        try {
243            StringBuilder sb = new StringBuilder();
244            sb.append(mField);
245            sb.append(":");
246            if (mValueType == TUPLE_VALUE_TYPE) {
247                sb.append("{");
248                StatsDimensionsValue[] sbvs = (StatsDimensionsValue[]) mValue;
249                for (int i = 0; i < sbvs.length; i++) {
250                    sb.append(sbvs[i].toString());
251                    sb.append("|");
252                }
253                sb.append("}");
254            } else {
255                sb.append(mValue.toString());
256            }
257            return sb.toString();
258        } catch (ClassCastException e) {
259            Slog.w(TAG, "Failed to successfully get value", e);
260        }
261        return "";
262    }
263
264    /**
265     * Parcelable Creator for StatsDimensionsValue.
266     */
267    public static final Parcelable.Creator<StatsDimensionsValue> CREATOR = new
268            Parcelable.Creator<StatsDimensionsValue>() {
269                public StatsDimensionsValue createFromParcel(Parcel in) {
270                    return new StatsDimensionsValue(in);
271                }
272
273                public StatsDimensionsValue[] newArray(int size) {
274                    return new StatsDimensionsValue[size];
275                }
276            };
277
278    @Override
279    public int describeContents() {
280        return 0;
281    }
282
283    @Override
284    public void writeToParcel(Parcel out, int flags) {
285        out.writeInt(mField);
286        out.writeInt(mValueType);
287        writeValueToParcel(mValueType, mValue, out, flags);
288    }
289
290    /** Writes mValue to a parcel. Returns true if succeeds. */
291    private static boolean writeValueToParcel(int valueType, Object value, Parcel out, int flags) {
292        try {
293            switch (valueType) {
294                case STRING_VALUE_TYPE:
295                    out.writeString((String) value);
296                    return true;
297                case INT_VALUE_TYPE:
298                    out.writeInt((Integer) value);
299                    return true;
300                case LONG_VALUE_TYPE:
301                    out.writeLong((Long) value);
302                    return true;
303                case BOOLEAN_VALUE_TYPE:
304                    out.writeBoolean((Boolean) value);
305                    return true;
306                case FLOAT_VALUE_TYPE:
307                    out.writeFloat((Float) value);
308                    return true;
309                case TUPLE_VALUE_TYPE: {
310                    StatsDimensionsValue[] values = (StatsDimensionsValue[]) value;
311                    out.writeInt(values.length);
312                    for (int i = 0; i < values.length; i++) {
313                        values[i].writeToParcel(out, flags);
314                    }
315                    return true;
316                }
317                default:
318                    Slog.w(TAG, "readValue of an impossible type " + valueType);
319                    return false;
320            }
321        } catch (ClassCastException e) {
322            Slog.w(TAG, "writeValue cast failed", e);
323            return false;
324        }
325    }
326
327    /** Reads mValue from a parcel. */
328    private static Object readValueFromParcel(int valueType, Parcel parcel) {
329        switch (valueType) {
330            case STRING_VALUE_TYPE:
331                return parcel.readString();
332            case INT_VALUE_TYPE:
333                return parcel.readInt();
334            case LONG_VALUE_TYPE:
335                return parcel.readLong();
336            case BOOLEAN_VALUE_TYPE:
337                return parcel.readBoolean();
338            case FLOAT_VALUE_TYPE:
339                return parcel.readFloat();
340            case TUPLE_VALUE_TYPE: {
341                final int sz = parcel.readInt();
342                StatsDimensionsValue[] values = new StatsDimensionsValue[sz];
343                for (int i = 0; i < sz; i++) {
344                    values[i] = new StatsDimensionsValue(parcel);
345                }
346                return values;
347            }
348            default:
349                Slog.w(TAG, "readValue of an impossible type " + valueType);
350                return null;
351        }
352    }
353}
354