Merge "Allow annotations to be added to Atom Id" into rvc-dev
diff --git a/apex/framework/test/TEST_MAPPING b/apex/TEST_MAPPING
similarity index 60%
rename from apex/framework/test/TEST_MAPPING
rename to apex/TEST_MAPPING
index f387958..93f1087 100644
--- a/apex/framework/test/TEST_MAPPING
+++ b/apex/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit" : [
     {
       "name" : "FrameworkStatsdTest"
+    },
+    {
+      "name" : "LibStatsPullTests"
     }
   ]
 }
diff --git a/apex/aidl/android/os/StatsDimensionsValueParcel.aidl b/apex/aidl/android/os/StatsDimensionsValueParcel.aidl
index a8685e3..05f78d0 100644
--- a/apex/aidl/android/os/StatsDimensionsValueParcel.aidl
+++ b/apex/aidl/android/os/StatsDimensionsValueParcel.aidl
@@ -4,12 +4,12 @@
  * @hide
  */
 parcelable StatsDimensionsValueParcel {
-    /**
-     * Field equals:
-     *      - atomTag for top level StatsDimensionsValueParcel
-     *      - position in dimension for all other levels
-     */
+    // Field equals atomTag for top level StatsDimensionsValueParcels or
+    // positions in depth (1-indexed) for lower level parcels.
     int field;
+
+    // Indicator for which type of value is stored. Should be set to one
+    // of the constants in StatsDimensionsValue.java.
     int valueType;
 
     String stringValue;
diff --git a/apex/framework/java/android/os/StatsDimensionsValue.java b/apex/framework/java/android/os/StatsDimensionsValue.java
index 35273da..7d9349c 100644
--- a/apex/framework/java/android/os/StatsDimensionsValue.java
+++ b/apex/framework/java/android/os/StatsDimensionsValue.java
@@ -58,7 +58,7 @@
     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.
+    // Keep constants in sync with frameworks/base/cmds/statsd/src/HashableDimensionKey.cpp.
     /** Indicates that this holds a String. */
     public static final int STRING_VALUE_TYPE = 2;
     /** Indicates that this holds an int. */
@@ -72,17 +72,7 @@
     /** 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
+    private final StatsDimensionsValueParcel mInner;
 
     /**
      * Creates a {@code StatsDimensionValue} from a parcel.
@@ -90,59 +80,25 @@
      * @hide
      */
     public StatsDimensionsValue(Parcel in) {
-        mField = in.readInt();
-        mValueType = in.readInt();
-        mValue = readValueFromParcel(mValueType, in);
+        mInner = StatsDimensionsValueParcel.CREATOR.createFromParcel(in);
     }
 
     /**
      * Creates a {@code StatsDimensionsValue} from a StatsDimensionsValueParcel
-     * TODO(b/149103391): Make StatsDimensionsValue a wrapper on top of
-     * StatsDimensionsValueParcel.
      *
      * @hide
      */
     public StatsDimensionsValue(StatsDimensionsValueParcel parcel) {
-        mField = parcel.field;
-        mValueType = parcel.valueType;
-        switch (mValueType) {
-            case STRING_VALUE_TYPE:
-                mValue = parcel.stringValue;
-                break;
-            case INT_VALUE_TYPE:
-                mValue = parcel.intValue;
-                break;
-            case LONG_VALUE_TYPE:
-                mValue = parcel.longValue;
-                break;
-            case BOOLEAN_VALUE_TYPE:
-                mValue = parcel.boolValue;
-                break;
-            case FLOAT_VALUE_TYPE:
-                mValue = parcel.floatValue;
-                break;
-            case TUPLE_VALUE_TYPE:
-                StatsDimensionsValue[] values = new StatsDimensionsValue[parcel.tupleValue.length];
-                for (int i = 0; i < parcel.tupleValue.length; i++) {
-                    values[i] = new StatsDimensionsValue(parcel.tupleValue[i]);
-                }
-                mValue = values;
-                break;
-            default:
-                Log.w(TAG, "StatsDimensionsValueParcel contains bad valueType: " + mValueType);
-                mValue = null;
-                break;
-        }
+        mInner = parcel;
     }
 
-
     /**
      * Return the field, i.e. the tag of a statsd atom.
      *
      * @return the field
      */
     public int getField() {
-        return mField;
+        return mInner.field;
     }
 
     /**
@@ -152,12 +108,12 @@
      *         null otherwise
      */
     public String getStringValue() {
-        try {
-            if (mValueType == STRING_VALUE_TYPE) return (String) mValue;
-        } catch (ClassCastException e) {
-            Log.w(TAG, "Failed to successfully get value", e);
+        if (mInner.valueType == STRING_VALUE_TYPE) {
+            return mInner.stringValue;
+        } else {
+            Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not string.");
+            return null;
         }
-        return null;
     }
 
     /**
@@ -166,12 +122,12 @@
      * @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) {
-            Log.w(TAG, "Failed to successfully get value", e);
+        if (mInner.valueType == INT_VALUE_TYPE) {
+            return mInner.intValue;
+        } else {
+            Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not int.");
+            return 0;
         }
-        return 0;
     }
 
     /**
@@ -180,12 +136,12 @@
      * @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) {
-            Log.w(TAG, "Failed to successfully get value", e);
+        if (mInner.valueType == LONG_VALUE_TYPE) {
+            return mInner.longValue;
+        } else {
+            Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not long.");
+            return 0;
         }
-        return 0;
     }
 
     /**
@@ -195,12 +151,12 @@
      *         false otherwise
      */
     public boolean getBooleanValue() {
-        try {
-            if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue;
-        } catch (ClassCastException e) {
-            Log.w(TAG, "Failed to successfully get value", e);
+        if (mInner.valueType == BOOLEAN_VALUE_TYPE) {
+            return mInner.boolValue;
+        } else {
+            Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not boolean.");
+            return false;
         }
-        return false;
     }
 
     /**
@@ -209,12 +165,12 @@
      * @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) {
-            Log.w(TAG, "Failed to successfully get value", e);
+        if (mInner.valueType == FLOAT_VALUE_TYPE) {
+            return mInner.floatValue;
+        } else {
+            Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not float.");
+            return 0;
         }
-        return 0;
     }
 
     /**
@@ -226,19 +182,15 @@
      *         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]);
+        if (mInner.valueType == TUPLE_VALUE_TYPE) {
+            int length = (mInner.tupleValue == null) ? 0 : mInner.tupleValue.length;
+            List<StatsDimensionsValue> tupleValues = new ArrayList<>(length);
+            for (int i = 0; i < length; i++) {
+                tupleValues.add(new StatsDimensionsValue(mInner.tupleValue[i]));
             }
-            return copy;
-        } catch (ClassCastException e) {
-            Log.w(TAG, "Failed to successfully get value", e);
+            return tupleValues;
+        } else {
+            Log.w(TAG, "Value type is " + getValueTypeAsString() + ", not tuple.");
             return null;
         }
     }
@@ -257,7 +209,7 @@
      * @return the constant representing the type of value stored
      */
     public int getValueType() {
-        return mValueType;
+        return mInner.valueType;
     }
 
     /**
@@ -267,7 +219,7 @@
      * @return true if {@link #getValueType()} is equal to {@code valueType}.
      */
     public boolean isValueType(int valueType) {
-        return mValueType == valueType;
+        return mInner.valueType == valueType;
     }
 
     /**
@@ -280,26 +232,40 @@
      */
     // 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) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(mInner.field);
+        sb.append(":");
+        switch (mInner.valueType) {
+            case STRING_VALUE_TYPE:
+                sb.append(mInner.stringValue);
+                break;
+            case INT_VALUE_TYPE:
+                sb.append(String.valueOf(mInner.intValue));
+                break;
+            case LONG_VALUE_TYPE:
+                sb.append(String.valueOf(mInner.longValue));
+                break;
+            case BOOLEAN_VALUE_TYPE:
+                sb.append(String.valueOf(mInner.boolValue));
+                break;
+            case FLOAT_VALUE_TYPE:
+                sb.append(String.valueOf(mInner.floatValue));
+                break;
+            case TUPLE_VALUE_TYPE:
                 sb.append("{");
-                StatsDimensionsValue[] sbvs = (StatsDimensionsValue[]) mValue;
-                for (int i = 0; i < sbvs.length; i++) {
-                    sb.append(sbvs[i].toString());
+                int length = (mInner.tupleValue == null) ? 0 : mInner.tupleValue.length;
+                for (int i = 0; i < length; i++) {
+                    StatsDimensionsValue child = new StatsDimensionsValue(mInner.tupleValue[i]);
+                    sb.append(child.toString());
                     sb.append("|");
                 }
                 sb.append("}");
-            } else {
-                sb.append(mValue.toString());
-            }
-            return sb.toString();
-        } catch (ClassCastException e) {
-            Log.w(TAG, "Failed to successfully get value", e);
+                break;
+            default:
+                Log.w(TAG, "Incorrect value type");
+                break;
         }
-        return "";
+        return sb.toString();
     }
 
     /**
@@ -324,72 +290,28 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeInt(mField);
-        out.writeInt(mValueType);
-        writeValueToParcel(mValueType, mValue, out, flags);
+        mInner.writeToParcel(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:
-                    Log.w(TAG, "readValue of an impossible type " + valueType);
-                    return false;
-            }
-        } catch (ClassCastException e) {
-            Log.w(TAG, "writeValue cast failed", e);
-            return false;
-        }
-    }
-
-    /** Reads mValue from a parcel. */
-    private static Object readValueFromParcel(int valueType, Parcel parcel) {
-        switch (valueType) {
+    /**
+     * Returns a string representation of the type of value stored.
+     */
+    private String getValueTypeAsString() {
+        switch (mInner.valueType) {
             case STRING_VALUE_TYPE:
-                return parcel.readString();
+                return "string";
             case INT_VALUE_TYPE:
-                return parcel.readInt();
+                return "int";
             case LONG_VALUE_TYPE:
-                return parcel.readLong();
+                return "long";
             case BOOLEAN_VALUE_TYPE:
-                return parcel.readBoolean();
+                return "boolean";
             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;
-            }
+                return "float";
+            case TUPLE_VALUE_TYPE:
+                return "tuple";
             default:
-                Log.w(TAG, "readValue of an impossible type " + valueType);
-                return null;
+                return "unknown";
         }
     }
 }
diff --git a/apex/framework/test/src/android/os/StatsDimensionsValueTest.java b/apex/framework/test/src/android/os/StatsDimensionsValueTest.java
new file mode 100644
index 0000000..db25911
--- /dev/null
+++ b/apex/framework/test/src/android/os/StatsDimensionsValueTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 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 static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public final class StatsDimensionsValueTest {
+
+    @Test
+    public void testConversionFromStructuredParcel() {
+        int tupleField = 100; // atom id
+        String stringValue = "Hello";
+        int intValue = 123;
+        long longValue = 123456789L;
+        float floatValue = 1.1f;
+        boolean boolValue = true;
+
+        // Construct structured parcel
+        StatsDimensionsValueParcel sdvp = new StatsDimensionsValueParcel();
+        sdvp.field = tupleField;
+        sdvp.valueType = StatsDimensionsValue.TUPLE_VALUE_TYPE;
+        sdvp.tupleValue = new StatsDimensionsValueParcel[5];
+
+        for (int i = 0; i < 5; i++) {
+            sdvp.tupleValue[i] = new StatsDimensionsValueParcel();
+            sdvp.tupleValue[i].field = i + 1;
+        }
+
+        sdvp.tupleValue[0].valueType = StatsDimensionsValue.STRING_VALUE_TYPE;
+        sdvp.tupleValue[1].valueType = StatsDimensionsValue.INT_VALUE_TYPE;
+        sdvp.tupleValue[2].valueType = StatsDimensionsValue.LONG_VALUE_TYPE;
+        sdvp.tupleValue[3].valueType = StatsDimensionsValue.FLOAT_VALUE_TYPE;
+        sdvp.tupleValue[4].valueType = StatsDimensionsValue.BOOLEAN_VALUE_TYPE;
+
+        sdvp.tupleValue[0].stringValue = stringValue;
+        sdvp.tupleValue[1].intValue = intValue;
+        sdvp.tupleValue[2].longValue = longValue;
+        sdvp.tupleValue[3].floatValue = floatValue;
+        sdvp.tupleValue[4].boolValue = boolValue;
+
+        // Convert to StatsDimensionsValue and check result
+        StatsDimensionsValue sdv = new StatsDimensionsValue(sdvp);
+
+        assertThat(sdv.getField()).isEqualTo(tupleField);
+        assertThat(sdv.getValueType()).isEqualTo(StatsDimensionsValue.TUPLE_VALUE_TYPE);
+        List<StatsDimensionsValue> sdvChildren = sdv.getTupleValueList();
+        assertThat(sdvChildren.size()).isEqualTo(5);
+
+        for (int i = 0; i < 5; i++) {
+            assertThat(sdvChildren.get(i).getField()).isEqualTo(i + 1);
+        }
+
+        assertThat(sdvChildren.get(0).getValueType())
+              .isEqualTo(StatsDimensionsValue.STRING_VALUE_TYPE);
+        assertThat(sdvChildren.get(1).getValueType())
+              .isEqualTo(StatsDimensionsValue.INT_VALUE_TYPE);
+        assertThat(sdvChildren.get(2).getValueType())
+              .isEqualTo(StatsDimensionsValue.LONG_VALUE_TYPE);
+        assertThat(sdvChildren.get(3).getValueType())
+              .isEqualTo(StatsDimensionsValue.FLOAT_VALUE_TYPE);
+        assertThat(sdvChildren.get(4).getValueType())
+              .isEqualTo(StatsDimensionsValue.BOOLEAN_VALUE_TYPE);
+
+        assertThat(sdvChildren.get(0).getStringValue()).isEqualTo(stringValue);
+        assertThat(sdvChildren.get(1).getIntValue()).isEqualTo(intValue);
+        assertThat(sdvChildren.get(2).getLongValue()).isEqualTo(longValue);
+        assertThat(sdvChildren.get(3).getFloatValue()).isEqualTo(floatValue);
+        assertThat(sdvChildren.get(4).getBooleanValue()).isEqualTo(boolValue);
+
+        // Ensure that StatsDimensionsValue and StatsDimensionsValueParcel are
+        // parceled equivalently
+        Parcel sdvpParcel = Parcel.obtain();
+        Parcel sdvParcel = Parcel.obtain();
+        sdvp.writeToParcel(sdvpParcel, 0);
+        sdv.writeToParcel(sdvParcel, 0);
+        assertThat(sdvpParcel.dataSize()).isEqualTo(sdvParcel.dataSize());
+    }
+
+    @Test
+    public void testNullTupleArray() {
+        int tupleField = 100; // atom id
+
+        StatsDimensionsValueParcel parcel = new StatsDimensionsValueParcel();
+        parcel.field = tupleField;
+        parcel.valueType = StatsDimensionsValue.TUPLE_VALUE_TYPE;
+        parcel.tupleValue = null;
+
+        StatsDimensionsValue sdv = new StatsDimensionsValue(parcel);
+        assertThat(sdv.getField()).isEqualTo(tupleField);
+        assertThat(sdv.getValueType()).isEqualTo(StatsDimensionsValue.TUPLE_VALUE_TYPE);
+        List<StatsDimensionsValue> sdvChildren = sdv.getTupleValueList();
+        assertThat(sdvChildren.size()).isEqualTo(0);
+    }
+}
diff --git a/apex/tests/libstatspull/TEST_MAPPING b/apex/tests/libstatspull/TEST_MAPPING
deleted file mode 100644
index 5e1178c..0000000
--- a/apex/tests/libstatspull/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit" : [
-    {
-      "name" : "LibStatsPullTests"
-    }
-  ]
-}
\ No newline at end of file