[automerger skipped] Added unit tests for optimization_atom_matcher_map flag am: 5e81a7c7cf am: db058ed654 -s ours

am skip reason: Merged-In I34693cf09ca19c0b98267b25530e23396462d968 with SHA-1 5e81a7c7cf is already in history

Original change: https://android-review.googlesource.com/c/platform/packages/modules/StatsD/+/2107856

Change-Id: Iac5eee8a1fe865cad154e624dac7e53777a142d8
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/framework/test/unittests/Android.bp b/framework/test/unittests/Android.bp
index e3fdfb9..243328c 100644
--- a/framework/test/unittests/Android.bp
+++ b/framework/test/unittests/Android.bp
@@ -24,6 +24,7 @@
     static_libs: [
         "androidx.test.rules",
         "truth-prebuilt",
+        "modules-utils-build",
     ],
     libs: [
         "android.test.runner.stubs",
diff --git a/framework/test/unittests/src/android/util/StatsEventTest.java b/framework/test/unittests/src/android/util/StatsEventTest.java
index ded21c3..6d7e92d 100644
--- a/framework/test/unittests/src/android/util/StatsEventTest.java
+++ b/framework/test/unittests/src/android/util/StatsEventTest.java
@@ -18,22 +18,18 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
-
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.os.SystemClock;
-
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
-
+import com.android.modules.utils.build.SdkLevel;
 import com.google.common.collect.Range;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Random;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Internal tests for {@link StatsEvent}.
@@ -421,6 +417,11 @@
 
     @Test
     public void testBoolArrayIntArrayLongArray() {
+        // Skip test if build version isn't T or greater.
+        if (!SdkLevel.isAtLeastT()) {
+            return;
+        }
+
         final int expectedAtomId = 109;
         final boolean[] field1 = new boolean[] {true, false, false};
         final int[] field1Converted = new int[] {1, 0, 0};
@@ -525,6 +526,11 @@
 
     @Test
     public void testFloatArrayStringArray() {
+        // Skip test if build version isn't T or greater.
+        if (!SdkLevel.isAtLeastT()) {
+            return;
+        }
+
         final int expectedAtomId = 109;
         final float[] field1 = new float[] {0.21f, 0.13f};
         final String[] field2 = new String[] {"str1", "str2", "str3"};
@@ -690,6 +696,11 @@
 
     @Test
     public void testArrayFieldAnnotations() {
+        // Skip test if build version isn't T or greater.
+        if (!SdkLevel.isAtLeastT()) {
+            return;
+        }
+
         final int expectedAtomId = 109;
         final int[] field1 = new int[] {4, 11};
         final byte boolAnnotationId = 45;
diff --git a/lib/libstatssocket/include/stats_event.h b/lib/libstatssocket/include/stats_event.h
index 3d3baf9..23e1419 100644
--- a/lib/libstatssocket/include/stats_event.h
+++ b/lib/libstatssocket/include/stats_event.h
@@ -141,6 +141,49 @@
                                        const char* const* tags, uint8_t numNodes);
 
 /**
+ * Write a int32 array field to this StatsEvent.
+ *
+ * Max size of array is 127. If exceeded, array is not written and ERROR_LIST_TOO_LONG is appended
+ * to StatsEvent.
+ **/
+void AStatsEvent_writeInt32Array(AStatsEvent* event, const int32_t* elements, size_t numElements);
+
+/**
+ * Write a int64 array field to this StatsEvent.
+ *
+ * Max size of array is 127. If exceeded, array is not written and ERROR_LIST_TOO_LONG is appended
+ * to StatsEvent.
+ **/
+void AStatsEvent_writeInt64Array(AStatsEvent* event, const int64_t* elements, size_t numElements);
+
+/**
+ * Write a float array field to this StatsEvent.
+ *
+ * Max size of array is 127. If exceeded, array is not written and ERROR_LIST_TOO_LONG is appended
+ * to StatsEvent.
+ **/
+void AStatsEvent_writeFloatArray(AStatsEvent* event, const float* elements, size_t numElements);
+
+/**
+ * Write a bool array field to this StatsEvent.
+ *
+ * Max size of array is 127. If exceeded, array is not written and ERROR_LIST_TOO_LONG is appended
+ * to StatsEvent.
+ **/
+void AStatsEvent_writeBoolArray(AStatsEvent* event, const bool* elements, size_t numElements);
+
+/**
+ * Write a string array field to this StatsEvent.
+ *
+ * String array encoding is UTF8.
+ *
+ * Strings must be null terminated. Max size of array is 127. If exceeded, array is not written and
+ * ERROR_LIST_TOO_LONG is appended to StatsEvent.
+ **/
+void AStatsEvent_writeStringArray(AStatsEvent* event, const char* const* elements,
+                                  size_t numElements);
+
+/**
  * Write a bool annotation for the previous field written.
  **/
 void AStatsEvent_addBoolAnnotation(AStatsEvent* event, uint8_t annotationId, bool value);
diff --git a/lib/libstatssocket/libstatssocket.map.txt b/lib/libstatssocket/libstatssocket.map.txt
index 5c13904..aa6eb30 100644
--- a/lib/libstatssocket/libstatssocket.map.txt
+++ b/lib/libstatssocket/libstatssocket.map.txt
@@ -12,6 +12,11 @@
         AStatsEvent_writeByteArray; # apex # introduced=30
         AStatsEvent_writeString; # apex # introduced=30
         AStatsEvent_writeAttributionChain; # apex # introduced=30
+        AStatsEvent_writeInt32Array; # apex # introduced=Tiramisu
+        AStatsEvent_writeInt64Array; # apex # introduced=Tiramisu
+        AStatsEvent_writeFloatArray; # apex # introduced=Tiramisu
+        AStatsEvent_writeBoolArray; # apex # introduced=Tiramisu
+        AStatsEvent_writeStringArray; # apex # introduced=Tiramisu
         AStatsEvent_addBoolAnnotation; # apex # introduced=30
         AStatsEvent_addInt32Annotation; # apex # introduced=30
         AStatsSocket_close; # apex # introduced=30
diff --git a/lib/libstatssocket/stats_event.c b/lib/libstatssocket/stats_event.c
index dcd34aa..9bb4c52 100644
--- a/lib/libstatssocket/stats_event.c
+++ b/lib/libstatssocket/stats_event.c
@@ -50,6 +50,7 @@
 #define ERROR_INVALID_VALUE_TYPE 0x400
 #define ERROR_STRING_NOT_NULL_TERMINATED 0x800
 #define ERROR_ATOM_ID_INVALID_POSITION 0x2000
+#define ERROR_LIST_TOO_LONG 0x4000
 
 /* TYPE IDS */
 #define INT32_TYPE 0x00
@@ -264,6 +265,69 @@
     }
 }
 
+static bool writeArrayMetadata(AStatsEvent* event, size_t numElements, uint8_t elementTypeId) {
+    if (numElements > MAX_BYTE_VALUE) {
+        event->errors |= ERROR_LIST_TOO_LONG;
+        return false;
+    }
+
+    start_field(event, LIST_TYPE);
+    append_byte(event, numElements);
+    append_byte(event, elementTypeId);
+    return true;
+}
+
+void AStatsEvent_writeInt32Array(AStatsEvent* event, const int32_t* elements, size_t numElements) {
+    if (!writeArrayMetadata(event, numElements, INT32_TYPE)) {
+        return;
+    }
+
+    for (size_t i = 0; i < numElements; i++) {
+        append_int32(event, elements[i]);
+    }
+}
+
+void AStatsEvent_writeInt64Array(AStatsEvent* event, const int64_t* elements, size_t numElements) {
+    if (!writeArrayMetadata(event, numElements, INT64_TYPE)) {
+        return;
+    }
+
+    for (size_t i = 0; i < numElements; i++) {
+        append_int64(event, elements[i]);
+    }
+}
+
+void AStatsEvent_writeFloatArray(AStatsEvent* event, const float* elements, size_t numElements) {
+    if (!writeArrayMetadata(event, numElements, FLOAT_TYPE)) {
+        return;
+    }
+
+    for (size_t i = 0; i < numElements; i++) {
+        append_float(event, elements[i]);
+    }
+}
+
+void AStatsEvent_writeBoolArray(AStatsEvent* event, const bool* elements, size_t numElements) {
+    if (!writeArrayMetadata(event, numElements, BOOL_TYPE)) {
+        return;
+    }
+
+    for (size_t i = 0; i < numElements; i++) {
+        append_bool(event, elements[i]);
+    }
+}
+
+void AStatsEvent_writeStringArray(AStatsEvent* event, const char* const* elements,
+                                  size_t numElements) {
+    if (!writeArrayMetadata(event, numElements, STRING_TYPE)) {
+        return;
+    }
+
+    for (size_t i = 0; i < numElements; i++) {
+        append_string(event, elements[i] == NULL ? "" : elements[i]);
+    }
+}
+
 // Side-effect: modifies event->errors if field has too many annotations
 static void increment_annotation_count(AStatsEvent* event) {
     uint8_t fieldType = event->buf[event->lastFieldPos] & 0x0F;
diff --git a/lib/libstatssocket/tests/stats_event_test.cpp b/lib/libstatssocket/tests/stats_event_test.cpp
index 2f9ccdc..93a99f1 100644
--- a/lib/libstatssocket/tests/stats_event_test.cpp
+++ b/lib/libstatssocket/tests/stats_event_test.cpp
@@ -33,6 +33,7 @@
 #define ERROR_INVALID_VALUE_TYPE 0x400
 #define ERROR_STRING_NOT_NULL_TERMINATED 0x800
 #define ERROR_ATOM_ID_INVALID_POSITION 0x2000
+#define ERROR_LIST_TOO_LONG 0x4000
 
 /* TYPE IDS */
 #define INT32_TYPE 0x00
@@ -87,6 +88,23 @@
     *buffer += size;  // move buffer past byte array we just read
 }
 
+void checkArrayMetadata(uint8_t** buffer, uint8_t numElements, uint8_t elementTypeId,
+                        uint8_t numAnnotations = 0) {
+    checkTypeHeader(buffer, LIST_TYPE, numAnnotations);
+    EXPECT_EQ(readNext<uint8_t>(buffer), numElements);
+    checkTypeHeader(buffer, elementTypeId);
+}
+
+template <class T>
+void checkScalarArray(uint8_t** buffer, uint8_t numElements, uint8_t elementTypeId,
+                      const T* expectedArrayValues, uint8_t numAnnotations = 0) {
+    checkArrayMetadata(buffer, numElements, elementTypeId, numAnnotations);
+
+    for (int i = 0; i < numElements; i++) {
+        checkScalar(buffer, expectedArrayValues[i]);
+    }
+}
+
 template <class T>
 void checkAnnotation(uint8_t** buffer, uint8_t annotationId, uint8_t typeId, T annotationValue) {
     EXPECT_EQ(readNext<uint8_t>(buffer), annotationId);
@@ -259,6 +277,61 @@
     AStatsEvent_release(event);
 }
 
+TEST(StatsEventTest, TestAllArrays) {
+    uint32_t atomId = 100;
+
+    uint8_t numElements = 3;
+    int32_t int32Array[3] = {3, 6, 9};
+    int64_t int64Array[3] = {1000L, 1001L, 1002L};
+    float floatArray[3] = {0.1f, 0.3f, 0.09f};
+    bool boolArray[3] = {0, 1, 1};
+
+    vector<string> stringArray = {"str1", "str2", "str3"};
+    const char* cStringArray[3];
+    for (int i = 0; i < numElements; i++) {
+        cStringArray[i] = stringArray[i].c_str();
+    }
+
+    int64_t startTime = android::elapsedRealtimeNano();
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, atomId);
+    AStatsEvent_writeInt32Array(event, int32Array, numElements);
+    AStatsEvent_writeInt64Array(event, int64Array, numElements);
+    AStatsEvent_writeFloatArray(event, floatArray, numElements);
+    AStatsEvent_writeBoolArray(event, boolArray, numElements);
+    AStatsEvent_writeStringArray(event, cStringArray, numElements);
+    AStatsEvent_build(event);
+    int64_t endTime = android::elapsedRealtimeNano();
+
+    size_t bufferSize;
+    uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize);
+    uint8_t* bufferEnd = buffer + bufferSize;
+
+    checkMetadata(&buffer, /*numTopLevelElements=*/5, startTime, endTime, atomId);
+
+    // check int32Array element
+    checkScalarArray(&buffer, numElements, INT32_TYPE, int32Array);
+
+    // check int64Array element
+    checkScalarArray(&buffer, numElements, INT64_TYPE, int64Array);
+
+    // check floatArray element
+    checkScalarArray(&buffer, numElements, FLOAT_TYPE, floatArray);
+
+    // check boolArray element
+    checkScalarArray(&buffer, numElements, BOOL_TYPE, boolArray);
+
+    // check stringArray element
+    checkArrayMetadata(&buffer, numElements, STRING_TYPE);
+    for (int i = 0; i < numElements; i++) {
+        checkString(&buffer, stringArray[i]);
+    }
+
+    EXPECT_EQ(buffer, bufferEnd);  // ensure that we have read the entire buffer
+    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
+    AStatsEvent_release(event);
+}
+
 TEST(StatsEventTest, TestAttributionChains) {
     uint32_t atomId = 100;
 
@@ -354,6 +427,43 @@
     AStatsEvent_release(event);
 }
 
+TEST(StatsEventTest, TestArrayFieldAnnotations) {
+    uint32_t atomId = 100;
+
+    // array annotation info
+    uint8_t boolAnnotationId = 1;
+    uint8_t int32AnnotationId = 2;
+    bool boolAnnotationValue = true;
+    int32_t int32AnnotationValue = 4;
+
+    uint8_t numElements = 3;
+    int32_t int32Array[3] = {3, 6, 9};
+
+    int64_t startTime = android::elapsedRealtimeNano();
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, atomId);
+    AStatsEvent_writeInt32Array(event, int32Array, numElements);
+    AStatsEvent_addBoolAnnotation(event, boolAnnotationId, boolAnnotationValue);
+    AStatsEvent_addInt32Annotation(event, int32AnnotationId, int32AnnotationValue);
+    AStatsEvent_build(event);
+    int64_t endTime = android::elapsedRealtimeNano();
+
+    size_t bufferSize;
+    uint8_t* buffer = AStatsEvent_getBuffer(event, &bufferSize);
+    uint8_t* bufferEnd = buffer + bufferSize;
+
+    checkMetadata(&buffer, /*numElements=*/1, startTime, endTime, atomId);
+
+    // check first element
+    checkScalarArray(&buffer, numElements, INT32_TYPE, int32Array, /*numAnnotations=*/2);
+    checkAnnotation(&buffer, boolAnnotationId, BOOL_TYPE, boolAnnotationValue);
+    checkAnnotation(&buffer, int32AnnotationId, INT32_TYPE, int32AnnotationValue);
+
+    EXPECT_EQ(buffer, bufferEnd);  // ensure that we have read the entire buffer
+    EXPECT_EQ(AStatsEvent_getErrors(event), 0);
+    AStatsEvent_release(event);
+}
+
 TEST(StatsEventTest, TestAtomLevelAnnotations) {
     uint32_t atomId = 100;
     // atom-level annotation information
@@ -514,3 +624,43 @@
     EXPECT_EQ(AStatsEvent_getErrors(event), 0);
     AStatsEvent_release(event);
 }
+
+TEST(StatsEventTest, TestAttributionChainTooLongError) {
+    uint32_t atomId = 100;
+    uint8_t numNodes = 128;
+    uint32_t uids[numNodes];
+    vector<string> tags(numNodes);  // storage that cTag elements point to
+    const char* cTags[numNodes];
+    for (int i = 0; i < (int)numNodes; i++) {
+        uids[i] = i;
+        if (0 == i) {
+            tags.push_back("");
+            cTags[i] = nullptr;
+        } else {
+            tags.push_back("test" + std::to_string(i));
+            cTags[i] = tags[i].c_str();
+        }
+    }
+
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, atomId);
+    AStatsEvent_writeAttributionChain(event, uids, cTags, numNodes);
+    AStatsEvent_build(event);
+
+    uint32_t errors = AStatsEvent_getErrors(event);
+    EXPECT_EQ(errors & ERROR_ATTRIBUTION_CHAIN_TOO_LONG, ERROR_ATTRIBUTION_CHAIN_TOO_LONG);
+}
+
+TEST(StatsEventTest, TestListTooLongError) {
+    uint32_t atomId = 100;
+    uint8_t numElements = 128;
+    int32_t int32Array[128] = {1};
+
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, atomId);
+    AStatsEvent_writeInt32Array(event, int32Array, numElements);
+    AStatsEvent_build(event);
+
+    uint32_t errors = AStatsEvent_getErrors(event);
+    EXPECT_EQ(errors & ERROR_LIST_TOO_LONG, ERROR_LIST_TOO_LONG);
+}
diff --git a/statsd/src/FieldValue.cpp b/statsd/src/FieldValue.cpp
index 9e4e450..406c19f 100644
--- a/statsd/src/FieldValue.cpp
+++ b/statsd/src/FieldValue.cpp
@@ -137,6 +137,10 @@
     return fieldValue.mAnnotations.isUidField();
 }
 
+bool isPrimitiveRepeatedField(const Field& field) {
+    return field.getDepth() == 1;
+}
+
 Value::Value(const Value& from) {
     type = from.getType();
     switch (type) {
@@ -496,6 +500,19 @@
     return false;
 }
 
+bool HasPrimitiveRepeatedField(const FieldMatcher& matcher) {
+    for (const auto& child : matcher.child()) {
+        if (child.has_position() && child.child_size() == 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool ShouldUseNestedDimensions(const FieldMatcher& matcher) {
+    return HasPositionALL(matcher) || HasPrimitiveRepeatedField(matcher);
+}
+
 size_t getSize(const std::vector<FieldValue>& fieldValues) {
     size_t totalSize = 0;
     for (const FieldValue& fieldValue : fieldValues) {
diff --git a/statsd/src/FieldValue.h b/statsd/src/FieldValue.h
index 81fe28c..979264c 100644
--- a/statsd/src/FieldValue.h
+++ b/statsd/src/FieldValue.h
@@ -194,13 +194,13 @@
  * It contains all information needed to match one or more leaf node.
  * All information is encoded in a Field(2 ints) and a bit mask(1 int).
  *
- * For example, to match the first/any/last uid field in attribution chain in Atom 10,
+ * For example, to match the first/all/last uid field in attribution chain in Atom 10,
  * we have the following FieldMatcher in statsd_config
  *    FieldMatcher {
  *        field:10
  *         FieldMatcher {
  *              field:1
- *              position: any/last/first
+ *              position: all/last/first
  *              FieldMatcher {
  *                  field:1
  *              }
@@ -210,7 +210,6 @@
  * We translate the FieldMatcher into a Field, and mask
  * First: [Matcher Field] 0x02010101  [Mask]0xff7f7f7f
  * Last:  [Matcher Field] 0x02018001  [Mask]0xff7f807f
- * Any:   [Matcher Field] 0x02010001  [Mask]0xff7f007f
  * All:   [Matcher Field] 0x02010001  [Mask]0xff7f7f7f
  *
  * [To match a log Field with a Matcher] we apply the bit mask to the log Field and check if
@@ -242,15 +241,7 @@
     }
 
     bool hasAllPositionMatcher() const {
-        return mMatcher.getDepth() == 2 && getRawMaskAtDepth(1) == 0x7f;
-    }
-
-    bool hasAnyPositionMatcher(int* prefix) const {
-        if (mMatcher.getDepth() == 2 && mMatcher.getRawPosAtDepth(1) == 0) {
-            (*prefix) = mMatcher.getPrefix(1);
-            return true;
-        }
-        return false;
+        return mMatcher.getDepth() >= 1 && getRawMaskAtDepth(1) == 0x7f;
     }
 
     inline bool operator!=(const Matcher& that) const {
@@ -450,6 +441,8 @@
 
 bool HasPositionANY(const FieldMatcher& matcher);
 bool HasPositionALL(const FieldMatcher& matcher);
+bool HasPrimitiveRepeatedField(const FieldMatcher& matcher);
+bool ShouldUseNestedDimensions(const FieldMatcher& matcher);
 
 bool isAttributionUidField(const FieldValue& value);
 
@@ -460,6 +453,7 @@
 
 bool isAttributionUidField(const Field& field, const Value& value);
 bool isUidField(const FieldValue& fieldValue);
+bool isPrimitiveRepeatedField(const Field& field);
 
 bool equalDimensions(const std::vector<Matcher>& dimension_a,
                      const std::vector<Matcher>& dimension_b);
diff --git a/statsd/src/StatsLogProcessor.cpp b/statsd/src/StatsLogProcessor.cpp
index 1f0f564..b90a5b7 100644
--- a/statsd/src/StatsLogProcessor.cpp
+++ b/statsd/src/StatsLogProcessor.cpp
@@ -139,9 +139,9 @@
 }
 
 void StatsLogProcessor::mapIsolatedUidToHostUidIfNecessaryLocked(LogEvent* event) const {
-    if (std::pair<int, int> indexRange; event->hasAttributionChain(&indexRange)) {
+    if (std::pair<size_t, size_t> indexRange; event->hasAttributionChain(&indexRange)) {
         vector<FieldValue>* const fieldValues = event->getMutableValues();
-        for (int i = indexRange.first; i <= indexRange.second; i++) {
+        for (size_t i = indexRange.first; i <= indexRange.second; i++) {
             FieldValue& fieldValue = fieldValues->at(i);
             if (isAttributionUidField(fieldValue)) {
                 const int hostUid = mUidMap->getHostUidOrSelf(fieldValue.mValue.int_value);
diff --git a/statsd/src/StatsLogProcessor.h b/statsd/src/StatsLogProcessor.h
index 761748c..231b411 100644
--- a/statsd/src/StatsLogProcessor.h
+++ b/statsd/src/StatsLogProcessor.h
@@ -322,6 +322,7 @@
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByFirstUid);
     FRIEND_TEST(AttributionE2eTest, TestAttributionMatchAndSliceByChain);
     FRIEND_TEST(GaugeMetricE2ePushedTest, TestMultipleFieldsForPushedEvent);
+    FRIEND_TEST(GaugeMetricE2ePushedTest, TestRepeatedFieldsForPushedEvent);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvents);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEvent_LateAlarm);
     FRIEND_TEST(GaugeMetricE2ePulledTest, TestRandomSamplePulledEventsWithActivation);
@@ -359,6 +360,7 @@
     FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithMap);
     FRIEND_TEST(CountMetricE2eTest, TestMultipleSlicedStates);
     FRIEND_TEST(CountMetricE2eTest, TestSlicedStateWithPrimaryFields);
+    FRIEND_TEST(CountMetricE2eTest, TestRepeatedFieldsAndEmptyArrays);
 
     FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
     FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
@@ -372,6 +374,7 @@
     FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
     FRIEND_TEST(DurationMetricE2eTest, TestUploadThreshold);
+    FRIEND_TEST(DurationMetricE2eTest, TestConditionOnRepeatedEnumField);
 
     FRIEND_TEST(ValueMetricE2eTest, TestInitialConditionChanges);
     FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
@@ -380,6 +383,9 @@
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithIncorrectDimensions);
+    FRIEND_TEST(ValueMetricE2eTest, TestInitWithValueFieldPositionALL);
+
+    FRIEND_TEST(KllMetricE2eTest, TestInitWithKllFieldPositionALL);
 };
 
 }  // namespace statsd
diff --git a/statsd/src/external/puller_util.cpp b/statsd/src/external/puller_util.cpp
index dc7cdce..b0c68f4 100644
--- a/statsd/src/external/puller_util.cpp
+++ b/statsd/src/external/puller_util.cpp
@@ -51,7 +51,7 @@
                                       int tagId, const vector<int>& additiveFieldsVec) {
     // Check the first LogEvent for attribution chain or a uid field as either all atoms with this
     // tagId have them or none of them do.
-    std::pair<int, int> attrIndexRange;
+    std::pair<size_t, size_t> attrIndexRange;
     const bool hasAttributionChain = data[0]->hasAttributionChain(&attrIndexRange);
     const uint8_t numUidFields = data[0]->getNumUidFields();
 
@@ -68,7 +68,7 @@
         }
         if (hasAttributionChain) {
             vector<FieldValue>* const fieldValues = event->getMutableValues();
-            for (int i = attrIndexRange.first; i <= attrIndexRange.second; i++) {
+            for (size_t i = attrIndexRange.first; i <= attrIndexRange.second; i++) {
                 FieldValue& fieldValue = fieldValues->at(i);
                 if (isAttributionUidField(fieldValue)) {
                     const int hostUid = uidMap->getHostUidOrSelf(fieldValue.mValue.int_value);
@@ -101,12 +101,15 @@
     bool needMerge = true;
 
     // 3. do the merge.
-    // The loop invariant is this: for every event, check if it differs on
-    // non-additive fields, or have different attribution chain length.
-    // If so, no need to merge, add itself to the result.
-    // Otherwise, merge the value onto the one immediately next to it.
+    // The loop invariant is this: for every event,
+    // - check if it has a different length (means different attribution chains or repeated fields)
+    // - check if fields are different
+    // - check if non-additive field values are different (non-additive is default for repeated
+    // fields)
+    // If any are true, no need to merge, add itself to the result. Otherwise, merge the
+    // value onto the one immediately next to it.
     for (int i = 0; i < (int)data.size() - 1; i++) {
-        // Size different, must be different chains.
+        // Size different, must be different chains or repeated fields.
         if (data[i]->size() != data[i + 1]->size()) {
             mergedData.push_back(data[i]);
             continue;
@@ -115,10 +118,16 @@
         vector<FieldValue>* rhsValues = data[i + 1]->getMutableValues();
         needMerge = true;
         for (int p = 0; p < (int)lhsValues->size(); p++) {
-            if ((*lhsValues)[p] != (*rhsValues)[p]) {
+            if ((*lhsValues)[p].mField != (*rhsValues)[p].mField) {
+                needMerge = false;
+                break;
+            }
+            if ((*lhsValues)[p].mValue != (*rhsValues)[p].mValue) {
                 int pos = (*lhsValues)[p].mField.getPosAtDepth(0);
                 // Differ on non-additive field, abort.
-                if (additiveFields.find(pos) == additiveFields.end()) {
+                // Repeated additive fields are treated as non-additive fields.
+                if (isPrimitiveRepeatedField((*lhsValues)[p].mField) ||
+                    (additiveFields.find(pos) == additiveFields.end())) {
                     needMerge = false;
                     break;
                 }
@@ -131,7 +140,9 @@
         // This should be infrequent operation.
         for (int p = 0; p < (int)lhsValues->size(); p++) {
             int pos = (*lhsValues)[p].mField.getPosAtDepth(0);
-            if (additiveFields.find(pos) != additiveFields.end()) {
+            // Don't merge repeated fields.
+            if (!isPrimitiveRepeatedField((*lhsValues)[p].mField) &&
+                (additiveFields.find(pos) != additiveFields.end())) {
                 (*rhsValues)[p].mValue += (*lhsValues)[p].mValue;
             }
         }
diff --git a/statsd/src/logd/LogEvent.cpp b/statsd/src/logd/LogEvent.cpp
index e89d067..398ce3e 100644
--- a/statsd/src/logd/LogEvent.cpp
+++ b/statsd/src/logd/LogEvent.cpp
@@ -39,34 +39,6 @@
 using std::string;
 using std::vector;
 
-// stats_event.h socket types. Keep in sync.
-/* ERRORS */
-#define ERROR_NO_TIMESTAMP 0x1
-#define ERROR_NO_ATOM_ID 0x2
-#define ERROR_OVERFLOW 0x4
-#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
-#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
-#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
-#define ERROR_INVALID_ANNOTATION_ID 0x40
-#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
-#define ERROR_TOO_MANY_ANNOTATIONS 0x100
-#define ERROR_TOO_MANY_FIELDS 0x200
-#define ERROR_INVALID_VALUE_TYPE 0x400
-#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
-
-/* TYPE IDS */
-#define INT32_TYPE 0x00
-#define INT64_TYPE 0x01
-#define STRING_TYPE 0x02
-#define LIST_TYPE 0x03
-#define FLOAT_TYPE 0x04
-#define BOOL_TYPE 0x05
-#define BYTE_ARRAY_TYPE 0x06
-#define OBJECT_TYPE 0x07
-#define KEY_VALUE_PAIRS_TYPE 0x08
-#define ATTRIBUTION_CHAIN_TYPE 0x09
-#define ERROR_TYPE 0x0F
-
 LogEvent::LogEvent(int32_t uid, int32_t pid)
     : mLogdTimestampNs(time(nullptr)), mLogUid(uid), mLogPid(pid) {
 }
@@ -203,8 +175,11 @@
 
 void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last,
                                      uint8_t numAnnotations) {
-    const unsigned int firstUidInChainIndex = mValues.size();
-    const int32_t numNodes = readNextValue<uint8_t>();
+    std::optional<size_t> firstUidInChainIndex = mValues.size();
+    const uint8_t numNodes = readNextValue<uint8_t>();
+
+    if (numNodes > INT8_MAX) mValid = false;
+
     for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) {
         last[1] = (pos[1] == numNodes);
 
@@ -218,39 +193,96 @@
         parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
     }
 
-    if (mValues.size() - 1 > INT8_MAX) {
-        mValid = false;
-    } else if (mValues.size() - 1 > firstUidInChainIndex) {
+    if (mValues.size() > (firstUidInChainIndex.value() + 1)) {
         // At least one node was successfully parsed.
-        mAttributionChainStartIndex = static_cast<int8_t>(firstUidInChainIndex);
-        mAttributionChainEndIndex = static_cast<int8_t>(mValues.size() - 1);
+        mAttributionChainStartIndex = firstUidInChainIndex;
+        mAttributionChainEndIndex = mValues.size() - 1;
+    } else {
+        firstUidInChainIndex = std::nullopt;
+        mValid = false;
     }
 
     if (mValid) {
-        parseAnnotations(numAnnotations, firstUidInChainIndex);
+        parseAnnotations(numAnnotations, /*numElements*/ std::nullopt, firstUidInChainIndex);
     }
 
     pos[1] = pos[2] = 1;
     last[1] = last[2] = false;
 }
 
+void LogEvent::parseArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
+    const uint8_t numElements = readNextValue<uint8_t>();
+    const uint8_t typeInfo = readNextValue<uint8_t>();
+    const uint8_t typeId = getTypeId(typeInfo);
+
+    if (numElements > INT8_MAX) mValid = false;
+
+    for (pos[1] = 1; pos[1] <= numElements; pos[1]++) {
+        last[1] = (pos[1] == numElements);
+
+        // The top-level array is at depth 0, and all of its elements are at depth 1.
+        // Once nested fields are supported, array elements will be at top-level depth + 1.
+
+        switch (typeId) {
+            case INT32_TYPE:
+                parseInt32(pos, /*depth=*/1, last, /*numAnnotations=*/0);
+                break;
+            case INT64_TYPE:
+                parseInt64(pos, /*depth=*/1, last, /*numAnnotations=*/0);
+                break;
+            case FLOAT_TYPE:
+                parseFloat(pos, /*depth=*/1, last, /*numAnnotations=*/0);
+                break;
+            case BOOL_TYPE:
+                parseBool(pos, /*depth=*/1, last, /*numAnnotations=*/0);
+                break;
+            case STRING_TYPE:
+                parseString(pos, /*depth=*/1, last, /*numAnnotations=*/0);
+                break;
+            default:
+                mValid = false;
+                break;
+        }
+    }
+
+    parseAnnotations(numAnnotations, numElements);
+
+    pos[1] = 1;
+    last[1] = false;
+}
+
 // Assumes that mValues is not empty
 bool LogEvent::checkPreviousValueType(Type expected) {
     return mValues[mValues.size() - 1].mValue.getType() == expected;
 }
 
-void LogEvent::parseIsUidAnnotation(uint8_t annotationType) {
-    if (mValues.empty() || mValues.size() - 1 > INT8_MAX || !checkPreviousValueType(INT)
-            || annotationType != BOOL_TYPE) {
+void LogEvent::parseIsUidAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements) {
+    // Need to set numElements if not an array.
+    if (!numElements) {
+        numElements = 1;
+    }
+
+    // If array is empty, skip uid parsing.
+    if (numElements == 0 && annotationType == BOOL_TYPE) {
+        readNextValue<uint8_t>();
+        return;
+    }
+
+    // Allowed types: INT, repeated INT
+    if (numElements > mValues.size() || !checkPreviousValueType(INT) ||
+        annotationType != BOOL_TYPE) {
         mValid = false;
         return;
     }
 
     bool isUid = readNextValue<uint8_t>();
     if (isUid) {
-        mNumUidFields++;
+        mNumUidFields += numElements.value();
     }
-    mValues[mValues.size() - 1].mAnnotations.setUidField(isUid);
+
+    for (int i = 1; i <= numElements; i++) {
+        mValues[mValues.size() - i].mAnnotations.setUidField(isUid);
+    }
 }
 
 void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) {
@@ -262,8 +294,11 @@
     mTruncateTimestamp = readNextValue<uint8_t>();
 }
 
-void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType) {
-    if (mValues.empty() || annotationType != BOOL_TYPE) {
+void LogEvent::parsePrimaryFieldAnnotation(uint8_t annotationType,
+                                           std::optional<uint8_t> numElements,
+                                           std::optional<size_t> firstUidInChainIndex) {
+    // Allowed types: all types except for attribution chains and repeated fields.
+    if (mValues.empty() || annotationType != BOOL_TYPE || firstUidInChainIndex || numElements) {
         mValid = false;
         return;
     }
@@ -273,41 +308,42 @@
 }
 
 void LogEvent::parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType,
-                                                   int firstUidInChainIndex) {
-    if (mValues.empty() || annotationType != BOOL_TYPE || -1 == firstUidInChainIndex) {
+                                                   std::optional<size_t> firstUidInChainIndex) {
+    // Allowed types: attribution chains
+    if (mValues.empty() || annotationType != BOOL_TYPE || !firstUidInChainIndex) {
         mValid = false;
         return;
     }
 
-    if (static_cast<int>(mValues.size() - 1) < firstUidInChainIndex) { // AttributionChain is empty.
+    if (mValues.size() < firstUidInChainIndex.value() + 1) {  // AttributionChain is empty.
         mValid = false;
         android_errorWriteLog(0x534e4554, "174485572");
         return;
     }
 
     const bool primaryField = readNextValue<uint8_t>();
-    mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(primaryField);
+    mValues[firstUidInChainIndex.value()].mAnnotations.setPrimaryField(primaryField);
 }
 
-void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType) {
-    if (mValues.empty() || annotationType != BOOL_TYPE) {
-        mValid = false;
-        return;
-    }
-
-    if (mValues.size() - 1 > INT8_MAX) {
-        android_errorWriteLog(0x534e4554, "174488848");
+void LogEvent::parseExclusiveStateAnnotation(uint8_t annotationType,
+                                             std::optional<uint8_t> numElements) {
+    // Allowed types: INT
+    if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) ||
+        numElements) {
         mValid = false;
         return;
     }
 
     const bool exclusiveState = readNextValue<uint8_t>();
-    mExclusiveStateFieldIndex = static_cast<int8_t>(mValues.size() - 1);
-    mValues[getExclusiveStateFieldIndex()].mAnnotations.setExclusiveState(exclusiveState);
+    mExclusiveStateFieldIndex = mValues.size() - 1;
+    mValues[getExclusiveStateFieldIndex().value()].mAnnotations.setExclusiveState(exclusiveState);
 }
 
-void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) {
-    if (mValues.empty() || annotationType != INT32_TYPE) {
+void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType,
+                                                std::optional<uint8_t> numElements) {
+    // Allowed types: INT
+    if (mValues.empty() || annotationType != INT32_TYPE || !checkPreviousValueType(INT) ||
+        numElements) {
         mValid = false;
         return;
     }
@@ -315,8 +351,11 @@
     mResetState = readNextValue<int32_t>();
 }
 
-void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) {
-    if (mValues.empty() || annotationType != BOOL_TYPE) {
+void LogEvent::parseStateNestedAnnotation(uint8_t annotationType,
+                                          std::optional<uint8_t> numElements) {
+    // Allowed types: INT
+    if (mValues.empty() || annotationType != BOOL_TYPE || !checkPreviousValueType(INT) ||
+        numElements) {
         mValid = false;
         return;
     }
@@ -327,32 +366,34 @@
 
 // firstUidInChainIndex is a default parameter that is only needed when parsing
 // annotations for attribution chains.
-void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) {
+// numElements is a default param that is only needed when parsing annotations for repeated fields
+void LogEvent::parseAnnotations(uint8_t numAnnotations, std::optional<uint8_t> numElements,
+                                std::optional<size_t> firstUidInChainIndex) {
     for (uint8_t i = 0; i < numAnnotations; i++) {
         uint8_t annotationId = readNextValue<uint8_t>();
         uint8_t annotationType = readNextValue<uint8_t>();
 
         switch (annotationId) {
             case ANNOTATION_ID_IS_UID:
-                parseIsUidAnnotation(annotationType);
+                parseIsUidAnnotation(annotationType, numElements);
                 break;
             case ANNOTATION_ID_TRUNCATE_TIMESTAMP:
                 parseTruncateTimestampAnnotation(annotationType);
                 break;
             case ANNOTATION_ID_PRIMARY_FIELD:
-                parsePrimaryFieldAnnotation(annotationType);
+                parsePrimaryFieldAnnotation(annotationType, numElements, firstUidInChainIndex);
                 break;
             case ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID:
                 parsePrimaryFieldFirstUidAnnotation(annotationType, firstUidInChainIndex);
                 break;
             case ANNOTATION_ID_EXCLUSIVE_STATE:
-                parseExclusiveStateAnnotation(annotationType);
+                parseExclusiveStateAnnotation(annotationType, numElements);
                 break;
             case ANNOTATION_ID_TRIGGER_STATE_RESET:
-                parseTriggerStateResetAnnotation(annotationType);
+                parseTriggerStateResetAnnotation(annotationType, numElements);
                 break;
             case ANNOTATION_ID_STATE_NESTED:
-                parseStateNestedAnnotation(annotationType);
+                parseStateNestedAnnotation(annotationType, numElements);
                 break;
             default:
                 mValid = false;
@@ -375,7 +416,7 @@
     if (getTypeId(typeInfo) != OBJECT_TYPE) mValid = false;
 
     uint8_t numElements = readNextValue<uint8_t>();
-    if (numElements < 2 || numElements > 127) mValid = false;
+    if (numElements < 2 || numElements > INT8_MAX) mValid = false;
 
     typeInfo = readNextValue<uint8_t>();
     if (getTypeId(typeInfo) != INT64_TYPE) mValid = false;
@@ -419,6 +460,9 @@
             case ATTRIBUTION_CHAIN_TYPE:
                 parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 break;
+            case LIST_TYPE:
+                parseArray(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                break;
             case ERROR_TYPE:
                 /* mErrorBitmask =*/ readNextValue<int32_t>();
                 mValid = false;
@@ -583,14 +627,14 @@
     writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput);
 }
 
-bool LogEvent::hasAttributionChain(std::pair<int, int>* indexRange) const {
-    if (mAttributionChainStartIndex == -1 || mAttributionChainEndIndex == -1) {
+bool LogEvent::hasAttributionChain(std::pair<size_t, size_t>* indexRange) const {
+    if (!mAttributionChainStartIndex || !mAttributionChainEndIndex) {
         return false;
     }
 
     if (nullptr != indexRange) {
-        indexRange->first = static_cast<int>(mAttributionChainStartIndex);
-        indexRange->second = static_cast<int>(mAttributionChainEndIndex);
+        indexRange->first = mAttributionChainStartIndex.value();
+        indexRange->second = mAttributionChainEndIndex.value();
     }
 
     return true;
diff --git a/statsd/src/logd/LogEvent.h b/statsd/src/logd/LogEvent.h
index 92541d7..88dd9b7 100644
--- a/statsd/src/logd/LogEvent.h
+++ b/statsd/src/logd/LogEvent.h
@@ -16,18 +16,49 @@
 
 #pragma once
 
-#include "FieldValue.h"
-
 #include <android/util/ProtoOutputStream.h>
 #include <private/android_logger.h>
 
+#include <optional>
 #include <string>
 #include <vector>
 
+#include "FieldValue.h"
+
 namespace android {
 namespace os {
 namespace statsd {
 
+// stats_event.h socket types. Keep in sync.
+/* ERRORS */
+#define ERROR_NO_TIMESTAMP 0x1
+#define ERROR_NO_ATOM_ID 0x2
+#define ERROR_OVERFLOW 0x4
+#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
+#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
+#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
+#define ERROR_INVALID_ANNOTATION_ID 0x40
+#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
+#define ERROR_TOO_MANY_ANNOTATIONS 0x100
+#define ERROR_TOO_MANY_FIELDS 0x200
+#define ERROR_INVALID_VALUE_TYPE 0x400
+#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
+#define ERROR_ATOM_ID_INVALID_POSITION 0x2000
+#define ERROR_LIST_TOO_LONG 0x4000
+
+/* TYPE IDS */
+#define INT32_TYPE 0x00
+#define INT64_TYPE 0x01
+#define STRING_TYPE 0x02
+#define LIST_TYPE 0x03
+#define FLOAT_TYPE 0x04
+#define BOOL_TYPE 0x05
+#define BYTE_ARRAY_TYPE 0x06
+#define OBJECT_TYPE 0x07
+#define KEY_VALUE_PAIRS_TYPE 0x08
+#define ATTRIBUTION_CHAIN_TYPE 0x09
+#define ERROR_TYPE 0x0F
+
 struct InstallTrainInfo {
     int64_t trainVersionCode;
     std::string trainName;
@@ -156,20 +187,20 @@
     // Returns whether this LogEvent has an AttributionChain.
     // If it does and indexRange is not a nullptr, populate indexRange with the start and end index
     // of the AttributionChain within mValues.
-    bool hasAttributionChain(std::pair<int, int>* indexRange = nullptr) const;
+    bool hasAttributionChain(std::pair<size_t, size_t>* indexRange = nullptr) const;
 
     // Returns the index of the exclusive state field within the FieldValues vector if
     // an exclusive state exists. If there is no exclusive state field, returns -1.
     //
     // If the index within the atom definition is desired, do the following:
-    //    int vectorIndex = LogEvent.getExclusiveStateFieldIndex();
-    //    if (vectorIndex != -1) {
-    //        FieldValue& v = LogEvent.getValues()[vectorIndex];
+    //    const std::optional<size_t>& vectorIndex = LogEvent.getExclusiveStateFieldIndex();
+    //    if (!vectorIndex) {
+    //        FieldValue& v = LogEvent.getValues()[vectorIndex.value()];
     //        int atomIndex = v.mField.getPosAtDepth(0);
     //    }
     // Note that atomIndex is 1-indexed.
-    inline int getExclusiveStateFieldIndex() const {
-        return static_cast<int>(mExclusiveStateFieldIndex);
+    inline std::optional<size_t> getExclusiveStateFieldIndex() const {
+        return mExclusiveStateFieldIndex;
     }
 
     // If a reset state is not sent in the StatsEvent, returns -1. Note that a
@@ -212,15 +243,20 @@
     void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
     void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
     void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
+    void parseArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
 
-    void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1);
-    void parseIsUidAnnotation(uint8_t annotationType);
+    void parseAnnotations(uint8_t numAnnotations, std::optional<uint8_t> numElements = std::nullopt,
+                          std::optional<size_t> firstUidInChainIndex = std::nullopt);
+    void parseIsUidAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements);
     void parseTruncateTimestampAnnotation(uint8_t annotationType);
-    void parsePrimaryFieldAnnotation(uint8_t annotationType);
-    void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType, int firstUidInChainIndex);
-    void parseExclusiveStateAnnotation(uint8_t annotationType);
-    void parseTriggerStateResetAnnotation(uint8_t annotationType);
-    void parseStateNestedAnnotation(uint8_t annotationType);
+    void parsePrimaryFieldAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements,
+                                     std::optional<size_t> firstUidInChainIndex);
+    void parsePrimaryFieldFirstUidAnnotation(uint8_t annotationType,
+                                             std::optional<size_t> firstUidInChainIndex);
+    void parseExclusiveStateAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements);
+    void parseTriggerStateResetAnnotation(uint8_t annotationType,
+                                          std::optional<uint8_t> numElements);
+    void parseStateNestedAnnotation(uint8_t annotationType, std::optional<uint8_t> numElements);
     bool checkPreviousValueType(Type expected);
 
     /**
@@ -266,10 +302,8 @@
     template <class T>
     void addToValues(int32_t* pos, int32_t depth, T& value, bool* last) {
         Field f = Field(mTagId, pos, depth);
-        // do not decorate last position at depth 0
-        for (int i = 1; i < depth; i++) {
-            if (last[i]) f.decorateLastPos(i);
-        }
+        // only decorate last position for depths with repeated fields (depth 1)
+        if (depth > 0 && last[1]) f.decorateLastPos(1);
 
         Value v = Value(value);
         mValues.push_back(FieldValue(f, v));
@@ -302,13 +336,11 @@
     bool mTruncateTimestamp = false;
     int mResetState = -1;
 
-    uint8_t mNumUidFields = 0;
+    size_t mNumUidFields = 0;
 
-    // Indexes within the FieldValue vector can be stored in 7 bits because
-    // that's the assumption enforced by the encoding used in FieldValue.
-    int8_t mAttributionChainStartIndex = -1;
-    int8_t mAttributionChainEndIndex = -1;
-    int8_t mExclusiveStateFieldIndex = -1;
+    std::optional<size_t> mAttributionChainStartIndex;
+    std::optional<size_t> mAttributionChainEndIndex;
+    std::optional<size_t> mExclusiveStateFieldIndex;
 };
 
 void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
diff --git a/statsd/src/matchers/matcher_util.cpp b/statsd/src/matchers/matcher_util.cpp
index 68c1159..27b9fb2 100644
--- a/statsd/src/matchers/matcher_util.cpp
+++ b/statsd/src/matchers/matcher_util.cpp
@@ -237,18 +237,13 @@
         case FieldValueMatcher::ValueMatcherCase::kNeqAnyString: {
             const auto& str_list = matcher.neq_any_string();
             for (int i = start; i < end; i++) {
-                bool notEqAll = true;
                 for (const auto& str : str_list.str_value()) {
                     if (tryMatchString(uidMap, values[i], str)) {
-                        notEqAll = false;
-                        break;
+                        return false;
                     }
                 }
-                if (notEqAll) {
-                    return true;
-                }
             }
-            return false;
+            return true;
         }
         case FieldValueMatcher::ValueMatcherCase::kEqAnyString: {
             const auto& str_list = matcher.eq_any_string();
diff --git a/statsd/src/metrics/CountMetricProducer.cpp b/statsd/src/metrics/CountMetricProducer.cpp
index 9e003a2..67a7250 100644
--- a/statsd/src/metrics/CountMetricProducer.cpp
+++ b/statsd/src/metrics/CountMetricProducer.cpp
@@ -90,7 +90,7 @@
         mContainANYPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
     }
 
-    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
+    mShouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what());
 
     if (metric.links().size() > 0) {
         for (const auto& link : metric.links()) {
@@ -224,8 +224,8 @@
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
 
-    // Fills the dimension path if not slicing by ALL.
-    if (!mSliceByPositionALL) {
+    // Fills the dimension path if not slicing by a primitive repeated field or position ALL.
+    if (!mShouldUseNestedDimensions) {
         if (!mDimensionsInWhat.empty()) {
             uint64_t dimenPathToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
@@ -244,7 +244,7 @@
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension.
-        if (mSliceByPositionALL) {
+        if (mShouldUseNestedDimensions) {
             uint64_t dimensionToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
             writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
diff --git a/statsd/src/metrics/DurationMetricProducer.cpp b/statsd/src/metrics/DurationMetricProducer.cpp
index add5fa4..5b7a1c1 100644
--- a/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/statsd/src/metrics/DurationMetricProducer.cpp
@@ -117,7 +117,7 @@
         mValid = false;
     }
 
-    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
+    mShouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what());
 
     if (metric.links().size() > 0) {
         for (const auto& link : metric.links()) {
@@ -507,7 +507,7 @@
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
 
-    if (!mSliceByPositionALL) {
+    if (!mShouldUseNestedDimensions) {
         if (!mDimensionsInWhat.empty()) {
             uint64_t dimenPathToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
@@ -528,7 +528,7 @@
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension.
-        if (mSliceByPositionALL) {
+        if (mShouldUseNestedDimensions) {
             uint64_t dimensionToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
             writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
diff --git a/statsd/src/metrics/GaugeMetricProducer.cpp b/statsd/src/metrics/GaugeMetricProducer.cpp
index b4fbe17..4df3e24 100644
--- a/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -128,7 +128,7 @@
         }
         mConditionSliced = true;
     }
-    mSliceByPositionALL = HasPositionALL(metric.dimensions_in_what());
+    mShouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what());
 
     flushIfNeededLocked(startTimeNs);
     // Kicks off the puller immediately.
@@ -256,8 +256,8 @@
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
 
-    // Fills the dimension path if not slicing by ALL.
-    if (!mSliceByPositionALL) {
+    // Fills the dimension path if not slicing by a primitive repeated field or position ALL.
+    if (!mShouldUseNestedDimensions) {
         if (!mDimensionsInWhat.empty()) {
             uint64_t dimenPathToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
@@ -294,7 +294,7 @@
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension.
-        if (mSliceByPositionALL) {
+        if (mShouldUseNestedDimensions) {
             uint64_t dimensionToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
             writeDimensionToProto(dimensionKey.getDimensionKeyInWhat(), str_set, protoOutput);
diff --git a/statsd/src/metrics/MetricProducer.cpp b/statsd/src/metrics/MetricProducer.cpp
index 9a00c87..17aa312 100644
--- a/statsd/src/metrics/MetricProducer.cpp
+++ b/statsd/src/metrics/MetricProducer.cpp
@@ -66,7 +66,7 @@
       mConditionSliced(false),
       mWizard(wizard),
       mContainANYPositionInDimensionsInWhat(false),
-      mSliceByPositionALL(false),
+      mShouldUseNestedDimensions(false),
       mHasLinksToAllConditionDimensionsInTracker(false),
       mEventActivationMap(eventActivationMap),
       mEventDeactivationMap(eventDeactivationMap),
diff --git a/statsd/src/metrics/MetricProducer.h b/statsd/src/metrics/MetricProducer.h
index 435287a..95f8dd4 100644
--- a/statsd/src/metrics/MetricProducer.h
+++ b/statsd/src/metrics/MetricProducer.h
@@ -520,7 +520,9 @@
 
     bool mContainANYPositionInDimensionsInWhat;
 
-    bool mSliceByPositionALL;
+    // Metrics slicing by primitive repeated field and/or position ALL need to use nested
+    // dimensions.
+    bool mShouldUseNestedDimensions;
 
     vector<Matcher> mDimensionsInWhat;  // The dimensions_in_what defined in statsd_config
 
diff --git a/statsd/src/metrics/ValueMetricProducer.cpp b/statsd/src/metrics/ValueMetricProducer.cpp
index dccdbef..50958ca 100644
--- a/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/statsd/src/metrics/ValueMetricProducer.cpp
@@ -101,7 +101,7 @@
         translateFieldMatcher(whatOptions.dimensionsInWhat, &mDimensionsInWhat);
     }
     mContainANYPositionInDimensionsInWhat = whatOptions.containsAnyPositionInDimensionsInWhat;
-    mSliceByPositionALL = whatOptions.sliceByPositionAll;
+    mShouldUseNestedDimensions = whatOptions.shouldUseNestedDimensions;
 
     if (conditionOptions.conditionLinks.size() > 0) {
         for (const auto& link : conditionOptions.conditionLinks) {
@@ -324,8 +324,8 @@
     }
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_TIME_BASE, (long long)mTimeBaseNs);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_BUCKET_SIZE, (long long)mBucketSizeNs);
-    // Fills the dimension path if not slicing by ALL.
-    if (!mSliceByPositionALL) {
+    // Fills the dimension path if not slicing by a primitive repeated field or position ALL.
+    if (!mShouldUseNestedDimensions) {
         if (!mDimensionsInWhat.empty()) {
             uint64_t dimenPathToken =
                     protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_PATH_IN_WHAT);
@@ -364,7 +364,7 @@
                 protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
         // First fill dimension.
-        if (mSliceByPositionALL) {
+        if (mShouldUseNestedDimensions) {
             uint64_t dimensionToken =
                     protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION_IN_WHAT);
             writeDimensionToProto(metricDimensionKey.getDimensionKeyInWhat(), strSet, protoOutput);
diff --git a/statsd/src/metrics/ValueMetricProducer.h b/statsd/src/metrics/ValueMetricProducer.h
index 6a9b931..956580a 100644
--- a/statsd/src/metrics/ValueMetricProducer.h
+++ b/statsd/src/metrics/ValueMetricProducer.h
@@ -83,7 +83,7 @@
 
     struct WhatOptions {
         const bool containsAnyPositionInDimensionsInWhat;
-        const bool sliceByPositionAll;
+        const bool shouldUseNestedDimensions;
         const int whatMatcherIndex;
         const sp<EventMatcherWizard>& matcherWizard;
         const FieldMatcher& dimensionsInWhat;
diff --git a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
index fd211ae..915c4ae 100644
--- a/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
+++ b/statsd/src/metrics/parsing_utils/metrics_manager_util.cpp
@@ -655,6 +655,11 @@
         ALOGE("cannot find \"value_field\" in ValueMetric \"%lld\"", (long long)metric.id());
         return nullopt;
     }
+    if (HasPositionALL(metric.value_field())) {
+        ALOGE("value field with position ALL is not supported. ValueMetric \"%lld\"",
+              (long long)metric.id());
+        return nullopt;
+    }
     std::vector<Matcher> fieldMatchers;
     translateFieldMatcher(metric.value_field(), &fieldMatchers);
     if (fieldMatchers.size() < 1) {
@@ -728,7 +733,7 @@
             MillisToNano(TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), bucketSizeTimeUnit));
 
     const bool containsAnyPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
-    const bool sliceByPositionAll = HasPositionALL(metric.dimensions_in_what());
+    const bool shouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what());
 
     const auto [dimensionSoftLimit, dimensionHardLimit] =
             StatsdStats::getAtomDimensionKeySizeLimits(pullTagId);
@@ -743,8 +748,8 @@
             key, metric, metricHash, {pullTagId, pullerManager},
             {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
              conditionCorrectionThresholdNs, getAppUpgradeBucketSplit(metric)},
-            {containsAnyPositionInDimensionsInWhat, sliceByPositionAll, trackerIndex, matcherWizard,
-             metric.dimensions_in_what(), fieldMatchers},
+            {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, trackerIndex,
+             matcherWizard, metric.dimensions_in_what(), fieldMatchers},
             {conditionIndex, metric.links(), initialConditionCache, wizard},
             {metric.state_link(), slicedStateAtoms, stateGroupMap},
             {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit});
@@ -776,6 +781,11 @@
         ALOGE("cannot find \"kll_field\" in KllMetric \"%lld\"", (long long)metric.id());
         return nullopt;
     }
+    if (HasPositionALL(metric.kll_field())) {
+        ALOGE("kll field with position ALL is not supported. KllMetric \"%lld\"",
+              (long long)metric.id());
+        return nullopt;
+    }
     std::vector<Matcher> fieldMatchers;
     translateFieldMatcher(metric.kll_field(), &fieldMatchers);
     if (fieldMatchers.empty()) {
@@ -845,7 +855,7 @@
             MillisToNano(TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), bucketSizeTimeUnit));
 
     const bool containsAnyPositionInDimensionsInWhat = HasPositionANY(metric.dimensions_in_what());
-    const bool sliceByPositionAll = HasPositionALL(metric.dimensions_in_what());
+    const bool shouldUseNestedDimensions = ShouldUseNestedDimensions(metric.dimensions_in_what());
 
     sp<AtomMatchingTracker> atomMatcher = allAtomMatchingTrackers.at(trackerIndex);
     const int atomTagId = *(atomMatcher->getAtomIds().begin());
@@ -856,8 +866,8 @@
             key, metric, metricHash, {/*pullTagId=*/-1, pullerManager},
             {timeBaseNs, currentTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
              /*conditionCorrectionThresholdNs=*/nullopt, getAppUpgradeBucketSplit(metric)},
-            {containsAnyPositionInDimensionsInWhat, sliceByPositionAll, trackerIndex, matcherWizard,
-             metric.dimensions_in_what(), fieldMatchers},
+            {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions, trackerIndex,
+             matcherWizard, metric.dimensions_in_what(), fieldMatchers},
             {conditionIndex, metric.links(), initialConditionCache, wizard},
             {metric.state_link(), slicedStateAtoms, stateGroupMap},
             {eventActivationMap, eventDeactivationMap}, {dimensionSoftLimit, dimensionHardLimit});
diff --git a/statsd/src/state/StateTracker.cpp b/statsd/src/state/StateTracker.cpp
index 719ff3f..ebba22a 100644
--- a/statsd/src/state/StateTracker.cpp
+++ b/statsd/src/state/StateTracker.cpp
@@ -176,13 +176,13 @@
 }
 
 bool getStateFieldValueFromLogEvent(const LogEvent& event, FieldValue* output) {
-    const int exclusiveStateFieldIndex = event.getExclusiveStateFieldIndex();
-    if (-1 == exclusiveStateFieldIndex) {
+    const std::optional<size_t>& exclusiveStateFieldIndex = event.getExclusiveStateFieldIndex();
+    if (!exclusiveStateFieldIndex) {
         ALOGE("error extracting state from log event. Missing exclusive state field.");
         return false;
     }
 
-    *output = event.getValues()[exclusiveStateFieldIndex];
+    *output = event.getValues()[exclusiveStateFieldIndex.value()];
     return true;
 }
 
diff --git a/statsd/src/stats_log_util.cpp b/statsd/src/stats_log_util.cpp
index 801c081..65eecaa 100644
--- a/statsd/src/stats_log_util.cpp
+++ b/statsd/src/stats_log_util.cpp
@@ -103,7 +103,7 @@
 namespace {
 
 void writeDimensionToProtoHelper(const std::vector<FieldValue>& dims, size_t* index, int depth,
-                                 int prefix, std::set<string> *str_set,
+                                 int prefix, std::set<string>* str_set,
                                  ProtoOutputStream* protoOutput) {
     size_t count = dims.size();
     while (*index < count) {
@@ -116,7 +116,9 @@
             return;
         }
 
-        if (depth == valueDepth && valuePrefix == prefix) {
+        // If valueDepth == 1, we're writing a repeated field. Use fieldNum at depth 0 instead
+        // of valueDepth.
+        if ((depth == valueDepth || valueDepth == 1) && valuePrefix == prefix) {
             uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
                                                  DIMENSIONS_VALUE_TUPLE_VALUE);
             protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum);
@@ -139,9 +141,8 @@
                                            dim.mValue.str_value);
                     } else {
                         str_set->insert(dim.mValue.str_value);
-                        protoOutput->write(
-                                FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH,
-                                (long long)Hash64(dim.mValue.str_value));
+                        protoOutput->write(FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH,
+                                           (long long)Hash64(dim.mValue.str_value));
                     }
                     break;
                 default:
@@ -151,10 +152,10 @@
                 protoOutput->end(token);
             }
             (*index)++;
-        } else if (valueDepth > depth && valuePrefix == prefix) {
+        } else if (valueDepth == depth + 2 && valuePrefix == prefix) {
             // Writing the sub tree
-            uint64_t dimensionToken = protoOutput->start(
-                    FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE);
+            uint64_t dimensionToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                         DIMENSIONS_VALUE_TUPLE_VALUE);
             protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum);
             uint64_t tupleToken =
                     protoOutput->start(FIELD_TYPE_MESSAGE | DIMENSIONS_VALUE_VALUE_TUPLE);
@@ -170,9 +171,8 @@
 }
 
 void writeDimensionLeafToProtoHelper(const std::vector<FieldValue>& dims,
-                                     const int dimensionLeafField,
-                                     size_t* index, int depth,
-                                     int prefix, std::set<string> *str_set,
+                                     const int dimensionLeafField, size_t* index, int depth,
+                                     int prefix, std::set<string>* str_set,
                                      ProtoOutputStream* protoOutput) {
     size_t count = dims.size();
     while (*index < count) {
@@ -184,7 +184,8 @@
             return;
         }
 
-        if (depth == valueDepth && valuePrefix == prefix) {
+        // If valueDepth == 1, we're writing a repeated field.
+        if ((depth == valueDepth || valueDepth == 1) && valuePrefix == prefix) {
             uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
                                                 dimensionLeafField);
             switch (dim.mValue.getType()) {
@@ -206,9 +207,8 @@
                                            dim.mValue.str_value);
                     } else {
                         str_set->insert(dim.mValue.str_value);
-                        protoOutput->write(
-                                FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH,
-                                (long long)Hash64(dim.mValue.str_value));
+                        protoOutput->write(FIELD_TYPE_UINT64 | DIMENSIONS_VALUE_VALUE_STR_HASH,
+                                           (long long)Hash64(dim.mValue.str_value));
                     }
                     break;
                 default:
@@ -218,7 +218,7 @@
                 protoOutput->end(token);
             }
             (*index)++;
-        } else if (valueDepth > depth && valuePrefix == prefix) {
+        } else if (valueDepth == depth + 2 && valuePrefix == prefix) {
             writeDimensionLeafToProtoHelper(dims, dimensionLeafField,
                                             index, valueDepth, dim.mField.getPrefix(valueDepth),
                                             str_set, protoOutput);
@@ -243,7 +243,7 @@
             return;
         }
 
-        if (depth == valueDepth && valuePrefix == prefix) {
+        if ((depth == valueDepth || valueDepth == 1) && valuePrefix == prefix) {
             uint64_t token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
                                                  DIMENSIONS_VALUE_TUPLE_VALUE);
             protoOutput->write(FIELD_TYPE_INT32 | DIMENSIONS_VALUE_FIELD, fieldNum);
@@ -251,7 +251,7 @@
                 protoOutput->end(token);
             }
             (*index)++;
-        } else if (valueDepth > depth && valuePrefix == prefix) {
+        } else if (valueDepth == depth + 2 && valuePrefix == prefix) {
             // Writing the sub tree
             uint64_t dimensionToken = protoOutput->start(
                     FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | DIMENSIONS_VALUE_TUPLE_VALUE);
@@ -312,8 +312,8 @@
 // Supported Atoms format
 // XYZ_Atom {
 //     repeated SubMsg field_1 = 1;
-//     SubMsg2 field_2 = 2;
-//     int32/float/string/int63 field_3 = 3;
+//     repeated int32/float/string/int64 field_2 = 2;
+//     optional int32/float/string/int64 field_3 = 3;
 // }
 // logd's msg format, doesn't allow us to distinguish between the 2 cases below
 // Case (1):
@@ -344,25 +344,31 @@
         const int valueDepth = dim.mField.getDepth();
         const int valuePrefix = dim.mField.getPrefix(depth);
         const int fieldNum = dim.mField.getPosAtDepth(depth);
+        const uint64_t repeatedFieldMask = (valueDepth == 1) ? FIELD_COUNT_REPEATED : 0;
         if (valueDepth > 2) {
             ALOGE("Depth > 2 not supported");
             return;
         }
 
-        if (depth == valueDepth && valuePrefix == prefix) {
+        // If valueDepth == 1, we're writing a repeated field. Use fieldNum at depth 0 instead
+        // of valueDepth.
+        if ((depth == valueDepth || valueDepth == 1) && valuePrefix == prefix) {
             switch (dim.mValue.getType()) {
                 case INT:
-                    protoOutput->write(FIELD_TYPE_INT32 | fieldNum, dim.mValue.int_value);
+                    protoOutput->write(FIELD_TYPE_INT32 | repeatedFieldMask | fieldNum,
+                                       dim.mValue.int_value);
                     break;
                 case LONG:
-                    protoOutput->write(FIELD_TYPE_INT64 | fieldNum,
+                    protoOutput->write(FIELD_TYPE_INT64 | repeatedFieldMask | fieldNum,
                                        (long long)dim.mValue.long_value);
                     break;
                 case FLOAT:
-                    protoOutput->write(FIELD_TYPE_FLOAT | fieldNum, dim.mValue.float_value);
+                    protoOutput->write(FIELD_TYPE_FLOAT | repeatedFieldMask | fieldNum,
+                                       dim.mValue.float_value);
                     break;
                 case STRING: {
-                    protoOutput->write(FIELD_TYPE_STRING | fieldNum, dim.mValue.str_value);
+                    protoOutput->write(FIELD_TYPE_STRING | repeatedFieldMask | fieldNum,
+                                       dim.mValue.str_value);
                     break;
                 }
                 case STORAGE:
@@ -374,15 +380,10 @@
                     break;
             }
             (*index)++;
-        } else if (valueDepth > depth && valuePrefix == prefix) {
+        } else if (valueDepth == depth + 2 && valuePrefix == prefix) {
             // Writing the sub tree
             uint64_t msg_token = 0ULL;
-            if (valueDepth == depth + 2) {
-                msg_token =
-                        protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum);
-            } else if (valueDepth == depth + 1) {
-                msg_token = protoOutput->start(FIELD_TYPE_MESSAGE | fieldNum);
-            }
+            msg_token = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | fieldNum);
             // Directly jump to the leaf value because the repeated position field is implied
             // by the position of the sub msg in the parent field.
             writeFieldValueTreeToStreamHelper(tagId, dims, index, valueDepth,
diff --git a/statsd/tests/FieldValue_test.cpp b/statsd/tests/FieldValue_test.cpp
index f4ac8e5..ff3e54a 100644
--- a/statsd/tests/FieldValue_test.cpp
+++ b/statsd/tests/FieldValue_test.cpp
@@ -34,6 +34,7 @@
 namespace statsd {
 
 namespace {
+
 void makeLogEvent(LogEvent* logEvent, const int32_t atomId, const int64_t timestamp,
                   const vector<int>& attributionUids, const vector<string>& attributionTags,
                   const string& name) {
@@ -59,6 +60,14 @@
 
     parseStatsEventToLogEvent(statsEvent, logEvent);
 }
+
+void makeRepeatedIntLogEvent(LogEvent* logEvent, const int32_t atomId,
+                             const vector<int>& intArray) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_writeInt32Array(statsEvent, intArray.data(), intArray.size());
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
 }  // anonymous namespace
 
 TEST(AtomMatcherTest, TestFieldTranslation) {
@@ -148,6 +157,76 @@
     EXPECT_EQ("some value", output.getValues()[6].mValue.str_value);
 }
 
+TEST(AtomMatcherTest, TestFilterRepeated_FIRST) {
+    FieldMatcher matcher;
+    matcher.set_field(123);
+    FieldMatcher* child = matcher.add_child();
+    child->set_field(1);
+    child->set_position(Position::FIRST);
+
+    vector<Matcher> matchers;
+    translateFieldMatcher(matcher, &matchers);
+
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    vector<int> intArray = {21, 9, 13};
+    makeRepeatedIntLogEvent(&event, 123, intArray);
+
+    HashableDimensionKey output;
+    EXPECT_TRUE(filterValues(matchers, event.getValues(), &output));
+
+    ASSERT_EQ((size_t)1, output.getValues().size());
+    EXPECT_EQ((int32_t)0x01010100, output.getValues()[0].mField.getField());
+    EXPECT_EQ((int32_t)21, output.getValues()[0].mValue.int_value);
+}
+
+TEST(AtomMatcherTest, TestFilterRepeated_LAST) {
+    FieldMatcher matcher;
+    matcher.set_field(123);
+    FieldMatcher* child = matcher.add_child();
+    child->set_field(1);
+    child->set_position(Position::LAST);
+
+    vector<Matcher> matchers;
+    translateFieldMatcher(matcher, &matchers);
+
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    vector<int> intArray = {21, 9, 13};
+    makeRepeatedIntLogEvent(&event, 123, intArray);
+
+    HashableDimensionKey output;
+    EXPECT_TRUE(filterValues(matchers, event.getValues(), &output));
+
+    ASSERT_EQ((size_t)1, output.getValues().size());
+    EXPECT_EQ((int32_t)0x01018000, output.getValues()[0].mField.getField());
+    EXPECT_EQ((int32_t)13, output.getValues()[0].mValue.int_value);
+}
+
+TEST(AtomMatcherTest, TestFilterRepeated_ALL) {
+    FieldMatcher matcher;
+    matcher.set_field(123);
+    FieldMatcher* child = matcher.add_child();
+    child->set_field(1);
+    child->set_position(Position::ALL);
+
+    vector<Matcher> matchers;
+    translateFieldMatcher(matcher, &matchers);
+
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    vector<int> intArray = {21, 9, 13};
+    makeRepeatedIntLogEvent(&event, 123, intArray);
+
+    HashableDimensionKey output;
+    EXPECT_TRUE(filterValues(matchers, event.getValues(), &output));
+
+    ASSERT_EQ((size_t)3, output.getValues().size());
+    EXPECT_EQ((int32_t)0x01010100, output.getValues()[0].mField.getField());
+    EXPECT_EQ((int32_t)21, output.getValues()[0].mValue.int_value);
+    EXPECT_EQ((int32_t)0x01010200, output.getValues()[1].mField.getField());
+    EXPECT_EQ((int32_t)9, output.getValues()[1].mValue.int_value);
+    EXPECT_EQ((int32_t)0x01010300, output.getValues()[2].mField.getField());
+    EXPECT_EQ((int32_t)13, output.getValues()[2].mValue.int_value);
+}
+
 TEST(AtomMatcherTest, TestSubDimension) {
     HashableDimensionKey dim;
 
@@ -230,21 +309,25 @@
 }
 
 TEST(AtomMatcherTest, TestWriteDimensionPath) {
-    for (auto position : {Position::ANY, Position::ALL, Position::FIRST, Position::LAST}) {
+    for (auto position : {Position::ALL, Position::FIRST, Position::LAST}) {
         FieldMatcher matcher1;
         matcher1.set_field(10);
+
+        // Repeated nested fields (attribution chain).
         FieldMatcher* child = matcher1.add_child();
         child->set_field(2);
         child->set_position(position);
         child->add_child()->set_field(1);
         child->add_child()->set_field(3);
 
+        // Primitive field.
         child = matcher1.add_child();
         child->set_field(4);
 
+        // Repeated primitive field.
         child = matcher1.add_child();
         child->set_field(6);
-        child->add_child()->set_field(2);
+        child->set_position(position);
 
         vector<Matcher> matchers;
         translateFieldMatcher(matcher1, &matchers);
@@ -285,9 +368,6 @@
 
         const auto& dim3 = result.value_tuple().dimensions_value(2);
         EXPECT_EQ(6, dim3.field());
-        ASSERT_EQ(1, dim3.value_tuple().dimensions_value_size());
-        const auto& dim31 = dim3.value_tuple().dimensions_value(0);
-        EXPECT_EQ(2, dim31.field());
     }
 }
 
@@ -513,6 +593,48 @@
     EXPECT_EQ(999, atom.num_results());
 }
 
+TEST(AtomMatcherTest, TestWriteAtomWithRepeatedFieldsToProto) {
+    vector<int> intArray = {3, 6};
+    vector<int64_t> longArray = {1000L, 10002L};
+    vector<float> floatArray = {0.3f, 0.09f};
+    vector<string> stringArray = {"str1", "str2"};
+    int boolArrayLength = 2;
+    bool boolArray[boolArrayLength];
+    boolArray[0] = 1;
+    boolArray[1] = 0;
+    vector<bool> boolArrayVector = {1, 0};
+    vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF};
+
+    unique_ptr<LogEvent> event = CreateTestAtomReportedEventVariableRepeatedFields(
+            12345, intArray, longArray, floatArray, stringArray, boolArray, boolArrayLength,
+            enumArray);
+
+    android::util::ProtoOutputStream protoOutput;
+    writeFieldValueTreeToStream(event->GetTagId(), event->getValues(), &protoOutput);
+
+    vector<uint8_t> outData;
+    outData.resize(protoOutput.size());
+    size_t pos = 0;
+    sp<ProtoReader> reader = protoOutput.data();
+    while (reader->readBuffer() != NULL) {
+        size_t toRead = reader->currentToRead();
+        std::memcpy(&(outData[pos]), reader->readBuffer(), toRead);
+        pos += toRead;
+        reader->move(toRead);
+    }
+
+    Atom result;
+    ASSERT_EQ(true, result.ParseFromArray(&outData[0], outData.size()));
+    EXPECT_EQ(Atom::PushedCase::kTestAtomReported, result.pushed_case());
+    TestAtomReported atom = result.test_atom_reported();
+    EXPECT_THAT(atom.repeated_int_field(), ElementsAreArray(intArray));
+    EXPECT_THAT(atom.repeated_long_field(), ElementsAreArray(longArray));
+    EXPECT_THAT(atom.repeated_float_field(), ElementsAreArray(floatArray));
+    EXPECT_THAT(atom.repeated_string_field(), ElementsAreArray(stringArray));
+    EXPECT_THAT(atom.repeated_boolean_field(), ElementsAreArray(boolArrayVector));
+    EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray));
+}
+
 /*
  * Test two Matchers is not a subset of one Matcher.
  * Test one Matcher is subset of two Matchers.
@@ -645,6 +767,31 @@
     EXPECT_FALSE(subsetDimensions(matchers2, matchers1));
 }
 
+TEST(AtomMatcherTest, TestIsPrimitiveRepeatedField) {
+    int pos1[] = {1, 1, 1};  // attribution uid
+    int pos2[] = {1, 1, 2};  // attribution tag
+    int pos3[] = {1, 2, 1};  // attribution uid - second node
+    int pos4[] = {1, 2, 2};  // attribution tag - second node
+    int pos5[] = {2, 1, 1};  // repeated field first element
+    int pos6[] = {2, 2, 1};  // repeated field second element
+    int pos7[] = {3, 1, 1};  // top-level field
+    Field field1(10, pos1, 2);
+    Field field2(10, pos2, 2);
+    Field field3(10, pos3, 2);
+    Field field4(10, pos4, 2);
+    Field field5(10, pos5, 1);
+    Field field6(10, pos6, 1);
+    Field field7(10, pos7, 0);
+
+    EXPECT_FALSE(isPrimitiveRepeatedField(field1));
+    EXPECT_FALSE(isPrimitiveRepeatedField(field2));
+    EXPECT_FALSE(isPrimitiveRepeatedField(field3));
+    EXPECT_FALSE(isPrimitiveRepeatedField(field4));
+    EXPECT_TRUE(isPrimitiveRepeatedField(field5));
+    EXPECT_TRUE(isPrimitiveRepeatedField(field6));
+    EXPECT_FALSE(isPrimitiveRepeatedField(field7));
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/LogEntryMatcher_test.cpp b/statsd/tests/LogEntryMatcher_test.cpp
index 0dc1888..8a4fb19 100644
--- a/statsd/tests/LogEntryMatcher_test.cpp
+++ b/statsd/tests/LogEntryMatcher_test.cpp
@@ -107,6 +107,36 @@
     parseStatsEventToLogEvent(statsEvent, logEvent);
 }
 
+void makeRepeatedIntLogEvent(LogEvent* logEvent, const int32_t atomId,
+                             const vector<int>& intArray) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_writeInt32Array(statsEvent, intArray.data(), intArray.size());
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+void makeRepeatedUidLogEvent(LogEvent* logEvent, const int32_t atomId,
+                             const vector<int>& intArray) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_writeInt32Array(statsEvent, intArray.data(), intArray.size());
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
+void makeRepeatedStringLogEvent(LogEvent* logEvent, const int32_t atomId,
+                                const vector<string>& stringArray) {
+    vector<const char*> cStringArray(stringArray.size());
+    for (int i = 0; i < cStringArray.size(); i++) {
+        cStringArray[i] = stringArray[i].c_str();
+    }
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_writeStringArray(statsEvent, cStringArray.data(), stringArray.size());
+    parseStatsEventToLogEvent(statsEvent, logEvent);
+}
+
 }  // anonymous namespace
 
 TEST(AtomMatcherTest, TestSimpleMatcher) {
@@ -389,11 +419,99 @@
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
 
     // Event has is_uid annotation, but uid maps to different package name.
-    simpleMatcher->mutable_field_value_matcher(0)->set_eq_string("Pkg2");
+    simpleMatcher->mutable_field_value_matcher(0)->set_eq_string(
+            "pkg2");  // package names are normalized
     EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
 }
 
-TEST(AtomMatcherTest, TestNeqAnyStringMatcher) {
+TEST(AtomMatcherTest, TestRepeatedUidFieldMatcher) {
+    sp<UidMap> uidMap = new UidMap();
+    uidMap->updateMap(
+            1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
+            {android::String16("v1"), android::String16("v1"), android::String16("v2"),
+             android::String16("v1"), android::String16("v2")},
+            {android::String16("pkg0"), android::String16("pkg1"), android::String16("pkg1"),
+             android::String16("Pkg2"), android::String16("PkG3")} /* package name list */,
+            {android::String16(""), android::String16(""), android::String16(""),
+             android::String16(""), android::String16("")},
+            /* certificateHash */ {{}, {}, {}, {}, {}});
+
+    // Set up matcher.
+    AtomMatcher matcher;
+    SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+    FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(FIELD_ID_1);
+
+    // No is_uid annotation, no mapping from uid to package name.
+    vector<int> intArray = {1111, 3333, 2222};
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeRepeatedIntLogEvent(&event1, TAG_ID, intArray);
+
+    fieldValueMatcher->set_position(Position::FIRST);
+    fieldValueMatcher->set_eq_string("pkg0");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+
+    fieldValueMatcher->set_position(Position::LAST);
+    fieldValueMatcher->set_eq_string("pkg1");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+
+    fieldValueMatcher->set_position(Position::ANY);
+    fieldValueMatcher->set_eq_string("pkg2");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+
+    // is_uid annotation, mapping from uid to package name.
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeRepeatedUidLogEvent(&event2, TAG_ID, intArray);
+
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+    fieldValueMatcher->set_eq_string("pkg0");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+    fieldValueMatcher->set_eq_string("pkg1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+
+    fieldValueMatcher->set_position(Position::ANY);
+    fieldValueMatcher->set_eq_string("pkg");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+    fieldValueMatcher->set_eq_string("pkg2");  // package names are normalized
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event2));
+}
+
+TEST(AtomMatcherTest, TestNeqAnyStringMatcher_SingleString) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the matcher
+    AtomMatcher matcher;
+    SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+
+    FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(FIELD_ID_1);
+    StringListMatcher* neqStringList = fieldValueMatcher->mutable_neq_any_string();
+    neqStringList->add_str_value("some value");
+    neqStringList->add_str_value("another value");
+
+    // First string matched.
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeStringLogEvent(&event1, TAG_ID, 0, "some value");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event1));
+
+    // Second string matched.
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeStringLogEvent(&event2, TAG_ID, 0, "another value");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event2));
+
+    // No strings matched.
+    LogEvent event3(/*uid=*/0, /*pid=*/0);
+    makeStringLogEvent(&event3, TAG_ID, 0, "foo");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event3));
+}
+
+TEST(AtomMatcherTest, TestNeqAnyStringMatcher_AttributionUids) {
     sp<UidMap> uidMap = new UidMap();
     uidMap->updateMap(
             1, {1111, 1111, 2222, 3333, 3333} /* uid list */, {1, 1, 2, 1, 2} /* version list */,
@@ -571,6 +689,284 @@
     EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
 }
 
+TEST(AtomMatcherTest, TestIntMatcher_EmptyRepeatedField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    makeRepeatedIntLogEvent(&event, TAG_ID, {});
+
+    // Set up the matcher.
+    AtomMatcher matcher;
+    SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+    FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(FIELD_ID_1);
+
+    // Match first int.
+    fieldValueMatcher->set_position(Position::FIRST);
+    fieldValueMatcher->set_eq_int(9);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match last int.
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match any int.
+    fieldValueMatcher->set_position(Position::ANY);
+    fieldValueMatcher->set_eq_int(13);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
+TEST(AtomMatcherTest, TestIntMatcher_RepeatedIntField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    vector<int> intArray = {21, 9};
+    makeRepeatedIntLogEvent(&event, TAG_ID, intArray);
+
+    // Set up the matcher.
+    AtomMatcher matcher;
+    SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+
+    // Match first int.
+    FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(FIELD_ID_1);
+    fieldValueMatcher->set_position(Position::FIRST);
+    fieldValueMatcher->set_eq_int(9);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_eq_int(21);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match last int.
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_eq_int(9);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match any int.
+    fieldValueMatcher->set_position(Position::ANY);
+    fieldValueMatcher->set_eq_int(13);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_eq_int(21);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_eq_int(9);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
+TEST(AtomMatcherTest, TestLtIntMatcher_RepeatedIntField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    vector<int> intArray = {21, 9};
+    makeRepeatedIntLogEvent(&event, TAG_ID, intArray);
+
+    // Set up the matcher.
+    AtomMatcher matcher;
+    SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+
+    // Match first int.
+    FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(FIELD_ID_1);
+    fieldValueMatcher->set_position(Position::FIRST);
+    fieldValueMatcher->set_lt_int(9);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_lt_int(21);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_lt_int(23);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match last int.
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_lt_int(9);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_lt_int(8);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match any int.
+    fieldValueMatcher->set_position(Position::ANY);
+    fieldValueMatcher->set_lt_int(21);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_lt_int(8);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_lt_int(23);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
+TEST(AtomMatcherTest, TestStringMatcher_RepeatedStringField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    vector<string> strArray = {"str1", "str2", "str3"};
+    makeRepeatedStringLogEvent(&event, TAG_ID, strArray);
+
+    // Set up the matcher.
+    AtomMatcher matcher;
+    SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+
+    // Match first int.
+    FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(FIELD_ID_1);
+    fieldValueMatcher->set_position(Position::FIRST);
+    fieldValueMatcher->set_eq_string("str2");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_eq_string("str1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match last int.
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_eq_string("str3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    // Match any int.
+    fieldValueMatcher->set_position(Position::ANY);
+    fieldValueMatcher->set_eq_string("str4");
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_eq_string("str1");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_eq_string("str2");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    fieldValueMatcher->set_eq_string("str3");
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
+TEST(AtomMatcherTest, TestEqAnyStringMatcher_RepeatedStringField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    vector<string> strArray = {"str1", "str2", "str3"};
+    makeRepeatedStringLogEvent(&event, TAG_ID, strArray);
+
+    // Set up the matcher.
+    AtomMatcher matcher;
+    SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+
+    FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(FIELD_ID_1);
+    StringListMatcher* eqStringList = fieldValueMatcher->mutable_eq_any_string();
+
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    eqStringList->add_str_value("str4");
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    eqStringList->add_str_value("str2");
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    eqStringList->add_str_value("str3");
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    eqStringList->add_str_value("str1");
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
+TEST(AtomMatcherTest, TestNeqAnyStringMatcher_RepeatedStringField) {
+    sp<UidMap> uidMap = new UidMap();
+
+    // Set up the log event.
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    vector<string> strArray = {"str1", "str2", "str3"};
+    makeRepeatedStringLogEvent(&event, TAG_ID, strArray);
+
+    // Set up the matcher.
+    AtomMatcher matcher;
+    SimpleAtomMatcher* simpleMatcher = matcher.mutable_simple_atom_matcher();
+    simpleMatcher->set_atom_id(TAG_ID);
+
+    FieldValueMatcher* fieldValueMatcher = simpleMatcher->add_field_value_matcher();
+    fieldValueMatcher->set_field(FIELD_ID_1);
+    StringListMatcher* neqStringList = fieldValueMatcher->mutable_neq_any_string();
+
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    neqStringList->add_str_value("str4");
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    neqStringList->add_str_value("str2");
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    neqStringList->add_str_value("str3");
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_TRUE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+
+    neqStringList->add_str_value("str1");
+    fieldValueMatcher->set_position(Position::FIRST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::LAST);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+    fieldValueMatcher->set_position(Position::ANY);
+    EXPECT_FALSE(matchesSimple(uidMap, *simpleMatcher, event));
+}
+
 TEST(AtomMatcherTest, TestMultiFieldsMatcher) {
     sp<UidMap> uidMap = new UidMap();
     // Set up the matcher
diff --git a/statsd/tests/LogEvent_test.cpp b/statsd/tests/LogEvent_test.cpp
index 4b4c6bc..d87b20b 100644
--- a/statsd/tests/LogEvent_test.cpp
+++ b/statsd/tests/LogEvent_test.cpp
@@ -37,46 +37,111 @@
 Field getField(int32_t tag, const vector<int32_t>& pos, int32_t depth, const vector<bool>& last) {
     Field f(tag, (int32_t*)pos.data(), depth);
 
-    // For loop starts at 1 because the last field at depth 0 is not decorated.
-    for (int i = 1; i < depth; i++) {
-        if (last[i]) f.decorateLastPos(i);
-    }
+    // only decorate last position for depths with repeated fields (depth 1)
+    if (depth > 0 && last[1]) f.decorateLastPos(1);
 
     return f;
 }
 
-void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
-                                         bool annotationValue) {
-    AStatsEvent* statsEvent = AStatsEvent_obtain();
+void createStatsEvent(AStatsEvent* statsEvent, uint8_t typeId) {
     AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
-    AStatsEvent_writeInt32(statsEvent, 10);
+
+    int int32Array[2] = {3, 6};
+    uint32_t uids[] = {1001, 1002};
+    const char* tags[] = {"tag1", "tag2"};
+
+    switch (typeId) {
+        case INT32_TYPE:
+            AStatsEvent_writeInt32(statsEvent, 10);
+            break;
+        case INT64_TYPE:
+            AStatsEvent_writeInt64(statsEvent, 1000L);
+            break;
+        case STRING_TYPE:
+            AStatsEvent_writeString(statsEvent, "test");
+            break;
+        case LIST_TYPE:
+            AStatsEvent_writeInt32Array(statsEvent, int32Array, 2);
+            break;
+        case FLOAT_TYPE:
+            AStatsEvent_writeFloat(statsEvent, 1.3f);
+            break;
+        case BOOL_TYPE:
+            AStatsEvent_writeBool(statsEvent, 1);
+            break;
+        case BYTE_ARRAY_TYPE:
+            AStatsEvent_writeByteArray(statsEvent, (uint8_t*)"test", strlen("test"));
+            break;
+        case ATTRIBUTION_CHAIN_TYPE:
+            AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
+            break;
+        default:
+            break;
+    }
+}
+
+void createFieldWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+                                           bool annotationValue, bool parseBufferResult) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    createStatsEvent(statsEvent, typeId);
     AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
     AStatsEvent_build(statsEvent);
 
     size_t size;
     uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    EXPECT_TRUE(logEvent->parseBuffer(buf, size));
+    EXPECT_EQ(parseBufferResult, logEvent->parseBuffer(buf, size));
 
     AStatsEvent_release(statsEvent);
 }
 
-void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
-                                        int annotationValue) {
+void createFieldWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t typeId, uint8_t annotationId,
+                                          int annotationValue, bool parseBufferResult) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
-    AStatsEvent_writeInt32(statsEvent, 10);
+    createStatsEvent(statsEvent, typeId);
     AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
     AStatsEvent_build(statsEvent);
 
     size_t size;
     uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
-    EXPECT_TRUE(logEvent->parseBuffer(buf, size));
+    EXPECT_EQ(parseBufferResult, logEvent->parseBuffer(buf, size));
 
     AStatsEvent_release(statsEvent);
 }
 
 }  // anonymous namespace
 
+// Setup for parameterized tests.
+class LogEventTestBadAnnotationFieldTypes : public testing::TestWithParam<int> {
+public:
+    static std::string ToString(testing::TestParamInfo<int> info) {
+        switch (info.param) {
+            case INT32_TYPE:
+                return "Int32";
+            case INT64_TYPE:
+                return "Int64";
+            case STRING_TYPE:
+                return "String";
+            case LIST_TYPE:
+                return "List";
+            case FLOAT_TYPE:
+                return "Float";
+            case BYTE_ARRAY_TYPE:
+                return "ByteArray";
+            case ATTRIBUTION_CHAIN_TYPE:
+                return "AttributionChain";
+            default:
+                return "Unknown";
+        }
+    }
+};
+
+// TODO(b/222539899): Add BOOL_TYPE value once parseAnnotations is updated to check specific
+// typeIds. BOOL_TYPE should be a bad field type for is_uid, nested, and reset state annotations.
+INSTANTIATE_TEST_SUITE_P(BadAnnotationFieldTypes, LogEventTestBadAnnotationFieldTypes,
+                         testing::Values(INT32_TYPE, INT64_TYPE, STRING_TYPE, LIST_TYPE, FLOAT_TYPE,
+                                         BYTE_ARRAY_TYPE, ATTRIBUTION_CHAIN_TYPE),
+                         LogEventTestBadAnnotationFieldTypes::ToString);
+
 TEST(LogEventTest, TestPrimitiveParsing) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
@@ -225,6 +290,25 @@
     AStatsEvent_release(event);
 }
 
+TEST(LogEventTest, TestTooManyTopLevelElements) {
+    int32_t numElements = 128;
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+
+    for (int i = 0; i < numElements; i++) {
+        AStatsEvent_writeInt32(event, i);
+    }
+
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+
+    AStatsEvent_release(event);
+}
+
 TEST(LogEventTest, TestAttributionChain) {
     AStatsEvent* event = AStatsEvent_obtain();
     AStatsEvent_setAtomId(event, 100);
@@ -251,7 +335,7 @@
     const vector<FieldValue>& values = logEvent.getValues();
     ASSERT_EQ(4, values.size());  // 2 per attribution node
 
-    std::pair<int, int> attrIndexRange;
+    std::pair<size_t, size_t> attrIndexRange;
     EXPECT_TRUE(logEvent.hasAttributionChain(&attrIndexRange));
     EXPECT_EQ(0, attrIndexRange.first);
     EXPECT_EQ(3, attrIndexRange.second);
@@ -285,9 +369,236 @@
     AStatsEvent_release(event);
 }
 
+TEST(LogEventTest, TestEmptyAttributionChain) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+
+    AStatsEvent_writeAttributionChain(event, {}, {}, 0);
+    AStatsEvent_writeInt32(event, 10);
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+
+    AStatsEvent_release(event);
+}
+
+TEST(LogEventTest, TestAttributionChainTooManyElements) {
+    int32_t numNodes = 128;
+    uint32_t uids[numNodes];
+    vector<string> tags(numNodes);  // storage that cTag elements point to
+    const char* cTags[numNodes];
+
+    for (int i = 0; i < numNodes; i++) {
+        uids[i] = i;
+        tags.push_back("test");
+        cTags[i] = tags[i].c_str();
+    }
+
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeAttributionChain(event, uids, cTags, numNodes);
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+
+    AStatsEvent_release(event);
+}
+
+TEST(LogEventTest, TestArrayParsing) {
+    size_t numElements = 2;
+    int32_t int32Array[2] = {3, 6};
+    int64_t int64Array[2] = {1000L, 1002L};
+    float floatArray[2] = {0.3f, 0.09f};
+    bool boolArray[2] = {0, 1};
+
+    vector<string> stringArray = {"str1", "str2"};
+    const char* cStringArray[2];
+    for (int i = 0; i < numElements; i++) {
+        cStringArray[i] = stringArray[i].c_str();
+    }
+
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32Array(event, int32Array, numElements);
+    AStatsEvent_writeInt64Array(event, int64Array, numElements);
+    AStatsEvent_writeFloatArray(event, floatArray, numElements);
+    AStatsEvent_writeBoolArray(event, boolArray, numElements);
+    AStatsEvent_writeStringArray(event, cStringArray, numElements);
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
+    EXPECT_EQ(100, logEvent.GetTagId());
+    EXPECT_EQ(1000, logEvent.GetUid());
+    EXPECT_EQ(1001, logEvent.GetPid());
+    EXPECT_FALSE(logEvent.hasAttributionChain());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(10, values.size());  // 2 for each array type
+
+    const FieldValue& int32ArrayItem1 = values[0];
+    Field expectedField = getField(100, {1, 1, 1}, 1, {false, false, false});
+    EXPECT_EQ(expectedField, int32ArrayItem1.mField);
+    EXPECT_EQ(Type::INT, int32ArrayItem1.mValue.getType());
+    EXPECT_EQ(3, int32ArrayItem1.mValue.int_value);
+
+    const FieldValue& int32ArrayItem2 = values[1];
+    expectedField = getField(100, {1, 2, 1}, 1, {false, true, false});
+    EXPECT_EQ(expectedField, int32ArrayItem2.mField);
+    EXPECT_EQ(Type::INT, int32ArrayItem2.mValue.getType());
+    EXPECT_EQ(6, int32ArrayItem2.mValue.int_value);
+
+    const FieldValue& int64ArrayItem1 = values[2];
+    expectedField = getField(100, {2, 1, 1}, 1, {false, false, false});
+    EXPECT_EQ(expectedField, int64ArrayItem1.mField);
+    EXPECT_EQ(Type::LONG, int64ArrayItem1.mValue.getType());
+    EXPECT_EQ(1000L, int64ArrayItem1.mValue.long_value);
+
+    const FieldValue& int64ArrayItem2 = values[3];
+    expectedField = getField(100, {2, 2, 1}, 1, {false, true, false});
+    EXPECT_EQ(expectedField, int64ArrayItem2.mField);
+    EXPECT_EQ(Type::LONG, int64ArrayItem2.mValue.getType());
+    EXPECT_EQ(1002L, int64ArrayItem2.mValue.long_value);
+
+    const FieldValue& floatArrayItem1 = values[4];
+    expectedField = getField(100, {3, 1, 1}, 1, {false, false, false});
+    EXPECT_EQ(expectedField, floatArrayItem1.mField);
+    EXPECT_EQ(Type::FLOAT, floatArrayItem1.mValue.getType());
+    EXPECT_EQ(0.3f, floatArrayItem1.mValue.float_value);
+
+    const FieldValue& floatArrayItem2 = values[5];
+    expectedField = getField(100, {3, 2, 1}, 1, {false, true, false});
+    EXPECT_EQ(expectedField, floatArrayItem2.mField);
+    EXPECT_EQ(Type::FLOAT, floatArrayItem2.mValue.getType());
+    EXPECT_EQ(0.09f, floatArrayItem2.mValue.float_value);
+
+    const FieldValue& boolArrayItem1 = values[6];
+    expectedField = getField(100, {4, 1, 1}, 1, {false, false, false});
+    EXPECT_EQ(expectedField, boolArrayItem1.mField);
+    EXPECT_EQ(Type::INT,
+              boolArrayItem1.mValue.getType());  // FieldValue does not support boolean type
+    EXPECT_EQ(false, boolArrayItem1.mValue.int_value);
+
+    const FieldValue& boolArrayItem2 = values[7];
+    expectedField = getField(100, {4, 2, 1}, 1, {false, true, false});
+    EXPECT_EQ(expectedField, boolArrayItem2.mField);
+    EXPECT_EQ(Type::INT,
+              boolArrayItem2.mValue.getType());  // FieldValue does not support boolean type
+    EXPECT_EQ(true, boolArrayItem2.mValue.int_value);
+
+    const FieldValue& stringArrayItem1 = values[8];
+    expectedField = getField(100, {5, 1, 1}, 1, {true, false, false});
+    EXPECT_EQ(expectedField, stringArrayItem1.mField);
+    EXPECT_EQ(Type::STRING, stringArrayItem1.mValue.getType());
+    EXPECT_EQ("str1", stringArrayItem1.mValue.str_value);
+
+    const FieldValue& stringArrayItem2 = values[9];
+    expectedField = getField(100, {5, 2, 1}, 1, {true, true, false});
+    EXPECT_EQ(expectedField, stringArrayItem2.mField);
+    EXPECT_EQ(Type::STRING, stringArrayItem2.mValue.getType());
+    EXPECT_EQ("str2", stringArrayItem2.mValue.str_value);
+}
+
+TEST(LogEventTest, TestEmptyStringArray) {
+    const char* cStringArray[2];
+    string empty = "";
+    cStringArray[0] = empty.c_str();
+    cStringArray[1] = empty.c_str();
+
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeStringArray(event, cStringArray, 2);
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
+    EXPECT_EQ(100, logEvent.GetTagId());
+    EXPECT_EQ(1000, logEvent.GetUid());
+    EXPECT_EQ(1001, logEvent.GetPid());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(2, values.size());
+
+    const FieldValue& stringArrayItem1 = values[0];
+    Field expectedField = getField(100, {1, 1, 1}, 1, {true, false, false});
+    EXPECT_EQ(expectedField, stringArrayItem1.mField);
+    EXPECT_EQ(Type::STRING, stringArrayItem1.mValue.getType());
+    EXPECT_EQ(empty, stringArrayItem1.mValue.str_value);
+
+    const FieldValue& stringArrayItem2 = values[1];
+    expectedField = getField(100, {1, 2, 1}, 1, {true, true, false});
+    EXPECT_EQ(expectedField, stringArrayItem2.mField);
+    EXPECT_EQ(Type::STRING, stringArrayItem2.mValue.getType());
+    EXPECT_EQ(empty, stringArrayItem2.mValue.str_value);
+
+    AStatsEvent_release(event);
+}
+
+TEST(LogEventTest, TestArrayTooManyElements) {
+    int32_t numElements = 128;
+    int32_t int32Array[numElements];
+
+    for (int i = 0; i < numElements; i++) {
+        int32Array[i] = 1;
+    }
+
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32Array(event, int32Array, numElements);
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+
+    AStatsEvent_release(event);
+}
+
+TEST(LogEventTest, TestEmptyArray) {
+    int32_t int32Array[0] = {};
+
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32Array(event, int32Array, 0);
+    AStatsEvent_build(event);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
+
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+
+    EXPECT_EQ(100, logEvent.GetTagId());
+    EXPECT_EQ(1000, logEvent.GetUid());
+    EXPECT_EQ(1001, logEvent.GetPid());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(0, values.size());
+
+    AStatsEvent_release(event);
+}
+
 TEST(LogEventTest, TestAnnotationIdIsUid) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true);
+    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID, true,
+                                          /*parseBufferResult*/ true);
 
     ASSERT_EQ(event.getNumUidFields(), 1);
 
@@ -296,33 +607,214 @@
     EXPECT_TRUE(isUidField(values.at(0)));
 }
 
+TEST(LogEventTest, TestAnnotationIdIsUid_RepeatedIntAndOtherFields) {
+    size_t numElements = 2;
+    int32_t int32Array[2] = {3, 6};
+
+    vector<string> stringArray = {"str1", "str2"};
+    const char* cStringArray[2];
+    for (int i = 0; i < numElements; i++) {
+        cStringArray[i] = stringArray[i].c_str();
+    }
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 100);
+    AStatsEvent_writeInt32(statsEvent, 5);
+    AStatsEvent_writeInt32Array(statsEvent, int32Array, numElements);
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_writeStringArray(statsEvent, cStringArray, numElements);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_EQ(2, logEvent.getNumUidFields());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(values.size(), 5);
+    EXPECT_FALSE(isUidField(values.at(0)));
+    EXPECT_TRUE(isUidField(values.at(1)));
+    EXPECT_TRUE(isUidField(values.at(2)));
+    EXPECT_FALSE(isUidField(values.at(3)));
+    EXPECT_FALSE(isUidField(values.at(4)));
+}
+
+TEST(LogEventTest, TestAnnotationIdIsUid_RepeatedIntOneEntry) {
+    size_t numElements = 1;
+    int32_t int32Array[1] = {3};
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 100);
+    AStatsEvent_writeInt32Array(statsEvent, int32Array, numElements);
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_EQ(1, logEvent.getNumUidFields());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    ASSERT_EQ(values.size(), 1);
+    EXPECT_TRUE(isUidField(values.at(0)));
+}
+
+TEST(LogEventTest, TestAnnotationIdIsUid_EmptyIntArray) {
+    int32_t int32Array[0] = {};
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 100);
+    AStatsEvent_writeInt32Array(statsEvent, int32Array, /*numElements*/ 0);
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_writeInt32(statsEvent, 5);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    EXPECT_EQ(0, logEvent.getNumUidFields());
+
+    const vector<FieldValue>& values = logEvent.getValues();
+    EXPECT_EQ(values.size(), 1);
+}
+
+TEST(LogEventTest, TestAnnotationIdIsUid_BadRepeatedInt64) {
+    int64_t int64Array[2] = {1000L, 1002L};
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+    AStatsEvent_writeInt64Array(statsEvent, int64Array, /*numElements*/ 2);
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+
+    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+    EXPECT_EQ(0, logEvent.getNumUidFields());
+
+    AStatsEvent_release(statsEvent);
+}
+
+TEST(LogEventTest, TestAnnotationIdIsUid_BadRepeatedString) {
+    size_t numElements = 2;
+    vector<string> stringArray = {"str1", "str2"};
+    const char* cStringArray[2];
+    for (int i = 0; i < numElements; i++) {
+        cStringArray[i] = stringArray[i].c_str();
+    }
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+    AStatsEvent_writeStringArray(statsEvent, cStringArray, /*numElements*/ 2);
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
+
+    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
+    EXPECT_EQ(0, logEvent.getNumUidFields());
+
+    AStatsEvent_release(statsEvent);
+}
+
+TEST_P(LogEventTestBadAnnotationFieldTypes, TestAnnotationIdIsUid) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+
+    if (GetParam() != INT32_TYPE && GetParam() != LIST_TYPE) {
+        createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_IS_UID, true,
+                                              /*parseBufferResult*/ false);
+    }
+}
+
+TEST(LogEventTest, TestAnnotationIdIsUid_NotIntAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_IS_UID, 10,
+                                         /*parseBufferResult*/ false);
+}
+
 TEST(LogEventTest, TestAnnotationIdStateNested) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true);
+    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_STATE_NESTED, true,
+                                          /*parseBufferResult*/ true);
 
     const vector<FieldValue>& values = event.getValues();
     ASSERT_EQ(values.size(), 1);
     EXPECT_TRUE(values[0].mAnnotations.isNested());
 }
 
+TEST_P(LogEventTestBadAnnotationFieldTypes, TestAnnotationIdStateNested) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+
+    if (GetParam() != INT32_TYPE) {
+        createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_STATE_NESTED, true,
+                                              /*parseBufferResult*/ false);
+    }
+}
+
+TEST(LogEventTest, TestAnnotationIdStateNested_NotIntAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_STATE_NESTED, 10,
+                                         /*parseBufferResult*/ false);
+}
+
 TEST(LogEventTest, TestPrimaryFieldAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_PRIMARY_FIELD, true);
+    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_PRIMARY_FIELD, true,
+                                          /*parseBufferResult*/ true);
 
     const vector<FieldValue>& values = event.getValues();
     ASSERT_EQ(values.size(), 1);
     EXPECT_TRUE(values[0].mAnnotations.isPrimaryField());
 }
 
+TEST_P(LogEventTestBadAnnotationFieldTypes, TestPrimaryFieldAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+
+    if (GetParam() == LIST_TYPE || GetParam() == ATTRIBUTION_CHAIN_TYPE) {
+        createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_PRIMARY_FIELD, true,
+                                              /*parseBufferResult*/ false);
+    }
+}
+
+TEST(LogEventTest, TestPrimaryFieldAnnotation_NotIntAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_PRIMARY_FIELD, 10,
+                                         /*parseBufferResult*/ false);
+}
+
 TEST(LogEventTest, TestExclusiveStateAnnotation) {
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_EXCLUSIVE_STATE, true);
+    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_EXCLUSIVE_STATE, true,
+                                          /*parseBufferResult*/ true);
 
     const vector<FieldValue>& values = event.getValues();
     ASSERT_EQ(values.size(), 1);
     EXPECT_TRUE(values[0].mAnnotations.isExclusiveState());
 }
 
+TEST_P(LogEventTestBadAnnotationFieldTypes, TestExclusiveStateAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+
+    if (GetParam() != INT32_TYPE) {
+        createFieldWithBoolAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_EXCLUSIVE_STATE,
+                                              true,
+                                              /*parseBufferResult*/ false);
+    }
+}
+
+TEST(LogEventTest, TestExclusiveStateAnnotation_NotIntAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_EXCLUSIVE_STATE, 10,
+                                         /*parseBufferResult*/ false);
+}
+
 TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
     // Event has 10 ints and then an attribution chain
     int numInts = 10;
@@ -355,100 +847,72 @@
     EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField());
 }
 
+TEST_P(LogEventTestBadAnnotationFieldTypes, TestPrimaryFieldFirstUidAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+
+    if (GetParam() != ATTRIBUTION_CHAIN_TYPE) {
+        createFieldWithBoolAnnotationLogEvent(&event, GetParam(),
+                                              ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, true,
+                                              /*parseBufferResult*/ false);
+    }
+}
+
+TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation_NotIntAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createFieldWithIntAnnotationLogEvent(&event, ATTRIBUTION_CHAIN_TYPE,
+                                         ANNOTATION_ID_PRIMARY_FIELD_FIRST_UID, 10,
+                                         /*parseBufferResult*/ false);
+}
+
 TEST(LogEventTest, TestResetStateAnnotation) {
     int32_t resetState = 10;
     LogEvent event(/*uid=*/0, /*pid=*/0);
-    createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_TRIGGER_STATE_RESET, resetState);
+    createFieldWithIntAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_TRIGGER_STATE_RESET,
+                                         resetState, /*parseBufferResult*/ true);
 
     const vector<FieldValue>& values = event.getValues();
     ASSERT_EQ(values.size(), 1);
     EXPECT_EQ(event.getResetState(), resetState);
 }
 
-TEST(LogEventTest, TestExclusiveStateAnnotationAfterTooManyFields) {
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, 100);
+TEST_P(LogEventTestBadAnnotationFieldTypes, TestResetStateAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    int32_t resetState = 10;
 
-    const unsigned int numAttributionNodes = 64;
-
-    uint32_t uids[numAttributionNodes];
-    const char* tags[numAttributionNodes];
-
-    for (unsigned int i = 1; i <= numAttributionNodes; i++) {
-        uids[i-1] = i;
-        tags[i-1] = std::to_string(i).c_str();
+    if (GetParam() != INT32_TYPE) {
+        createFieldWithIntAnnotationLogEvent(&event, GetParam(), ANNOTATION_ID_TRIGGER_STATE_RESET,
+                                             resetState,
+                                             /*parseBufferResult*/ false);
     }
-
-    AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes);
-    AStatsEvent_writeInt32(event, 1);
-    AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_EXCLUSIVE_STATE, true);
-
-    AStatsEvent_build(event);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
-
-    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
-    EXPECT_EQ(-1, logEvent.getExclusiveStateFieldIndex());
-
-    AStatsEvent_release(event);
 }
 
-TEST(LogEventTest, TestUidAnnotationAfterTooManyFields) {
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, 100);
+TEST(LogEventTest, TestResetStateAnnotation_NotBoolAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createFieldWithBoolAnnotationLogEvent(&event, INT32_TYPE, ANNOTATION_ID_TRIGGER_STATE_RESET,
+                                          true,
+                                          /*parseBufferResult*/ false);
+}
 
-    const unsigned int numAttributionNodes = 64;
+TEST(LogEventTest, TestUidAnnotationWithInt8MaxValues) {
+    int32_t numElements = INT8_MAX;
+    int32_t int32Array[numElements];
 
-    uint32_t uids[numAttributionNodes];
-    const char* tags[numAttributionNodes];
-
-    for (unsigned int i = 1; i <= numAttributionNodes; i++) {
-        uids[i-1] = i;
-        tags[i-1] = std::to_string(i).c_str();
+    for (int i = 0; i < numElements; i++) {
+        int32Array[i] = i;
     }
 
-    AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes);
-    AStatsEvent_writeInt32(event, 1);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32Array(event, int32Array, numElements);
+    AStatsEvent_writeInt32(event, 10);
+    AStatsEvent_writeInt32(event, 11);
     AStatsEvent_addBoolAnnotation(event, ANNOTATION_ID_IS_UID, true);
-
     AStatsEvent_build(event);
 
     size_t size;
     uint8_t* buf = AStatsEvent_getBuffer(event, &size);
-
     LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
-    EXPECT_EQ(0, logEvent.getNumUidFields());
-
-    AStatsEvent_release(event);
-}
-
-TEST(LogEventTest, TestAttributionChainEndIndexAfterTooManyFields) {
-    AStatsEvent* event = AStatsEvent_obtain();
-    AStatsEvent_setAtomId(event, 100);
-
-    const unsigned int numAttributionNodes = 65;
-
-    uint32_t uids[numAttributionNodes];
-    const char* tags[numAttributionNodes];
-
-    for (unsigned int i = 1; i <= numAttributionNodes; i++) {
-        uids[i-1] = i;
-        tags[i-1] = std::to_string(i).c_str();
-    }
-
-    AStatsEvent_writeAttributionChain(event, uids, tags, numAttributionNodes);
-
-    AStatsEvent_build(event);
-
-    size_t size;
-    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
-
-    LogEvent logEvent(/*uid=*/1000, /*pid=*/1001);
-    EXPECT_FALSE(logEvent.parseBuffer(buf, size));
-    EXPECT_FALSE(logEvent.hasAttributionChain());
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
 
     AStatsEvent_release(event);
 }
diff --git a/statsd/tests/StatsLogProcessor_test.cpp b/statsd/tests/StatsLogProcessor_test.cpp
index 77597fa..4bca452 100644
--- a/statsd/tests/StatsLogProcessor_test.cpp
+++ b/statsd/tests/StatsLogProcessor_test.cpp
@@ -21,11 +21,11 @@
 
 #include "StatsService.h"
 #include "config/ConfigKey.h"
-#include "src/stats_log.pb.h"
-#include "src/statsd_config.pb.h"
 #include "guardrail/StatsdStats.h"
 #include "logd/LogEvent.h"
 #include "packages/UidMap.h"
+#include "src/stats_log.pb.h"
+#include "src/statsd_config.pb.h"
 #include "statslog_statsdtest.h"
 #include "storage/StorageManager.h"
 #include "tests/statsd_test_util.h"
@@ -1890,6 +1890,86 @@
     EXPECT_EQ(field2, actualFieldValues->at(5).mValue.int_value);
 }
 
+/* *
+ * Test cases for repeated uid fields:
+ * - empty field
+ * - single host uid
+ * - single isolated uid
+ * - multiple host uids
+ * - multiple isolated uids
+ * - multiple host and isolated uids
+ */
+TEST(StatsLogProcessorTest_mapIsolatedUidToHostUid, LogRepeatedUidField) {
+    int hostUid1 = 21;
+    int hostUid2 = 22;
+    int isolatedUid1 = 31;
+    int isolatedUid2 = 32;
+    uint64_t eventTimeNs = 12355;
+    int atomId = 89;
+    int field1 = 90;
+    int field2 = 28;
+    sp<MockUidMap> mockUidMap =
+            makeMockUidMapForHosts({{hostUid1, {isolatedUid1}}, {hostUid2, {isolatedUid2}}});
+
+    ConfigKey cfgKey;
+    StatsdConfig config = MakeConfig(false);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(1, 1, config, cfgKey, nullptr, 0, mockUidMap);
+
+    // Empty repeated uid field.
+    shared_ptr<LogEvent> logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {});
+    processor->OnLogEvent(logEvent.get());
+
+    const vector<FieldValue>* actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(0, actualFieldValues->size());
+
+    // Single host uid.
+    logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {hostUid1});
+    processor->OnLogEvent(logEvent.get());
+
+    actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(1, actualFieldValues->size());
+    EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value);
+
+    // Single isolated uid.
+    logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {isolatedUid1});
+    processor->OnLogEvent(logEvent.get());
+
+    actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(1, actualFieldValues->size());
+    EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value);
+
+    // Multiple host uids.
+    logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {hostUid1, hostUid2});
+    processor->OnLogEvent(logEvent.get());
+
+    actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(2, actualFieldValues->size());
+    EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value);
+
+    // Multiple isolated uids.
+    logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs, {isolatedUid1, isolatedUid2});
+    processor->OnLogEvent(logEvent.get());
+
+    actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(2, actualFieldValues->size());
+    EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value);
+
+    // Multiple host and isolated uids.
+    logEvent = makeRepeatedUidLogEvent(atomId, eventTimeNs,
+                                       {isolatedUid1, hostUid2, isolatedUid2, hostUid1});
+    processor->OnLogEvent(logEvent.get());
+
+    actualFieldValues = &logEvent->getValues();
+    ASSERT_EQ(4, actualFieldValues->size());
+    EXPECT_EQ(hostUid1, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostUid2, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(hostUid1, actualFieldValues->at(3).mValue.int_value);
+}
+
 TEST_P(StatsLogProcessorTest, TestDumpReportWithoutErasingDataDoesNotUpdateTimestamp) {
     int hostUid = 20;
     int isolatedUid = 30;
diff --git a/statsd/tests/e2e/CountMetric_e2e_test.cpp b/statsd/tests/e2e/CountMetric_e2e_test.cpp
index 8ea8861..ef183b7 100644
--- a/statsd/tests/e2e/CountMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -965,6 +965,788 @@
                         3);
 }
 
+TEST(CountMetricE2eTest, TestRepeatedFieldsAndEmptyArrays) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedAtomMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedAtomMatcher;
+
+    int64_t metricId = 123456;
+    CountMetric* countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(testAtomReportedAtomMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    ConfigKey cfgKey(123, 987);
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    vector<int> intArray = {3, 6};
+    vector<int64_t> longArray = {1000L, 10002L};
+    vector<float> floatArray = {0.3f, 0.09f};
+    vector<string> stringArray = {"str1", "str2"};
+    int boolArrayLength = 2;
+    bool boolArray[boolArrayLength];
+    boolArray[0] = 1;
+    boolArray[1] = 0;
+    vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 10 * NS_PER_SEC, intArray, longArray, floatArray, stringArray,
+            boolArray, boolArrayLength, enumArray));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {}));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(1, countMetrics.data_size());
+
+    CountMetricData data = countMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+}
+
+TEST(CountMetricE2eTest, TestMatchRepeatedFieldPositionAny) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedStateAnyOnAtomMatcher =
+            CreateTestAtomRepeatedStateAnyOnAtomMatcher();
+    *config.add_atom_matcher() = testAtomReportedStateAnyOnAtomMatcher;
+
+    int64_t metricId = 123456;
+    CountMetric* countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(testAtomReportedStateAnyOnAtomMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    ConfigKey cfgKey(123, 987);
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    vector<int> enumArrayOnFirst = {TestAtomReported::ON, TestAtomReported::OFF};
+    vector<int> enumArrayOnLast = {TestAtomReported::OFF, TestAtomReported::ON};
+    vector<int> enumArrayNoOn = {TestAtomReported::OFF, TestAtomReported::OFF};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnFirst));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayNoOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 60 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnLast));
+    // No matching is done on empty array.
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 80 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {}));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs * 2 + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(1, countMetrics.data_size());
+
+    CountMetricData data = countMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+}
+
+TEST(CountMetricE2eTest, TestRepeatedFieldDimension_PositionFirst) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedAtomMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedAtomMatcher;
+
+    int64_t metricId = 123456;
+    CountMetric* countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(testAtomReportedAtomMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {14 /*repeated_enum_field*/}, {Position::FIRST});
+
+    // Initialize StatsLogProcessor.
+    ConfigKey cfgKey(2000, 921);
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    vector<int> enumArrayOnOff = {TestAtomReported::ON, TestAtomReported::OFF};
+    vector<int> enumArrayOnOn = {TestAtomReported::ON, TestAtomReported::ON};
+    vector<int> enumArrayOffOn = {TestAtomReported::OFF, TestAtomReported::ON};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOff));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 60 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 80 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {}));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(3, countMetrics.data_size());
+
+    // Empty dimensions case.
+    CountMetricData data = countMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 0);
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(),
+              TestAtomReported::OFF);
+
+    data = countMetrics.data(2);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(),
+              TestAtomReported::ON);
+}
+
+TEST(CountMetricE2eTest, TestRepeatedFieldDimension_PositionLast) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedAtomMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedAtomMatcher;
+
+    int64_t metricId = 123456;
+    CountMetric* countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(testAtomReportedAtomMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {14 /*repeated_enum_field*/}, {Position::LAST});
+
+    // Initialize StatsLogProcessor.
+    ConfigKey cfgKey(2000, 921);
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    vector<int> enumArrayOnOff = {TestAtomReported::ON, TestAtomReported::OFF};
+    vector<int> enumArrayOffOff = {TestAtomReported::OFF, TestAtomReported::OFF};
+    vector<int> enumArrayOffOn = {TestAtomReported::OFF, TestAtomReported::ON};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOff));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOff));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 60 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(2, countMetrics.data_size());
+
+    CountMetricData data = countMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(),
+              TestAtomReported::OFF);
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(),
+              TestAtomReported::ON);
+}
+
+TEST(CountMetricE2eTest, TestRepeatedFieldDimension_PositionAll) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedAtomMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedAtomMatcher;
+
+    int64_t metricId = 123456;
+    CountMetric* countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(testAtomReportedAtomMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {14 /*repeated_enum_field*/}, {Position::ALL});
+
+    // Initialize StatsLogProcessor.
+    ConfigKey cfgKey(2000, 921);
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    vector<int> enumArrayOnOff = {TestAtomReported::ON, TestAtomReported::OFF};
+    vector<int> enumArrayOnOn = {TestAtomReported::ON, TestAtomReported::ON};
+    vector<int> enumArrayOffOn = {TestAtomReported::OFF, TestAtomReported::ON};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOff));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 60 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOnOff));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 40 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOffOn));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    // Don't need to backfill dimension path because dimensions with position ALL are not encoded
+    // with the path format.
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(3, countMetrics.data_size());
+
+    CountMetricData data = countMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        3);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(),
+              TestAtomReported::OFF);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(),
+              TestAtomReported::ON);
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(),
+              TestAtomReported::ON);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(),
+              TestAtomReported::OFF);
+
+    data = countMetrics.data(2);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(),
+              TestAtomReported::ON);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(),
+              TestAtomReported::ON);
+}
+
+TEST(CountMetricE2eTest, TestMultipleRepeatedFieldDimensions_PositionFirst) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedAtomMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedAtomMatcher;
+
+    int64_t metricId = 123456;
+    CountMetric* countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(testAtomReportedAtomMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/, 14 /*repeated_enum_field*/},
+            {Position::FIRST, Position::FIRST});
+
+    // Initialize StatsLogProcessor.
+    ConfigKey cfgKey(2000, 921);
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    vector<int> intArrayThree = {3, 6, 9};
+    vector<int> intArraySix = {6, 9};
+    vector<int> enumArrayOn = {TestAtomReported::ON, TestAtomReported::OFF};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, intArrayThree, {}, {}, {}, {}, 0, enumArrayOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 40 * NS_PER_SEC, intArraySix, {}, {}, {}, {}, 0, enumArrayOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 60 * NS_PER_SEC, intArrayThree, {}, {}, {}, {}, 0, enumArrayOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 80 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayOn));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 100 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {}));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 120 * NS_PER_SEC, intArraySix, {}, {}, {}, {}, 0, {}));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(5, countMetrics.data_size());
+
+    CountMetricData data = countMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 0);
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 6);
+
+    data = countMetrics.data(2);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(),
+              TestAtomReported::ON);
+
+    data = countMetrics.data(3);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 3);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(),
+              TestAtomReported::ON);
+
+    data = countMetrics.data(4);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 6);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(),
+              TestAtomReported::ON);
+}
+
+TEST(CountMetricE2eTest, TestMultipleRepeatedFieldDimensions_PositionAll) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedAtomMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedAtomMatcher;
+
+    int64_t metricId = 123456;
+    CountMetric* countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(testAtomReportedAtomMatcher.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    *countMetric->mutable_dimensions_in_what() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/, 14 /*repeated_enum_field*/},
+            {Position::ALL, Position::ALL});
+
+    // Initialize StatsLogProcessor.
+    ConfigKey cfgKey(2000, 921);
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    vector<int> intArray1 = {3, 6};
+    vector<int> intArray2 = {6, 9};
+    vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, intArray1, {}, {}, {}, {}, 0, enumArray));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 40 * NS_PER_SEC, intArray2, {}, {}, {}, {}, 0, enumArray));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 80 * NS_PER_SEC, intArray1, {}, {}, {}, {}, 0, enumArray));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 100 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArray));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 120 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {}));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 140 * NS_PER_SEC, intArray2, {}, {}, {}, {}, 0, {}));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(5, countMetrics.data_size());
+
+    CountMetricData data = countMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 0);
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 6);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), 9);
+
+    data = countMetrics.data(2);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 2);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(),
+              TestAtomReported::ON);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(),
+              TestAtomReported::OFF);
+
+    data = countMetrics.data(3);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 4);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 3);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), 6);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(2).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(2).value_int(),
+              TestAtomReported::ON);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(3).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(3).value_int(),
+              TestAtomReported::OFF);
+
+    data = countMetrics.data(4);
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    EXPECT_EQ(util::TEST_ATOM_REPORTED, data.dimensions_in_what().field());
+    ASSERT_EQ(data.dimensions_in_what().value_tuple().dimensions_value_size(), 4);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).field(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(0).value_int(), 6);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).field(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(1).value_int(), 9);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(2).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(2).value_int(),
+              TestAtomReported::ON);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(3).field(), 14);
+    EXPECT_EQ(data.dimensions_in_what().value_tuple().dimensions_value(3).value_int(),
+              TestAtomReported::OFF);
+}
+
+TEST(CountMetricE2eTest, TestConditionSlicedByRepeatedUidWithUidDimension) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher uidProcessStateChangedAtomMatcher = CreateUidProcessStateChangedAtomMatcher();
+    AtomMatcher repeatedStateFirstOffAtomMatcher = CreateTestAtomRepeatedStateFirstOffAtomMatcher();
+    AtomMatcher repeatedStateFirstOnAtomMatcher = CreateTestAtomRepeatedStateFirstOnAtomMatcher();
+    *config.add_atom_matcher() = uidProcessStateChangedAtomMatcher;
+    *config.add_atom_matcher() = repeatedStateFirstOffAtomMatcher;
+    *config.add_atom_matcher() = repeatedStateFirstOnAtomMatcher;
+
+    Predicate testAtomRepeatedStateFirstOffPerUidPredicate =
+            CreateTestAtomRepeatedStateFirstOffPredicate();
+    FieldMatcher* dimensions =
+            testAtomRepeatedStateFirstOffPerUidPredicate.mutable_simple_predicate()
+                    ->mutable_dimensions();
+    *dimensions = CreateRepeatedDimensions(util::TEST_ATOM_REPORTED, {9 /* repeated uid*/},
+                                           {Position::FIRST});
+    *config.add_predicate() = testAtomRepeatedStateFirstOffPerUidPredicate;
+
+    int64_t metricId = 123456;
+    CountMetric* countMetric = config.add_count_metric();
+    countMetric->set_id(metricId);
+    countMetric->set_what(uidProcessStateChangedAtomMatcher.id());
+    countMetric->set_condition(testAtomRepeatedStateFirstOffPerUidPredicate.id());
+    countMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    *countMetric->mutable_dimensions_in_what() =
+            CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /*uid*/});
+    MetricConditionLink* links = countMetric->add_links();
+    links->set_condition(testAtomRepeatedStateFirstOffPerUidPredicate.id());
+    *links->mutable_fields_in_what() =
+            CreateDimensions(util::UID_PROCESS_STATE_CHANGED, {1 /* uid*/});
+    *links->mutable_fields_in_condition() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /* repeated uid*/}, {Position::FIRST});
+
+    // Initialize StatsLogProcessor.
+    ConfigKey cfgKey(2000, 921);
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    const uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000LL;
+    const uint64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    vector<int> intArray1 = {1, 2};
+    vector<int> intArray2 = {2, 1};
+    vector<int> enumArrayOn = {TestAtomReported::ON, TestAtomReported::OFF};
+    vector<int> enumArrayOff = {TestAtomReported::OFF, TestAtomReported::ON};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    // Set condition to true for uid 1.
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, intArray1, {}, {}, {}, {}, 0, enumArrayOff));
+
+    // Uid 1 process state changed.
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 40 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));
+    // Uid 2 process state changed. Should not be counted.
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 60 * NS_PER_SEC, 2 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));
+
+    // Set condition to true for uid 2.
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 80 * NS_PER_SEC, intArray2, {}, {}, {}, {}, 0, enumArrayOff));
+    // Uid 1 process state changed.
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 100 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));
+    // Uid 2 process state changed.
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC, 2 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));
+
+    // Bucket 2
+    // Set condition to false for uid 1.
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucket2StartTimeNs + 20 * NS_PER_SEC, intArray1, {}, {}, {}, {}, 0, enumArrayOn));
+    // Uid 1 process state changed. Should not be counted.
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucket2StartTimeNs + 40 * NS_PER_SEC, 1 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));
+    // Uid 2 process state changed.
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucket2StartTimeNs + 60 * NS_PER_SEC, 2 /*uid*/,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucket2StartTimeNs + bucketSizeNs + 1, false, true, ADB_DUMP,
+                            FAST, &buffer);
+    ASSERT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_count_metrics());
+    StatsLogReport::CountMetricDataWrapper countMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).count_metrics(), &countMetrics);
+    ASSERT_EQ(2, countMetrics.data_size());
+
+    CountMetricData data = countMetrics.data(0);
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    ASSERT_EQ(1, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        2);
+
+    data = countMetrics.data(1);
+    ASSERT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value_size());
+    EXPECT_EQ(1, data.dimensions_in_what().value_tuple().dimensions_value(0).field());
+    EXPECT_EQ(2, data.dimensions_in_what().value_tuple().dimensions_value(0).value_int());
+    ASSERT_EQ(2, data.bucket_info_size());
+    ValidateCountBucket(data.bucket_info(0), bucketStartTimeNs, bucketStartTimeNs + bucketSizeNs,
+                        1);
+    ValidateCountBucket(data.bucket_info(1), bucket2StartTimeNs, bucket2StartTimeNs + bucketSizeNs,
+                        1);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index 8a4c962..3202368 100644
--- a/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -1562,6 +1562,93 @@
     EXPECT_EQ(baseTimeNs + bucketSizeNs * 2, data.bucket_info(0).end_bucket_elapsed_nanos());
 }
 
+TEST(DurationMetricE2eTest, TestConditionOnRepeatedEnumField) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher repeatedStateFirstOffAtomMatcher = CreateTestAtomRepeatedStateFirstOffAtomMatcher();
+    AtomMatcher repeatedStateFirstOnAtomMatcher = CreateTestAtomRepeatedStateFirstOnAtomMatcher();
+    *config.add_atom_matcher() = repeatedStateFirstOffAtomMatcher;
+    *config.add_atom_matcher() = repeatedStateFirstOnAtomMatcher;
+
+    Predicate durationPredicate = CreateTestAtomRepeatedStateFirstOffPredicate();
+    *config.add_predicate() = durationPredicate;
+
+    int64_t metricId = 123456;
+    DurationMetric* durationMetric = config.add_duration_metric();
+    durationMetric->set_id(metricId);
+    durationMetric->set_what(durationPredicate.id());
+    durationMetric->set_bucket(FIVE_MINUTES);
+    durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
+
+    const int64_t baseTimeNs = 0;                                   // 0:00
+    const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC;  // 0:01
+    const int64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000LL * 1000LL;
+
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+    vector<int> intArray = {3, 6};
+    vector<int64_t> longArray = {1000L, 10002L};
+    vector<float> floatArray = {0.3f, 0.09f};
+    vector<string> stringArray = {"str1", "str2"};
+    int boolArrayLength = 2;
+    bool boolArray[boolArrayLength];
+    boolArray[0] = 1;
+    boolArray[1] = 0;
+    vector<int> enumArrayOff = {TestAtomReported::OFF, TestAtomReported::ON};
+    vector<int> enumArrayOn = {TestAtomReported::ON, TestAtomReported::OFF};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    uint64_t falseDurationStartNs = configAddedTimeNs + 10 * NS_PER_SEC;
+    uint64_t durationStartNs = configAddedTimeNs + 20 * NS_PER_SEC;
+    uint64_t durationEndNs = durationStartNs + 50 * NS_PER_SEC;
+
+    // Condition false
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(falseDurationStartNs, {}, {},
+                                                                       {}, {}, {}, 0, enumArrayOn));
+    // Condition true - start collecting duration.
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(durationStartNs, {}, {}, {},
+                                                                       {}, {}, 0, enumArrayOff));
+    // Condition false - stop collecting duration.
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(durationEndNs, {}, {}, {},
+                                                                       {}, {}, 0, enumArrayOn));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(cfgKey, configAddedTimeNs + bucketSizeNs + 1 * NS_PER_SEC, false, true,
+                            ADB_DUMP, FAST, &buffer);  // 10:01
+    EXPECT_TRUE(buffer.size() > 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStartEndTimestamp(&reports);
+    ASSERT_EQ(1, reports.reports_size());
+    ASSERT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_EQ(metricId, reports.reports(0).metrics(0).metric_id());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+
+    StatsLogReport::DurationMetricDataWrapper durationMetrics;
+    sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).duration_metrics(),
+                                    &durationMetrics);
+    ASSERT_EQ(1, durationMetrics.data_size());
+
+    DurationMetricData data = durationMetrics.data(0);
+    ASSERT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(baseTimeNs + bucketSizeNs, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/EventMetric_e2e_test.cpp b/statsd/tests/e2e/EventMetric_e2e_test.cpp
index 0f66a5a..81a6d91 100644
--- a/statsd/tests/e2e/EventMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/EventMetric_e2e_test.cpp
@@ -101,6 +101,162 @@
     EXPECT_EQ(data.atom().wakelock_state_changed().tag(), "wl2");
 }
 
+TEST_F(EventMetricE2eTest, TestRepeatedFieldsAndEmptyArrays) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedAtomMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedAtomMatcher;
+
+    EventMetric testAtomReportedEventMetric =
+            createEventMetric("EventTestAtomReported", testAtomReportedAtomMatcher.id(), nullopt);
+    *config.add_event_metric() = testAtomReportedEventMetric;
+
+    ConfigKey key(123, 987);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+
+    // Initialize log events before update.
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    vector<int> intArray = {3, 6};
+    vector<int64_t> longArray = {1000L, 10002L};
+    vector<float> floatArray = {0.3f, 0.09f};
+    vector<string> stringArray = {"str1", "str2"};
+    int boolArrayLength = 2;
+    bool boolArray[boolArrayLength];
+    boolArray[0] = 1;
+    boolArray[1] = 0;
+    vector<bool> boolArrayVector = {1, 0};
+    vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF};
+
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 10 * NS_PER_SEC, intArray, longArray, floatArray, stringArray,
+            boolArray, boolArrayLength, enumArray));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {}));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 30 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArray));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    uint64_t dumpTimeNs = bucketStartTimeNs + 100 * NS_PER_SEC;
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    ASSERT_EQ(reports.reports_size(), 1);
+
+    ConfigMetricsReport report = reports.reports(0);
+    ASSERT_EQ(report.metrics_size(), 1);
+    StatsLogReport testAtomEventMetricReport = report.metrics(0);
+    EXPECT_EQ(testAtomEventMetricReport.metric_id(), testAtomReportedEventMetric.id());
+    EXPECT_TRUE(testAtomEventMetricReport.has_event_metrics());
+    ASSERT_EQ(testAtomEventMetricReport.event_metrics().data_size(), 3);
+
+    EventMetricData data = testAtomEventMetricReport.event_metrics().data(0);
+    EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 10 * NS_PER_SEC);
+    TestAtomReported atom = data.atom().test_atom_reported();
+    EXPECT_THAT(atom.repeated_int_field(), ElementsAreArray(intArray));
+    EXPECT_THAT(atom.repeated_long_field(), ElementsAreArray(longArray));
+    EXPECT_THAT(atom.repeated_float_field(), ElementsAreArray(floatArray));
+    EXPECT_THAT(atom.repeated_string_field(), ElementsAreArray(stringArray));
+    EXPECT_THAT(atom.repeated_boolean_field(), ElementsAreArray(boolArrayVector));
+    EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray));
+
+    data = testAtomEventMetricReport.event_metrics().data(1);
+    atom = data.atom().test_atom_reported();
+    EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 20 * NS_PER_SEC);
+    EXPECT_EQ(atom.repeated_int_field_size(), 0);
+    EXPECT_EQ(atom.repeated_long_field_size(), 0);
+    EXPECT_EQ(atom.repeated_float_field_size(), 0);
+    EXPECT_EQ(atom.repeated_string_field_size(), 0);
+    EXPECT_EQ(atom.repeated_boolean_field_size(), 0);
+    EXPECT_EQ(atom.repeated_enum_field_size(), 0);
+
+    data = testAtomEventMetricReport.event_metrics().data(2);
+    atom = data.atom().test_atom_reported();
+    EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 30 * NS_PER_SEC);
+    EXPECT_EQ(atom.repeated_int_field_size(), 0);
+    EXPECT_EQ(atom.repeated_long_field_size(), 0);
+    EXPECT_EQ(atom.repeated_float_field_size(), 0);
+    EXPECT_EQ(atom.repeated_string_field_size(), 0);
+    EXPECT_EQ(atom.repeated_boolean_field_size(), 0);
+    EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray));
+}
+
+TEST_F(EventMetricE2eTest, TestMatchRepeatedFieldPositionFirst) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedStateFirstOnAtomMatcher =
+            CreateTestAtomRepeatedStateFirstOnAtomMatcher();
+    *config.add_atom_matcher() = testAtomReportedStateFirstOnAtomMatcher;
+
+    EventMetric testAtomReportedEventMetric = createEventMetric(
+            "EventTestAtomReported", testAtomReportedStateFirstOnAtomMatcher.id(), nullopt);
+    *config.add_event_metric() = testAtomReportedEventMetric;
+
+    ConfigKey key(123, 987);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, key);
+
+    // Initialize log events before update.
+    std::vector<std::unique_ptr<LogEvent>> events;
+
+    vector<int> enumArrayNoMatch = {TestAtomReported::OFF, TestAtomReported::ON};
+    vector<int> enumArrayMatch = {TestAtomReported::ON, TestAtomReported::OFF};
+
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 10 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayNoMatch));
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, enumArrayMatch));
+    // No matching is done on an empty array.
+    events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+            bucketStartTimeNs + 30 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {}));
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    uint64_t dumpTimeNs = bucketStartTimeNs + 100 * NS_PER_SEC;
+    ConfigMetricsReportList reports;
+    vector<uint8_t> buffer;
+    processor->onDumpReport(key, dumpTimeNs, true, true, ADB_DUMP, FAST, &buffer);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+    backfillAggregatedAtoms(&reports);
+    ASSERT_EQ(reports.reports_size(), 1);
+
+    ConfigMetricsReport report = reports.reports(0);
+    ASSERT_EQ(report.metrics_size(), 1);
+    StatsLogReport testAtomEventMetricReport = report.metrics(0);
+    EXPECT_EQ(testAtomEventMetricReport.metric_id(), testAtomReportedEventMetric.id());
+    EXPECT_TRUE(testAtomEventMetricReport.has_event_metrics());
+    ASSERT_EQ(testAtomEventMetricReport.event_metrics().data_size(), 1);
+
+    EventMetricData data = testAtomEventMetricReport.event_metrics().data(0);
+    EXPECT_EQ(data.elapsed_timestamp_nanos(), bucketStartTimeNs + 20 * NS_PER_SEC);
+    TestAtomReported atom = data.atom().test_atom_reported();
+    ASSERT_EQ(atom.repeated_int_field_size(), 0);
+    ASSERT_EQ(atom.repeated_long_field_size(), 0);
+    ASSERT_EQ(atom.repeated_float_field_size(), 0);
+    ASSERT_EQ(atom.repeated_string_field_size(), 0);
+    ASSERT_EQ(atom.repeated_boolean_field_size(), 0);
+    EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArrayMatch));
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
index a4489ba..f5a0158 100644
--- a/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
+++ b/statsd/tests/e2e/GaugeMetric_e2e_push_test.cpp
@@ -69,6 +69,50 @@
     return config;
 }
 
+StatsdConfig CreateStatsdConfigForRepeatedFieldsPushedEvent(
+        const GaugeMetric::SamplingType sampling_type) {
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedAtomMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedAtomMatcher;
+
+    GaugeMetric* gaugeMetric = config.add_gauge_metric();
+    gaugeMetric->set_id(123456);
+    gaugeMetric->set_what(testAtomReportedAtomMatcher.id());
+    gaugeMetric->set_sampling_type(sampling_type);
+    FieldMatcher* fieldMatcher = gaugeMetric->mutable_gauge_fields_filter()->mutable_fields();
+    fieldMatcher->set_field(util::TEST_ATOM_REPORTED);
+
+    FieldMatcher* childFieldMatcher = fieldMatcher->add_child();
+    childFieldMatcher->set_field(9);  // repeated_int_field
+    childFieldMatcher->set_position(Position::FIRST);
+
+    childFieldMatcher = fieldMatcher->add_child();
+    childFieldMatcher->set_field(10);  // repeated_long_field
+    childFieldMatcher->set_position(Position::LAST);
+
+    childFieldMatcher = fieldMatcher->add_child();
+    childFieldMatcher->set_field(11);  // repeated_float_field
+    childFieldMatcher->set_position(Position::ALL);
+
+    childFieldMatcher = fieldMatcher->add_child();
+    childFieldMatcher->set_field(12);  // repeated_string_field
+    childFieldMatcher->set_position(Position::FIRST);
+
+    childFieldMatcher = fieldMatcher->add_child();
+    childFieldMatcher->set_field(13);  // repeated_boolean_field
+    childFieldMatcher->set_position(Position::LAST);
+
+    childFieldMatcher = fieldMatcher->add_child();
+    childFieldMatcher->set_field(14);  // repeated_enum_field
+    childFieldMatcher->set_position(Position::ALL);
+
+    gaugeMetric->set_bucket(FIVE_MINUTES);
+    return config;
+}
+
 }  // namespace
 
 // Setup for test fixture.
@@ -277,6 +321,99 @@
     }
 }
 
+TEST_F(GaugeMetricE2ePushedTest, TestRepeatedFieldsForPushedEvent) {
+    for (const auto& sampling_type :
+         {GaugeMetric::FIRST_N_SAMPLES, GaugeMetric::RANDOM_ONE_SAMPLE}) {
+        StatsdConfig config = CreateStatsdConfigForRepeatedFieldsPushedEvent(sampling_type);
+        int64_t bucketStartTimeNs = 10000000000;
+        int64_t bucketSizeNs =
+                TimeUnitToBucketSizeInMillis(config.gauge_metric(0).bucket()) * 1000000;
+
+        ConfigKey cfgKey;
+        sp<StatsLogProcessor> processor =
+                CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+        std::vector<std::unique_ptr<LogEvent>> events;
+
+        vector<int> intArray = {3, 6};
+        vector<int64_t> longArray = {1000L, 10002L};
+        vector<float> floatArray = {0.3f, 0.09f};
+        vector<string> stringArray = {"str1", "str2"};
+        int boolArrayLength = 2;
+        bool boolArray[boolArrayLength];
+        boolArray[0] = 1;
+        boolArray[1] = 0;
+        vector<bool> boolArrayVector = {1, 0};
+        vector<int> enumArray = {TestAtomReported::ON, TestAtomReported::OFF};
+
+        events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+                bucketStartTimeNs + 10 * NS_PER_SEC, intArray, longArray, floatArray, stringArray,
+                boolArray, boolArrayLength, enumArray));
+        events.push_back(CreateTestAtomReportedEventVariableRepeatedFields(
+                bucketStartTimeNs + 20 * NS_PER_SEC, {}, {}, {}, {}, {}, 0, {}));
+
+        for (const auto& event : events) {
+            processor->OnLogEvent(event.get());
+        }
+
+        ConfigMetricsReportList reports;
+        vector<uint8_t> buffer;
+        processor->onDumpReport(cfgKey, bucketStartTimeNs + 3 * bucketSizeNs, false, true, ADB_DUMP,
+                                FAST, &buffer);
+        EXPECT_TRUE(buffer.size() > 0);
+        EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+        backfillDimensionPath(&reports);
+        backfillStringInReport(&reports);
+        backfillStartEndTimestamp(&reports);
+        backfillAggregatedAtoms(&reports);
+
+        ASSERT_EQ(1, reports.reports_size());
+        ASSERT_EQ(1, reports.reports(0).metrics_size());
+        StatsLogReport::GaugeMetricDataWrapper gaugeMetrics;
+        sortMetricDataByDimensionsValue(reports.reports(0).metrics(0).gauge_metrics(),
+                                        &gaugeMetrics);
+        ASSERT_EQ(1, gaugeMetrics.data_size());
+
+        GaugeMetricData data = gaugeMetrics.data(0);
+        ASSERT_EQ(1, data.bucket_info_size());
+        if (sampling_type == GaugeMetric::FIRST_N_SAMPLES) {
+            EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+            EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+                      data.bucket_info(0).end_bucket_elapsed_nanos());
+            ASSERT_EQ(2, data.bucket_info(0).atom_size());
+
+            TestAtomReported atom = data.bucket_info(0).atom(0).test_atom_reported();
+            EXPECT_THAT(atom.repeated_int_field(), ElementsAreArray({3}));
+            EXPECT_THAT(atom.repeated_long_field(), ElementsAreArray({10002L}));
+            EXPECT_THAT(atom.repeated_float_field(), ElementsAreArray(floatArray));
+            EXPECT_THAT(atom.repeated_string_field(), ElementsAreArray({"str1"}));
+            EXPECT_THAT(atom.repeated_boolean_field(), ElementsAreArray({0}));
+            EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray));
+
+            atom = data.bucket_info(0).atom(1).test_atom_reported();
+            EXPECT_EQ(atom.repeated_int_field_size(), 0);
+            EXPECT_EQ(atom.repeated_long_field_size(), 0);
+            EXPECT_EQ(atom.repeated_float_field_size(), 0);
+            EXPECT_EQ(atom.repeated_string_field_size(), 0);
+            EXPECT_EQ(atom.repeated_boolean_field_size(), 0);
+            EXPECT_EQ(atom.repeated_enum_field_size(), 0);
+        } else {
+            EXPECT_EQ(bucketStartTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
+            EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
+                      data.bucket_info(0).end_bucket_elapsed_nanos());
+            ASSERT_EQ(1, data.bucket_info(0).atom_size());
+
+            TestAtomReported atom = data.bucket_info(0).atom(0).test_atom_reported();
+            EXPECT_THAT(atom.repeated_int_field(), ElementsAreArray({3}));
+            EXPECT_THAT(atom.repeated_long_field(), ElementsAreArray({10002L}));
+            EXPECT_THAT(atom.repeated_float_field(), ElementsAreArray(floatArray));
+            EXPECT_THAT(atom.repeated_string_field(), ElementsAreArray({"str1"}));
+            EXPECT_THAT(atom.repeated_boolean_field(), ElementsAreArray({0}));
+            EXPECT_THAT(atom.repeated_enum_field(), ElementsAreArray(enumArray));
+        }
+    }
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/KllMetric_e2e_test.cpp b/statsd/tests/e2e/KllMetric_e2e_test.cpp
index aae5bae..b62a133 100644
--- a/statsd/tests/e2e/KllMetric_e2e_test.cpp
+++ b/statsd/tests/e2e/KllMetric_e2e_test.cpp
@@ -89,6 +89,36 @@
     EXPECT_EQ(metricReport.kll_metrics().skipped_size(), 0);
 }
 
+TEST_F(KllMetricE2eTest, TestInitWithKllFieldPositionALL) {
+    // Create config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    // Create kll metric.
+    int64_t metricId = 123456;
+    KllMetric* kllMetric = config.add_kll_metric();
+    kllMetric->set_id(metricId);
+    kllMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    kllMetric->set_what(testAtomReportedMatcher.id());
+    *kllMetric->mutable_kll_field() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::ALL});
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // Config initialization fails.
+    ASSERT_EQ(0, processor->mMetricsManagers.size());
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
index c0059a2..1ea757a 100644
--- a/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
+++ b/statsd/tests/e2e/ValueMetric_pull_e2e_test.cpp
@@ -670,6 +670,36 @@
     ASSERT_EQ(0, processor->mMetricsManagers.size());
 }
 
+TEST(ValueMetricE2eTest, TestInitWithValueFieldPositionALL) {
+    // Create config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    AtomMatcher testAtomReportedMatcher =
+            CreateSimpleAtomMatcher("TestAtomReportedMatcher", util::TEST_ATOM_REPORTED);
+    *config.add_atom_matcher() = testAtomReportedMatcher;
+
+    // Create value metric.
+    int64_t metricId = 123456;
+    ValueMetric* valueMetric = config.add_value_metric();
+    valueMetric->set_id(metricId);
+    valueMetric->set_bucket(TimeUnit::FIVE_MINUTES);
+    valueMetric->set_what(testAtomReportedMatcher.id());
+    *valueMetric->mutable_value_field() = CreateRepeatedDimensions(
+            util::TEST_ATOM_REPORTED, {9 /*repeated_int_field*/}, {Position::ALL});
+
+    // Initialize StatsLogProcessor.
+    const uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    sp<StatsLogProcessor> processor =
+            CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // Config initialization fails.
+    ASSERT_EQ(0, processor->mMetricsManagers.size());
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/statsd/tests/external/puller_util_test.cpp b/statsd/tests/external/puller_util_test.cpp
index 0fca512..25c0c7e 100644
--- a/statsd/tests/external/puller_util_test.cpp
+++ b/statsd/tests/external/puller_util_test.cpp
@@ -430,6 +430,217 @@
               actualFieldValues->at(5).mValue.int_value);
 }
 
+// Test that repeated fields are treated as non-additive fields even when marked as additive.
+TEST(PullerUtilTest, RepeatedAdditiveField) {
+    vector<int> int32Array1 = {3, 6};
+    vector<int> int32Array2 = {6, 9};
+
+    vector<shared_ptr<LogEvent>> data = {
+            // 30->22->{3,6}
+            makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData,
+                            int32Array1),
+
+            // 30->22->{6,9}
+            makeUidLogEvent(uidAtomTagId, timestamp, isolatedUid1, hostNonAdditiveData,
+                            int32Array2),
+
+            // 20->22->{3,6}
+            makeUidLogEvent(uidAtomTagId, timestamp, hostUid, hostNonAdditiveData, int32Array1),
+    };
+
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields);
+
+    ASSERT_EQ(2, (int)data.size());
+    // Events 1 and 3 are merged - non-additive fields, including the repeated additive field, are
+    // equal.
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(4, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(3, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(6, actualFieldValues->at(3).mValue.int_value);
+
+    // Event 2 isn't merged - repeated additive field is not equal.
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(4, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(6, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(9, actualFieldValues->at(3).mValue.int_value);
+}
+
+// Test that repeated uid events are sorted and merged correctly.
+TEST(PullerUtilTest, RepeatedUidField) {
+    vector<int> uidArray1 = {isolatedUid1, hostUid};
+    vector<int> uidArray2 = {isolatedUid1, isolatedUid3};
+    vector<int> uidArray3 = {isolatedUid1, hostUid, isolatedUid2};
+
+    vector<shared_ptr<LogEvent>> data = {
+            // {30, 20}->22->21
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostNonAdditiveData,
+                                    hostAdditiveData),
+
+            // {30, 3000}->22->21 (different uid, not merged)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray2, hostNonAdditiveData,
+                                    hostAdditiveData),
+
+            // {30, 20}->22->31 (different additive field, merged)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostNonAdditiveData,
+                                    isolatedAdditiveData),
+
+            // {30, 20}->32->21 (different non-additive field, not merged)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, isolatedNonAdditiveData,
+                                    hostAdditiveData),
+
+            // {30, 20, 40}->22->21 (different repeated uid length, not merged)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray3, hostNonAdditiveData,
+                                    hostAdditiveData),
+
+            // {30, 20}->22->21 (same as first event, merged)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostNonAdditiveData,
+                                    hostAdditiveData),
+    };
+
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, additiveFields);
+
+    ASSERT_EQ(4, (int)data.size());
+    // Events 1 and 3 and 6 are merged.
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(4, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData + isolatedAdditiveData + hostAdditiveData,
+              actualFieldValues->at(3).mValue.int_value);
+
+    // Event 4 isn't merged - different non-additive data.
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(4, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(isolatedNonAdditiveData, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(3).mValue.int_value);
+
+    // Event 2 isn't merged - different uid.
+    actualFieldValues = &data[2]->getValues();
+    ASSERT_EQ(4, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(3).mValue.int_value);
+
+    // Event 5 isn't merged - different repeated uid length.
+    actualFieldValues = &data[3]->getValues();
+    ASSERT_EQ(5, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(hostNonAdditiveData, actualFieldValues->at(3).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(4).mValue.int_value);
+}
+
+// Test that repeated uid events with multiple repeated non-additive fields are sorted and merged
+// correctly.
+TEST(PullerUtilTest, MultipleRepeatedFields) {
+    vector<int> uidArray1 = {isolatedUid1, hostUid};
+    vector<int> uidArray2 = {isolatedUid1, isolatedUid3};
+    vector<int> uidArray3 = {isolatedUid1, hostUid, isolatedUid2};
+
+    vector<int> nonAdditiveArray1 = {1, 2, 3};
+    vector<int> nonAdditiveArray2 = {1, 5, 3};
+    vector<int> nonAdditiveArray3 = {1, 2};
+
+    const vector<int> secondAdditiveField = {2};
+
+    vector<shared_ptr<LogEvent>> data = {
+            // TODO: Once b/224880904 is fixed, can use different additive data without
+            // having the sort order messed up.
+
+            // Event 1 {30, 20}->21->{1, 2, 3} (merged with event 4)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostAdditiveData,
+                                    nonAdditiveArray1),
+
+            // Event 2 {30, 3000}->21->{1, 2, 3} (different uid, not merged)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray2, hostAdditiveData,
+                                    nonAdditiveArray1),
+
+            // Event 3 {30, 20, 40}->21->{1, 2} (different repeated fields with total length equal
+            // to event 1, merged with event 6)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray3, hostAdditiveData,
+                                    nonAdditiveArray3),
+
+            // Event 4 {30, 20}->21->{1, 2, 3} (merged with event 1)
+            // TODO: once sorting bug is fixed, can change this additive field
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostAdditiveData,
+                                    nonAdditiveArray1),
+
+            // Event 5 {30, 20}->21->{1, 5, 3} (different repeated field, not merged)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray1, hostAdditiveData,
+                                    nonAdditiveArray2),
+
+            // Event 6 {30, 20, 40}->22->{1, 2} (different repeated fields with total length equal
+            // to event 1, merged with event 3)
+            makeRepeatedUidLogEvent(uidAtomTagId, timestamp, uidArray3, isolatedAdditiveData,
+                                    nonAdditiveArray3),
+    };
+
+    // Expected event ordering after the sort:
+    // Event 3 {30, 20, 40}->21->{1, 2} (total size equal to event 1, merged with event 6)
+    // Event 6 {30, 20, 40}->22->{1, 2} (total size equal to event 1, merged with event 3)
+    // Event 1 {30, 20}->21->{1, 2, 3}
+    // Event 4 {30, 20}->21->{1, 2, 3} (merged with event 1)
+    // Event 5 {30, 20}->21->{1, 5, 3} (different repeated field, not merged)
+    // Event 2 {30, 3000}->21->{1, 2, 3} (different uid, not merged)
+
+    sp<MockUidMap> uidMap = makeMockUidMap();
+    mapAndMergeIsolatedUidsToHostUid(data, uidMap, uidAtomTagId, secondAdditiveField);
+
+    ASSERT_EQ(4, (int)data.size());
+
+    // Events 3 and 6 are merged. Not merged with event 1 because different repeated uids and
+    // fields, though length is same.
+    const vector<FieldValue>* actualFieldValues = &data[0]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData + isolatedAdditiveData, actualFieldValues->at(3).mValue.int_value);
+    EXPECT_EQ(1, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(2, actualFieldValues->at(5).mValue.int_value);
+
+    // Events 1 and 4 are merged.
+    actualFieldValues = &data[1]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData + hostAdditiveData, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(1, actualFieldValues->at(3).mValue.int_value);
+    EXPECT_EQ(2, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(3, actualFieldValues->at(5).mValue.int_value);
+
+    // Event 5 isn't merged - different repeated field.
+    actualFieldValues = &data[2]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(1, actualFieldValues->at(3).mValue.int_value);
+    EXPECT_EQ(5, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(3, actualFieldValues->at(5).mValue.int_value);
+
+    // Event 2 isn't merged - different uid.
+    actualFieldValues = &data[3]->getValues();
+    ASSERT_EQ(6, actualFieldValues->size());
+    EXPECT_EQ(hostUid, actualFieldValues->at(0).mValue.int_value);
+    EXPECT_EQ(hostUid2, actualFieldValues->at(1).mValue.int_value);
+    EXPECT_EQ(hostAdditiveData, actualFieldValues->at(2).mValue.int_value);
+    EXPECT_EQ(1, actualFieldValues->at(3).mValue.int_value);
+    EXPECT_EQ(2, actualFieldValues->at(4).mValue.int_value);
+    EXPECT_EQ(3, actualFieldValues->at(5).mValue.int_value);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/metrics/KllMetricProducer_test.cpp b/statsd/tests/metrics/KllMetricProducer_test.cpp
index 334a21b..e84efb0 100644
--- a/statsd/tests/metrics/KllMetricProducer_test.cpp
+++ b/statsd/tests/metrics/KllMetricProducer_test.cpp
@@ -121,7 +121,8 @@
                 TimeUnitToBucketSizeInMillisGuardrailed(kConfigKey.GetUid(), metric.bucket()));
         const bool containsAnyPositionInDimensionsInWhat =
                 HasPositionANY(metric.dimensions_in_what());
-        const bool sliceByPositionAll = HasPositionALL(metric.dimensions_in_what());
+        const bool shouldUseNestedDimensions =
+                ShouldUseNestedDimensions(metric.dimensions_in_what());
 
         vector<Matcher> fieldMatchers;
         translateFieldMatcher(metric.kll_field(), &fieldMatchers);
@@ -139,7 +140,8 @@
                 kConfigKey, metric, protoHash, {/*pullAtomId=*/-1, /*pullerManager=*/nullptr},
                 {timeBaseNs, startTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
                  /*conditionCorrectionThresholdNs=*/nullopt, metric.split_bucket_for_app_upgrade()},
-                {containsAnyPositionInDimensionsInWhat, sliceByPositionAll, logEventMatcherIndex,
+                {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions,
+                 logEventMatcherIndex,
                  /*eventMatcherWizard=*/nullptr, metric.dimensions_in_what(), fieldMatchers},
                 {conditionIndex, metric.links(), initialConditionCache, wizard},
                 {metric.state_link(), slicedStateAtoms, stateGroupMap},
diff --git a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
index b5f091e..1b5abaf 100644
--- a/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
+++ b/statsd/tests/metrics/NumericValueMetricProducer_test.cpp
@@ -186,7 +186,8 @@
                 TimeUnitToBucketSizeInMillisGuardrailed(kConfigKey.GetUid(), metric.bucket()));
         const bool containsAnyPositionInDimensionsInWhat =
                 HasPositionANY(metric.dimensions_in_what());
-        const bool sliceByPositionAll = HasPositionALL(metric.dimensions_in_what());
+        const bool shouldUseNestedDimensions =
+                ShouldUseNestedDimensions(metric.dimensions_in_what());
 
         vector<Matcher> fieldMatchers;
         translateFieldMatcher(metric.value_field(), &fieldMatchers);
@@ -210,8 +211,9 @@
                 kConfigKey, metric, protoHash, {pullAtomId, pullerManager},
                 {timeBaseNs, startTimeNs, bucketSizeNs, metric.min_bucket_size_nanos(),
                  conditionCorrectionThresholdNs, metric.split_bucket_for_app_upgrade()},
-                {containsAnyPositionInDimensionsInWhat, sliceByPositionAll, logEventMatcherIndex,
-                 eventMatcherWizard, metric.dimensions_in_what(), fieldMatchers},
+                {containsAnyPositionInDimensionsInWhat, shouldUseNestedDimensions,
+                 logEventMatcherIndex, eventMatcherWizard, metric.dimensions_in_what(),
+                 fieldMatchers},
                 {conditionIndex, metric.links(), initialConditionCache, wizard},
                 {metric.state_link(), slicedStateAtoms, stateGroupMap},
                 {/*eventActivationMap=*/{}, /*eventDeactivationMap=*/{}},
@@ -253,6 +255,20 @@
         metric.add_slice_by_state(StringToId(state));
         return metric;
     }
+
+    static ValueMetric createMetricWithRepeatedValueField() {
+        ValueMetric metric;
+        metric.set_id(metricId);
+        metric.set_bucket(ONE_MINUTE);
+        metric.mutable_value_field()->set_field(tagId);
+        FieldMatcher* valueChild = metric.mutable_value_field()->add_child();
+        valueChild->set_field(3);
+        valueChild->set_position(Position::FIRST);
+        metric.set_max_pull_delay_sec(INT_MAX);
+        metric.set_split_bucket_for_app_upgrade(true);
+        metric.set_aggregation_type(ValueMetric_AggregationType_SUM);
+        return metric;
+    }
 };
 
 // Setup for parameterized tests.
@@ -7410,6 +7426,85 @@
     ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, dumpReportTimeNs, {26}, -1, 0);
 }
 
+TEST(NumericValueMetricProducerTest, TestRepeatedValueFieldAndDimensions) {
+    ValueMetric metric = NumericValueMetricProducerTestHelper::createMetricWithRepeatedValueField();
+    metric.mutable_dimensions_in_what()->set_field(tagId);
+    FieldMatcher* valueChild = metric.mutable_dimensions_in_what()->add_child();
+    valueChild->set_field(1);
+    valueChild->set_position(Position::FIRST);
+
+    sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+
+    EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
+            // First field is a dimension field (repeated, position FIRST).
+            // Third field is the value field (repeated, position FIRST).
+            // NumericValueMetricProducer initialized.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(
+                        makeRepeatedUidLogEvent(tagId, bucketStartTimeNs + 1, {1, 10}, 5, {2, 3}));
+                data->push_back(
+                        makeRepeatedUidLogEvent(tagId, bucketStartTimeNs + 1, {2, 10}, 5, {3, 4}));
+                return true;
+            }))
+            // Dump report pull.
+            .WillOnce(Invoke([](int tagId, const ConfigKey&, const int64_t eventTimeNs,
+                                vector<std::shared_ptr<LogEvent>>* data) {
+                data->clear();
+                data->push_back(makeRepeatedUidLogEvent(tagId, bucket2StartTimeNs + 10000000000,
+                                                        {1, 10}, 5, {10, 3}));
+                data->push_back(makeRepeatedUidLogEvent(tagId, bucket2StartTimeNs + 10000000000,
+                                                        {2, 10}, 5, {14, 4}));
+                return true;
+            }));
+
+    sp<NumericValueMetricProducer> valueProducer =
+            NumericValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager,
+                                                                                  metric);
+
+    // Bucket 2 start.
+    vector<shared_ptr<LogEvent>> allData;
+    allData.clear();
+    allData.push_back(makeRepeatedUidLogEvent(tagId, bucket2StartTimeNs + 1, {1, 10}, 5, {5, 7}));
+    allData.push_back(makeRepeatedUidLogEvent(tagId, bucket2StartTimeNs + 1, {2, 10}, 5, {7, 5}));
+    valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
+
+    // Check dump report.
+    ProtoOutputStream output;
+    std::set<string> strSet;
+    int64_t dumpReportTimeNs = bucket2StartTimeNs + 10000000000;
+    valueProducer->onDumpReport(dumpReportTimeNs, true /* include current buckets */, true,
+                                NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output);
+
+    StatsLogReport report = outputStreamToProto(&output);
+    backfillDimensionPath(&report);
+    backfillStartEndTimestamp(&report);
+    EXPECT_TRUE(report.has_value_metrics());
+    StatsLogReport::ValueMetricDataWrapper valueMetrics;
+    sortMetricDataByDimensionsValue(report.value_metrics(), &valueMetrics);
+    ASSERT_EQ(2, valueMetrics.data_size());
+    EXPECT_EQ(0, report.value_metrics().skipped_size());
+
+    // Check data keyed to uid 1.
+    ValueMetricData data = valueMetrics.data(0);
+    ValidateUidDimension(data.dimensions_in_what(), tagId, 1);
+    ASSERT_EQ(2, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs, {3}, -1,
+                        0);  // Summed diffs of 2, 5
+    ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, dumpReportTimeNs, {5}, -1,
+                        0);  // Summed diffs of 5, 10
+
+    // Check data keyed to uid 2.
+    data = valueMetrics.data(1);
+    ValidateUidDimension(data.dimensions_in_what(), tagId, 2);
+    ASSERT_EQ(2, data.bucket_info_size());
+    ValidateValueBucket(data.bucket_info(0), bucketStartTimeNs, bucket2StartTimeNs, {4}, -1,
+                        0);  // Summed diffs of 3, 7
+    ValidateValueBucket(data.bucket_info(1), bucket2StartTimeNs, dumpReportTimeNs, {7}, -1,
+                        0);  // Summed diffs of 7, 14
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/statsd/tests/statsd_test_util.cpp b/statsd/tests/statsd_test_util.cpp
index ba8c0f6..6101f65 100644
--- a/statsd/tests/statsd_test_util.cpp
+++ b/statsd/tests/statsd_test_util.cpp
@@ -252,6 +252,35 @@
     return CreateSimpleAtomMatcher("AppStartOccurredMatcher", util::APP_START_OCCURRED);
 }
 
+AtomMatcher CreateTestAtomRepeatedStateAtomMatcher(const string& name,
+                                                   TestAtomReported::State state,
+                                                   Position position) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(util::TEST_ATOM_REPORTED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(14);  // Repeated enum field.
+    field_value_matcher->set_eq_int(state);
+    field_value_matcher->set_position(position);
+    return atom_matcher;
+}
+
+AtomMatcher CreateTestAtomRepeatedStateFirstOffAtomMatcher() {
+    return CreateTestAtomRepeatedStateAtomMatcher("TestFirstStateOff", TestAtomReported::OFF,
+                                                  Position::FIRST);
+}
+
+AtomMatcher CreateTestAtomRepeatedStateFirstOnAtomMatcher() {
+    return CreateTestAtomRepeatedStateAtomMatcher("TestFirstStateOn", TestAtomReported::ON,
+                                                  Position::FIRST);
+}
+
+AtomMatcher CreateTestAtomRepeatedStateAnyOnAtomMatcher() {
+    return CreateTestAtomRepeatedStateAtomMatcher("TestAnyStateOn", TestAtomReported::ON,
+                                                  Position::ANY);
+}
+
 void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher) {
     combinationMatcher->mutable_combination()->add_matcher(matcher.id());
 }
@@ -320,6 +349,14 @@
     return predicate;
 }
 
+Predicate CreateTestAtomRepeatedStateFirstOffPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("TestFirstStateIsOff"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("TestFirstStateOff"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("TestFirstStateOn"));
+    return predicate;
+}
+
 State CreateScreenState() {
     State state;
     state.set_id(StringToId("ScreenState"));
@@ -450,6 +487,22 @@
     return dimensions;
 }
 
+FieldMatcher CreateRepeatedDimensions(const int atomId, const std::vector<int>& fields,
+                                      const std::vector<Position>& positions) {
+    FieldMatcher dimensions;
+    if (fields.size() != positions.size()) {
+        return dimensions;
+    }
+
+    dimensions.set_field(atomId);
+    for (size_t i = 0; i < fields.size(); i++) {
+        auto child = dimensions.add_child();
+        child->set_field(fields[i]);
+        child->set_position(positions[i]);
+    }
+    return dimensions;
+}
+
 FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
                                                     const std::vector<Position>& positions,
                                                     const std::vector<int>& fields) {
@@ -744,6 +797,19 @@
     return statsEvent;
 }
 
+AStatsEvent* makeUidStatsEvent(int atomId, int64_t eventTimeNs, int uid, int data1,
+                               const vector<int>& data2) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+    AStatsEvent_writeInt32(statsEvent, uid);
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_writeInt32(statsEvent, data1);
+    AStatsEvent_writeInt32Array(statsEvent, data2.data(), data2.size());
+
+    return statsEvent;
+}
+
 shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1,
                                      int data2) {
     AStatsEvent* statsEvent = makeUidStatsEvent(atomId, eventTimeNs, uid, data1, data2);
@@ -753,6 +819,15 @@
     return logEvent;
 }
 
+shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1,
+                                     const vector<int>& data2) {
+    AStatsEvent* statsEvent = makeUidStatsEvent(atomId, eventTimeNs, uid, data1, data2);
+
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
 shared_ptr<LogEvent> makeExtraUidsLogEvent(int atomId, int64_t eventTimeNs, int uid1, int data1,
                                            int data2, const vector<int>& extraUids) {
     AStatsEvent* statsEvent = makeUidStatsEvent(atomId, eventTimeNs, uid1, data1, data2);
@@ -766,6 +841,53 @@
     return logEvent;
 }
 
+shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs,
+                                             const vector<int>& uids) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+    AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size());
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+
+    return logEvent;
+}
+
+shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs,
+                                             const vector<int>& uids, int data1, int data2) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+    AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size());
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_writeInt32(statsEvent, data1);
+    AStatsEvent_writeInt32(statsEvent, data2);
+
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+
+    return logEvent;
+}
+
+shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs,
+                                             const vector<int>& uids, int data1,
+                                             const vector<int>& data2) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, atomId);
+    AStatsEvent_overwriteTimestamp(statsEvent, eventTimeNs);
+    AStatsEvent_writeInt32Array(statsEvent, uids.data(), uids.size());
+    AStatsEvent_addBoolAnnotation(statsEvent, ANNOTATION_ID_IS_UID, true);
+    AStatsEvent_writeInt32(statsEvent, data1);
+    AStatsEvent_writeInt32Array(statsEvent, data2.data(), data2.size());
+
+    shared_ptr<LogEvent> logEvent = std::make_shared<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+
+    return logEvent;
+}
+
 shared_ptr<LogEvent> makeAttributionLogEvent(int atomId, int64_t eventTimeNs,
                                              const vector<int>& uids, const vector<string>& tags,
                                              int data1, int data2) {
@@ -898,6 +1020,57 @@
                                                ScheduledJobStateChanged::FINISHED, timestampNs);
 }
 
+std::unique_ptr<LogEvent> CreateTestAtomReportedEventVariableRepeatedFields(
+        uint64_t timestampNs, const vector<int>& repeatedIntField,
+        const vector<int64_t>& repeatedLongField, const vector<float>& repeatedFloatField,
+        const vector<string>& repeatedStringField, const bool* repeatedBoolField,
+        const size_t repeatedBoolFieldLength, const vector<int>& repeatedEnumField) {
+    return CreateTestAtomReportedEvent(timestampNs, {1001, 1002}, {"app1", "app2"}, 5, 1000l, 21.9f,
+                                       "string", 1, TestAtomReported::ON, {8, 1, 8, 2, 8, 3},
+                                       repeatedIntField, repeatedLongField, repeatedFloatField,
+                                       repeatedStringField, repeatedBoolField,
+                                       repeatedBoolFieldLength, repeatedEnumField);
+}
+
+std::unique_ptr<LogEvent> CreateTestAtomReportedEvent(
+        uint64_t timestampNs, const vector<int>& attributionUids,
+        const vector<string>& attributionTags, const int intField, const long longField,
+        const float floatField, const string& stringField, const bool boolField,
+        const TestAtomReported::State enumField, const vector<uint8_t>& bytesField,
+        const vector<int>& repeatedIntField, const vector<int64_t>& repeatedLongField,
+        const vector<float>& repeatedFloatField, const vector<string>& repeatedStringField,
+        const bool* repeatedBoolField, const size_t repeatedBoolFieldLength,
+        const vector<int>& repeatedEnumField) {
+    vector<const char*> cRepeatedStringField(repeatedStringField.size());
+    for (int i = 0; i < cRepeatedStringField.size(); i++) {
+        cRepeatedStringField[i] = repeatedStringField[i].c_str();
+    }
+
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::TEST_ATOM_REPORTED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    writeAttribution(statsEvent, attributionUids, attributionTags);
+    AStatsEvent_writeInt32(statsEvent, intField);
+    AStatsEvent_writeInt64(statsEvent, longField);
+    AStatsEvent_writeFloat(statsEvent, floatField);
+    AStatsEvent_writeString(statsEvent, stringField.c_str());
+    AStatsEvent_writeBool(statsEvent, boolField);
+    AStatsEvent_writeInt32(statsEvent, enumField);
+    AStatsEvent_writeByteArray(statsEvent, bytesField.data(), bytesField.size());
+    AStatsEvent_writeInt32Array(statsEvent, repeatedIntField.data(), repeatedIntField.size());
+    AStatsEvent_writeInt64Array(statsEvent, repeatedLongField.data(), repeatedLongField.size());
+    AStatsEvent_writeFloatArray(statsEvent, repeatedFloatField.data(), repeatedFloatField.size());
+    AStatsEvent_writeStringArray(statsEvent, cRepeatedStringField.data(),
+                                 repeatedStringField.size());
+    AStatsEvent_writeBoolArray(statsEvent, repeatedBoolField, repeatedBoolFieldLength);
+    AStatsEvent_writeInt32Array(statsEvent, repeatedEnumField.data(), repeatedEnumField.size());
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    parseStatsEventToLogEvent(statsEvent, logEvent.get());
+    return logEvent;
+}
+
 std::unique_ptr<LogEvent> CreateWakelockStateChangedEvent(uint64_t timestampNs,
                                                           const vector<int>& attributionUids,
                                                           const vector<string>& attributionTags,
@@ -1563,14 +1736,13 @@
 
 bool backfillDimensionPath(const DimensionsValue& path,
                            const google::protobuf::RepeatedPtrField<DimensionsValue>& leafValues,
-                           int* leafIndex,
-                           DimensionsValue* dimension) {
+                           int* leafIndex, DimensionsValue* dimension) {
     dimension->set_field(path.field());
     if (path.has_value_tuple()) {
         for (int i = 0; i < path.value_tuple().dimensions_value_size(); ++i) {
-            if (!backfillDimensionPath(
-                path.value_tuple().dimensions_value(i), leafValues, leafIndex,
-                dimension->mutable_value_tuple()->add_dimensions_value())) {
+            if (!backfillDimensionPath(path.value_tuple().dimensions_value(i), leafValues,
+                                       leafIndex,
+                                       dimension->mutable_value_tuple()->add_dimensions_value())) {
                 return false;
             }
         }
diff --git a/statsd/tests/statsd_test_util.h b/statsd/tests/statsd_test_util.h
index 4e73d89..59b134c 100644
--- a/statsd/tests/statsd_test_util.h
+++ b/statsd/tests/statsd_test_util.h
@@ -155,6 +155,20 @@
 // Create AtomMatcher proto for app launches.
 AtomMatcher CreateAppStartOccurredAtomMatcher();
 
+// Create AtomMatcher proto for test atom repeated state.
+AtomMatcher CreateTestAtomRepeatedStateAtomMatcher(const string& name,
+                                                   TestAtomReported::State state,
+                                                   Position position);
+
+// Create AtomMatcher proto for test atom repeated state is off, first position.
+AtomMatcher CreateTestAtomRepeatedStateFirstOffAtomMatcher();
+
+// Create AtomMatcher proto for test atom repeated state is on, first position.
+AtomMatcher CreateTestAtomRepeatedStateFirstOnAtomMatcher();
+
+// Create AtomMatcher proto for test atom repeated state is on, any position.
+AtomMatcher CreateTestAtomRepeatedStateAnyOnAtomMatcher();
+
 // Add an AtomMatcher to a combination AtomMatcher.
 void addMatcherToMatcherCombination(const AtomMatcher& matcher, AtomMatcher* combinationMatcher);
 
@@ -182,6 +196,9 @@
 // Create a Predicate proto for app is in background.
 Predicate CreateIsInBackgroundPredicate();
 
+// Create a Predicate proto for test atom repeated state field is off.
+Predicate CreateTestAtomRepeatedStateFirstOffPredicate();
+
 // Create State proto for screen state atom.
 State CreateScreenState();
 
@@ -221,6 +238,10 @@
 // Create dimensions from primitive fields.
 FieldMatcher CreateDimensions(const int atomId, const std::vector<int>& fields);
 
+// Create dimensions from repeated primitive fields.
+FieldMatcher CreateRepeatedDimensions(const int atomId, const std::vector<int>& fields,
+                                      const std::vector<Position>& positions);
+
 // Create dimensions by attribution uid and tag.
 FieldMatcher CreateAttributionUidAndTagDimensions(const int atomId,
                                                   const std::vector<Position>& positions);
@@ -307,12 +328,28 @@
 
 AStatsEvent* makeUidStatsEvent(int atomId, int64_t eventTimeNs, int uid, int data1, int data2);
 
+AStatsEvent* makeUidStatsEvent(int atomId, int64_t eventTimeNs, int uid, int data1,
+                               const vector<int>& data2);
+
 std::shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1,
                                           int data2);
 
+std::shared_ptr<LogEvent> makeUidLogEvent(int atomId, int64_t eventTimeNs, int uid, int data1,
+                                          const vector<int>& data2);
+
 shared_ptr<LogEvent> makeExtraUidsLogEvent(int atomId, int64_t eventTimeNs, int uid1, int data1,
                                            int data2, const std::vector<int>& extraUids);
 
+std::shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs,
+                                                  const std::vector<int>& uids);
+
+shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs,
+                                             const vector<int>& uids, int data1, int data2);
+
+shared_ptr<LogEvent> makeRepeatedUidLogEvent(int atomId, int64_t eventTimeNs,
+                                             const vector<int>& uids, int data1,
+                                             const vector<int>& data2);
+
 std::shared_ptr<LogEvent> makeAttributionLogEvent(int atomId, int64_t eventTimeNs,
                                                   const vector<int>& uids,
                                                   const vector<string>& tags, int data1, int data2);
@@ -404,6 +441,22 @@
         AppStartOccurred::TransitionType type, const string& activity_name,
         const string& calling_pkg_name, const bool is_instant_app, int64_t activity_start_msec);
 
+std::unique_ptr<LogEvent> CreateTestAtomReportedEventVariableRepeatedFields(
+        uint64_t timestampNs, const vector<int>& repeatedIntField,
+        const vector<int64_t>& repeatedLongField, const vector<float>& repeatedFloatField,
+        const vector<string>& repeatedStringField, const bool* repeatedBoolField,
+        const size_t repeatedBoolFieldLength, const vector<int>& repeatedEnumField);
+
+std::unique_ptr<LogEvent> CreateTestAtomReportedEvent(
+        uint64_t timestampNs, const vector<int>& attributionUids,
+        const vector<string>& attributionTags, const int intField, const long longField,
+        const float floatField, const string& stringField, const bool boolField,
+        const TestAtomReported::State enumField, const vector<uint8_t>& bytesField,
+        const vector<int>& repeatedIntField, const vector<int64_t>& repeatedLongField,
+        const vector<float>& repeatedFloatField, const vector<string>& repeatedStringField,
+        const bool* repeatedBoolField, const size_t repeatedBoolFieldLength,
+        const vector<int>& repeatedEnumField);
+
 // Create a statsd log event processor upon the start time in seconds, config and key.
 sp<StatsLogProcessor> CreateStatsLogProcessor(const int64_t timeBaseNs, const int64_t currentTimeNs,
                                               const StatsdConfig& config, const ConfigKey& key,
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index 451ad73..be8a12a 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -20,6 +20,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsStatsdHostTestCases.jar" />
     </test>