Flex API debug key list support and bug fixes

Bugs in assessing summary buckets and updating bucket amounts during
attribution were also addressed.

Bug: 307715563
Bug: 305247214

Test: atest com.android.adservices.service.measurement

Change-Id: Ic2282b5d4e398370689ae9b767510de7b8b796e1
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/AttributedTrigger.java b/adservices/service-core/java/com/android/adservices/service/measurement/AttributedTrigger.java
index 4866316..280a92d 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/AttributedTrigger.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/AttributedTrigger.java
@@ -32,6 +32,8 @@
     private final long mValue;
     private final long mTriggerTime;
     private final UnsignedLong mDedupKey;
+    private final UnsignedLong mDebugKey;
+    private final Boolean mHasSourceDebugKey;
     private long mContribution;
 
     @Override
@@ -46,12 +48,22 @@
                 && Objects.equals(mTriggerData, t.mTriggerData)
                 && mValue == t.mValue
                 && mTriggerTime == t.mTriggerTime
-                && Objects.equals(mDedupKey, t.mDedupKey);
+                && Objects.equals(mDedupKey, t.mDedupKey)
+                && Objects.equals(mDebugKey, t.mDebugKey)
+                && Objects.equals(mHasSourceDebugKey, t.mHasSourceDebugKey);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mTriggerId, mPriority, mTriggerData, mValue, mTriggerTime, mDedupKey);
+        return Objects.hash(
+                mTriggerId,
+                mPriority,
+                mTriggerData,
+                mValue,
+                mTriggerTime,
+                mDedupKey,
+                mDebugKey,
+                mHasSourceDebugKey);
     }
 
     public AttributedTrigger(JSONObject json) throws JSONException {
@@ -83,6 +95,17 @@
         } else {
             mDedupKey = null;
         }
+        if (!json.isNull(JsonKeys.DEBUG_KEY)) {
+            mDebugKey = new UnsignedLong(
+                    json.getString(JsonKeys.DEBUG_KEY));
+        } else {
+            mDebugKey = null;
+        }
+        if (!json.isNull(JsonKeys.HAS_SOURCE_DEBUG_KEY)) {
+            mHasSourceDebugKey = json.getBoolean(JsonKeys.HAS_SOURCE_DEBUG_KEY);
+        } else {
+            mHasSourceDebugKey = null;
+        }
     }
 
     public AttributedTrigger(
@@ -91,6 +114,8 @@
             UnsignedLong dedupKey) {
         mTriggerId = triggerId;
         mDedupKey = dedupKey;
+        mDebugKey = null;
+        mHasSourceDebugKey = null;
         mPriority = 0L;
         mTriggerData = triggerData;
         mValue = 0L;
@@ -103,13 +128,17 @@
             UnsignedLong triggerData,
             long value,
             long triggerTime,
-            UnsignedLong dedupKey) {
+            UnsignedLong dedupKey,
+            UnsignedLong debugKey,
+            boolean hasSourceDebugKey) {
         mTriggerId = triggerId;
         mPriority = priority;
         mTriggerData = triggerData;
         mValue = value;
         mTriggerTime = triggerTime;
         mDedupKey = dedupKey;
+        mDebugKey = debugKey;
+        mHasSourceDebugKey = hasSourceDebugKey;
     }
 
     public String getTriggerId() {
@@ -136,6 +165,18 @@
         return mDedupKey;
     }
 
+    public UnsignedLong getDebugKey() {
+        return mDebugKey;
+    }
+
+    /**
+     * Returns whether the source debug key was permitted and populated when this trigger was
+     * attributed.
+     */
+    public boolean hasSourceDebugKey() {
+        return mHasSourceDebugKey;
+    }
+
     public long getContribution() {
         return mContribution;
     }
@@ -180,6 +221,10 @@
             if (mDedupKey != null) {
                 json.put(JsonKeys.DEDUP_KEY, mDedupKey.toString());
             }
+            if (mDebugKey != null) {
+                json.put(JsonKeys.DEBUG_KEY, mDebugKey.toString());
+            }
+            json.put(JsonKeys.HAS_SOURCE_DEBUG_KEY, mHasSourceDebugKey);
             json.put(TriggerSpecs.FlexEventReportJsonKeys.PRIORITY, mPriority);
         } catch (JSONException e) {
             LoggerFactory.getMeasurementLogger()
@@ -194,5 +239,7 @@
     private interface JsonKeys {
         String TRIGGER_ID = "trigger_id";
         String DEDUP_KEY = "dedup_key";
+        String DEBUG_KEY = "debug_key";
+        String HAS_SOURCE_DEBUG_KEY = "has_source_debug_key";
     }
 }
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/EventReport.java b/adservices/service-core/java/com/android/adservices/service/measurement/EventReport.java
index 3ac460e..66eba53 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/EventReport.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/EventReport.java
@@ -494,7 +494,8 @@
                 @NonNull AttributedTrigger attributedTrigger,
                 long reportTime,
                 @NonNull Pair<Long, Long> triggerSummaryBucket,
-                @NonNull Pair<UnsignedLong, UnsignedLong> debugKeyPair,
+                @Nullable UnsignedLong sourceDebugKey,
+                @NonNull List<UnsignedLong> debugKeys,
                 @NonNull EventReportWindowCalcDelegate eventReportWindowCalcDelegate,
                 @NonNull SourceNoiseHandler sourceNoiseHandler,
                 List<Uri> eventReportDestinations) {
@@ -504,10 +505,10 @@
             mBuilding.mStatus = Status.PENDING;
             mBuilding.mAttributionDestinations = eventReportDestinations;
             mBuilding.mSourceType = source.getSourceType();
-            mBuilding.mSourceDebugKey = debugKeyPair.first;
-            mBuilding.mTriggerDebugKey = debugKeyPair.second;
+            mBuilding.mSourceDebugKey = sourceDebugKey;
+            mBuilding.mTriggerDebugKeys = debugKeys;
             mBuilding.mDebugReportStatus = DebugReportStatus.NONE;
-            if (mBuilding.mSourceDebugKey != null && mBuilding.mTriggerDebugKey != null) {
+            if (mBuilding.mSourceDebugKey != null && debugKeys.size() > 0) {
                 mBuilding.mDebugReportStatus = DebugReportStatus.PENDING;
             }
             mBuilding.mSourceId = source.getId();
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/TriggerSpecs.java b/adservices/service-core/java/com/android/adservices/service/measurement/TriggerSpecs.java
index a4b01d2..1411c2e 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/TriggerSpecs.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/TriggerSpecs.java
@@ -276,13 +276,17 @@
                 continue;
             }
 
-            // Event reports are sorted by summary bucket so this event report must be either for
-            // the first or the next bucket.
-            triggerDataToBucketIndexMap.merge(
-                    eventReport.getTriggerData(), 1, (oldValue, value) -> oldValue + 1);
+            UnsignedLong triggerData = eventReport.getTriggerData();
 
-            Pair<Long, Long> summaryBucket = eventReport.getTriggerSummaryBucket();
-            long bucketSize = summaryBucket.second - summaryBucket.first + 1;
+            // Event reports are sorted by summary bucket so this event report must be either for
+            // the first or the next bucket. The index for the map is one higher, corresponding to
+            // the current bucket we'll start with for attribution.
+            triggerDataToBucketIndexMap.merge(triggerData, 1, (oldValue, value) -> oldValue + 1);
+
+            List<Long> buckets = getSummaryBucketsForTriggerData(triggerData);
+            int bucketIndex = triggerDataToBucketIndexMap.get(triggerData) - 1;
+            long prevBucket = bucketIndex == 0 ? 0L : buckets.get(bucketIndex - 1);
+            long bucketSize = buckets.get(bucketIndex) - prevBucket;
 
             for (AttributedTrigger attributedTrigger : mAttributedTriggersRef) {
                 bucketSize -= restoreTriggerContributionAndGetBucketDelta(
@@ -315,8 +319,7 @@
                 return bucketSize;
             // The trigger only covers some of the report's bucket.
             } else {
-                long diff = attributedTrigger.getValue()
-                        - attributedTrigger.getContribution();
+                long diff = attributedTrigger.remainingValue();
                 attributedTrigger.addContribution(diff);
                 return diff;
             }
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java b/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java
index 2379456..1544aa6 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/attribution/AttributionJobHandler.java
@@ -760,20 +760,21 @@
         Pair<UnsignedLong, UnsignedLong> debugKeyPair =
                 new DebugKeyAccessor(measurementDao).getDebugKeys(source, trigger);
 
-        EventReport newEventReport =
-                new EventReport.Builder()
-                        .populateFromSourceAndTrigger(
-                                source,
-                                trigger,
-                                eventTrigger,
-                                debugKeyPair,
-                                mEventReportWindowCalcDelegate,
-                                mSourceNoiseHandler,
-                                getEventReportDestinations(source, trigger.getDestinationType()))
-                        .build();
         if (!mFlags.getMeasurementFlexibleEventReportingApiEnabled()
                 || source.getTriggerSpecsString() == null
                 || source.getTriggerSpecsString().isEmpty()) {
+            EventReport newEventReport =
+                    new EventReport.Builder()
+                            .populateFromSourceAndTrigger(
+                                    source,
+                                    trigger,
+                                    eventTrigger,
+                                    debugKeyPair,
+                                    mEventReportWindowCalcDelegate,
+                                    mSourceNoiseHandler,
+                                    getEventReportDestinations(
+                                            source, trigger.getDestinationType()))
+                            .build();
             if (!provisionEventReportQuota(source, trigger, newEventReport, measurementDao)) {
                 return TriggeringStatus.DROPPED;
             }
@@ -792,7 +793,7 @@
                 && !source.getTriggerSpecsString().isEmpty()) {
             try {
                 source.buildTriggerSpecs();
-                if (!provisionEventReportFlexEventApiQuota(
+                if (!generateFlexEventReports(
                         source, trigger, eventTrigger, debugKeyPair, measurementDao)) {
                     return TriggeringStatus.DROPPED;
                 }
@@ -806,37 +807,28 @@
         return TriggeringStatus.ATTRIBUTED;
     }
 
-    private boolean provisionEventReportFlexEventApiQuota(
+    private static int restoreTriggerContributionsAndProvisionFlexEventReportQuota(
             Source source,
             Trigger trigger,
-            EventTrigger eventTrigger,
-            Pair<UnsignedLong, UnsignedLong> debugKeyPair,
-            IMeasurementDao measurementDao)
-            throws DatastoreException {
-        TriggerSpecs triggerSpecs = source.getTriggerSpecs();
-
-        if (!triggerSpecs.containsTriggerData(eventTrigger.getTriggerData())) {
-            return false;
-        }
+            Map<UnsignedLong, Integer> triggerDataToBucketIndexMap,
+            IMeasurementDao measurementDao) throws DatastoreException {
 
         List<EventReport> sourceEventReports = measurementDao.getSourceEventReports(source);
 
         List<EventReport> reportsToDelete = new ArrayList<>();
-        // Store the current bucket index for each trigger data
-        Map<UnsignedLong, Integer> triggerDataToBucketIndexMap = new HashMap<>();
 
-        triggerSpecs.prepareFlexAttribution(
+        source.getTriggerSpecs().prepareFlexAttribution(
                 sourceEventReports,
                 trigger.getTriggerTime(),
                 reportsToDelete,
                 triggerDataToBucketIndexMap);
 
-        int numEventReports = sourceEventReports.size() - reportsToDelete.size();
-        int maxEventReports = triggerSpecs.getMaxReports();
+        int numEarlierScheduledReports = sourceEventReports.size() - reportsToDelete.size();
+        int maxEventReports = source.getTriggerSpecs().getMaxReports();
 
         // Completed reports already covered the allotted quota.
-        if (numEventReports == maxEventReports) {
-            return false;
+        if (numEarlierScheduledReports == maxEventReports) {
+            return 0;
         }
 
         // Delete pending reports. We will recreate an updated sequence below.
@@ -844,15 +836,49 @@
             measurementDao.deleteEventReport(eventReport);
         }
 
+        return maxEventReports - numEarlierScheduledReports;
+    }
+
+    private boolean generateFlexEventReports(
+            Source source,
+            Trigger trigger,
+            EventTrigger eventTrigger,
+            Pair<UnsignedLong, UnsignedLong> debugKeyPair,
+            IMeasurementDao measurementDao) throws DatastoreException {
+        TriggerSpecs triggerSpecs = source.getTriggerSpecs();
+
+        if (!triggerSpecs.containsTriggerData(eventTrigger.getTriggerData())) {
+            return false;
+        }
+
+        // Store the current bucket index for each trigger data
+        Map<UnsignedLong, Integer> triggerDataToBucketIndexMap = new HashMap<>();
+
+        int remainingReportQuota =
+                restoreTriggerContributionsAndProvisionFlexEventReportQuota(
+                        source, trigger, triggerDataToBucketIndexMap, measurementDao);
+
+        if (remainingReportQuota == 0) {
+            return false;
+        }
+
         List<AttributedTrigger> attributedTriggers = source.getAttributedTriggers();
 
+        long triggerValue =
+                triggerSpecs.getSummaryOperatorType(eventTrigger.getTriggerData())
+                        == TriggerSpec.SummaryOperatorType.COUNT
+                                ? 1L
+                                : eventTrigger.getTriggerValue();
+
         attributedTriggers.add(new AttributedTrigger(
                 trigger.getId(),
                 eventTrigger.getTriggerPriority(),
                 eventTrigger.getTriggerData(),
-                eventTrigger.getTriggerValue(),
+                triggerValue,
                 trigger.getTriggerTime(),
-                eventTrigger.getDedupKey()));
+                eventTrigger.getDedupKey(),
+                debugKeyPair.second,
+                debugKeyPair.first != null));
 
         attributedTriggers.sort(
                 Comparator.comparingLong(AttributedTrigger::getPriority).reversed()
@@ -860,6 +886,8 @@
 
         // Store for each trigger data any amount already covered for the current bucket.
         Map<UnsignedLong, Long> triggerDataToBucketAmountMap = new HashMap<>();
+        Map<UnsignedLong, List<AttributedTrigger>> triggerDataToContributingTriggersMap =
+                new HashMap<>();
 
         for (AttributedTrigger attributedTrigger : attributedTriggers) {
             // Flex API already inserts the attributed trigger and does not need an explicit action
@@ -870,19 +898,17 @@
                 measurementDao.updateSourceEventReportDedupKeys(source);
             }
 
-            numEventReports += updateFlexAttributionStateAndGetNumReports(
+            remainingReportQuota -= updateFlexAttributionStateAndGetNumReports(
                     source,
                     trigger,
                     attributedTrigger,
-                    debugKeyPair,
-                    numEventReports,
-                    maxEventReports,
-                    triggerSpecs,
+                    remainingReportQuota,
                     triggerDataToBucketIndexMap,
                     triggerDataToBucketAmountMap,
+                    triggerDataToContributingTriggersMap,
                     measurementDao);
 
-            if (numEventReports == maxEventReports) {
+            if (remainingReportQuota == 0) {
                 break;
             }
         }
@@ -891,6 +917,7 @@
                 source.getId(),
                 source.attributedTriggersToJsonFlexApi());
 
+        // TODO (b/307786346): represent actual report count.
         return true;
     }
 
@@ -898,17 +925,15 @@
             Source source,
             Trigger trigger,
             AttributedTrigger attributedTrigger,
-            Pair<UnsignedLong, UnsignedLong> debugKeyPair,
-            int numEventReports,
-            int maxEventReports,
-            TriggerSpecs triggerSpecs,
+            int remainingReportQuota,
             Map<UnsignedLong, Integer> triggerDataToBucketIndexMap,
             Map<UnsignedLong, Long> triggerDataToBucketAmountMap,
+            Map<UnsignedLong, List<AttributedTrigger>> triggerDataToContributingTriggersMap,
             IMeasurementDao measurementDao) throws DatastoreException {
+        TriggerSpecs triggerSpecs = source.getTriggerSpecs();
         UnsignedLong triggerData = attributedTrigger.getTriggerData();
 
         triggerDataToBucketIndexMap.putIfAbsent(triggerData, 0);
-        triggerDataToBucketAmountMap.putIfAbsent(triggerData, 0L);
 
         int bucketIndex = triggerDataToBucketIndexMap.get(triggerData);
         List<Long> buckets = triggerSpecs.getSummaryBucketsForTriggerData(triggerData);
@@ -919,62 +944,53 @@
             return 0;
         }
 
+        triggerDataToBucketAmountMap.putIfAbsent(triggerData, 0L);
+        triggerDataToContributingTriggersMap.putIfAbsent(triggerData, new ArrayList<>());
+
+        List<AttributedTrigger> contributingTriggers =
+                triggerDataToContributingTriggersMap.get(triggerData);
+        if (attributedTrigger.remainingValue() > 0L) {
+            contributingTriggers.add(attributedTrigger);
+        }
+
         long prevBucket = bucketIndex == 0 ? 0L : buckets.get(bucketIndex - 1);
-        long bucketSize = buckets.get(bucketIndex) - prevBucket;
         int numReportsCreated = 0;
 
-        // value_sum operator
-        if (triggerSpecs.getSummaryOperatorType(triggerData)
-                == TriggerSpec.SummaryOperatorType.VALUE_SUM) {
-            for (int i = bucketIndex; i < buckets.size(); i++) {
-                long bucket = buckets.get(i);
-                bucketSize = bucket - prevBucket;
-                long bucketAmount = triggerDataToBucketAmountMap.get(triggerData);
+        for (int i = bucketIndex; i < buckets.size(); i++) {
+            long bucket = buckets.get(i);
+            long bucketSize = bucket - prevBucket;
+            long bucketAmount = triggerDataToBucketAmountMap.get(triggerData);
 
-                if (attributedTrigger.remainingValue() >= bucketSize - bucketAmount) {
-                    finalizeEventReportCreationForFlex(
-                            source,
-                            trigger,
-                            attributedTrigger,
-                            debugKeyPair,
-                            TriggerSpecs.getSummaryBucketFromIndex(i, buckets),
-                            measurementDao);
-                    numReportsCreated += 1;
-                    attributedTrigger.addContribution(bucketSize - bucketAmount);
-                    triggerDataToBucketIndexMap.put(triggerData, i + 1);
-                    triggerDataToBucketAmountMap.put(triggerData, 0L);
-
-                    if (numEventReports + numReportsCreated == maxEventReports) {
-                        return numReportsCreated;
-                    }
-                } else {
-                    triggerDataToBucketIndexMap.put(triggerData, i);
-                    long diff = attributedTrigger.getValue()
-                            - attributedTrigger.getContribution();
-                    triggerDataToBucketAmountMap.put(triggerData, diff);
-                    attributedTrigger.addContribution(diff);
-                    break;
-                }
-                prevBucket = bucket;
-            }
-        // count operator for a trigger that we haven't yet counted.
-        } else if (attributedTrigger.getContribution() == 0) {
-            if (bucketSize - triggerDataToBucketAmountMap.get(triggerData) == 1) {
+            if (attributedTrigger.remainingValue() >= bucketSize - bucketAmount) {
                 finalizeEventReportCreationForFlex(
                         source,
                         trigger,
                         attributedTrigger,
-                        debugKeyPair,
-                        TriggerSpecs.getSummaryBucketFromIndex(bucketIndex, buckets),
+                        contributingTriggers,
+                        TriggerSpecs.getSummaryBucketFromIndex(i, buckets),
                         measurementDao);
                 numReportsCreated += 1;
-                triggerDataToBucketIndexMap.put(triggerData, bucketIndex + 1);
+
+                if (remainingReportQuota - numReportsCreated == 0) {
+                    return numReportsCreated;
+                }
+
+                attributedTrigger.addContribution(bucketSize - bucketAmount);
+                triggerDataToBucketIndexMap.put(triggerData, i + 1);
                 triggerDataToBucketAmountMap.put(triggerData, 0L);
-                attributedTrigger.addContribution(1L);
+                contributingTriggers.clear();
+                if (attributedTrigger.remainingValue() > 0L) {
+                    contributingTriggers.add(attributedTrigger);
+                }
             } else {
+                triggerDataToBucketIndexMap.put(triggerData, i);
+                long diff = attributedTrigger.remainingValue();
                 triggerDataToBucketAmountMap.merge(
-                        triggerData, 0L, (oldValue, value) -> oldValue + 1L);
+                        triggerData, diff, (oldValue, value) -> oldValue + diff);
+                attributedTrigger.addContribution(diff);
+                break;
             }
+            prevBucket = bucket;
         }
 
         return numReportsCreated;
@@ -1085,7 +1101,7 @@
             Source source,
             Trigger trigger,
             AttributedTrigger attributedTrigger,
-            Pair<UnsignedLong, UnsignedLong> debugKeyPair,
+            List<AttributedTrigger> contributingTriggers,
             Pair<Long, Long> triggerSummaryBucket,
             IMeasurementDao measurementDao)
             throws DatastoreException {
@@ -1102,6 +1118,8 @@
                 // much earlier were the actual attributed triggers counted towards the bucket.
                 trigger.getTriggerTime(),
                 attributedTrigger.getTriggerData());
+        Pair<UnsignedLong, List<UnsignedLong>> debugKeys =
+                getDebugKeysForFlex(contributingTriggers, source);
         EventReport eventReport =
                 new EventReport.Builder()
                         .getForFlex(
@@ -1110,7 +1128,8 @@
                                 attributedTrigger,
                                 reportTime,
                                 triggerSummaryBucket,
-                                debugKeyPair,
+                                debugKeys.first,
+                                debugKeys.second,
                                 mEventReportWindowCalcDelegate,
                                 mSourceNoiseHandler,
                                 getEventReportDestinations(
@@ -1474,6 +1493,36 @@
                 : WebAddresses.topPrivateDomainAndScheme(uri);
     }
 
+    private static Pair<UnsignedLong, List<UnsignedLong>> getDebugKeysForFlex(
+            List<AttributedTrigger> contributingTriggers, Source source) {
+        List<UnsignedLong> triggerDebugKeys = new ArrayList<>();
+        // To provide a source debug key in the event report, the source debug key must have been
+        // populated for each evaluation for source and trigger for all triggers contributing to the
+        // bucket.
+        boolean allBucketContributorsHadNonNullSourceDebugKeys = true;
+        for (AttributedTrigger trigger : contributingTriggers) {
+            // Only add a debug key to the result if the invariant is maintained. Otherwise, the
+            // invariant has been broken, but conclude the iteration to process source debug-key.
+            if (trigger.getDebugKey() != null) {
+                triggerDebugKeys.add(trigger.getDebugKey());
+            }
+            // Update the value of the boolean for source debug key as a series of AND
+            // operations that must all be true.
+            allBucketContributorsHadNonNullSourceDebugKeys &= trigger.hasSourceDebugKey();
+        }
+        // We are allowed to access the actual source debug key value if the invariant has been
+        // maintained.
+        UnsignedLong sourceDebugKey = allBucketContributorsHadNonNullSourceDebugKeys
+                ? source.getDebugKey()
+                : null;
+        // All triggers must have debug keys for the report to include any.
+        if (contributingTriggers.size() == triggerDebugKeys.size()) {
+            return Pair.create(sourceDebugKey, triggerDebugKeys);
+        } else {
+            return Pair.create(sourceDebugKey, Collections.emptyList());
+        }
+    }
+
     private static boolean hasDeduplicationKey(@NonNull Source source,
             @NonNull UnsignedLong dedupKey) {
         for (AttributedTrigger attributedTrigger : source.getAttributedTriggers()) {
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/ReportUtil.java b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/ReportUtil.java
index 82cd379..8c74428 100644
--- a/adservices/service-core/java/com/android/adservices/service/measurement/reporting/ReportUtil.java
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/reporting/ReportUtil.java
@@ -71,13 +71,13 @@
      * Prepare summary bucket for report JSON
      *
      * @param summaryBucket the summary bucket
-     * @return the string encoded summary bucket in format [start, end]
+     * @return the encoded summary bucket in format [start, end]
      */
     @Nullable
-    public static String serializeSummaryBucket(@NonNull Pair<Long, Long> summaryBucket) {
+    public static JSONArray serializeSummaryBucket(@NonNull Pair<Long, Long> summaryBucket) {
         JSONArray result = new JSONArray();
         result.put(summaryBucket.first);
         result.put(summaryBucket.second);
-        return result.toString();
+        return result;
     }
 }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_incremental_bucket_larger_than_one_for_count_generate_report.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_incremental_bucket_larger_than_one_for_count_generate_report.json
index 55f8320..6c5e51c 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_incremental_bucket_larger_than_one_for_count_generate_report.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_incremental_bucket_larger_than_one_for_count_generate_report.json
@@ -137,7 +137,7 @@
           "source_event_id": "2",
           "trigger_data": "0",
           "source_type": "event",
-          "trigger_summary_bucket": "[2,2147483646]",
+          "trigger_summary_bucket": [2, 2147483646],
           "randomized_trigger_rate": 0.000002494582008677539
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_mixed_priority_for_multiple_reports.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_mixed_priority_for_multiple_reports.json
index a77d8f1..8072324 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_mixed_priority_for_multiple_reports.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_mixed_priority_for_multiple_reports.json
@@ -137,7 +137,7 @@
           "source_event_id": "123",
           "trigger_data": "1",
           "source_type": "event",
-          "trigger_summary_bucket": "[10,19]",
+          "trigger_summary_bucket": [10, 19],
           "randomized_trigger_rate": 0.00001663031163901838
         }
       },
@@ -150,7 +150,7 @@
           "source_event_id": "123",
           "trigger_data": "0",
           "source_type": "event",
-          "trigger_summary_bucket": "[10,19]",
+          "trigger_summary_bucket": [10, 19],
           "randomized_trigger_rate": 0.00001663031163901838
         }
       },
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_mixed_priority_for_one_report.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_mixed_priority_for_one_report.json
index 1bda7c6..67bde2d 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_mixed_priority_for_one_report.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_mixed_priority_for_one_report.json
@@ -1,5 +1,5 @@
 {
-  "description": "One event source with two trigger specs, followed by three triggers. The last two triggers that have the highest priority win attribution.",
+  "description": "One event source followed by three triggers. The last two triggers that have the highest priority win attribution.",
   "phflags_override": {
     "measurement_flexible_event_reporting_api_enabled": "true"
   },
@@ -136,7 +136,7 @@
           "source_event_id": "123",
           "trigger_data": "1",
           "source_type": "event",
-          "trigger_summary_bucket": "[10,99]",
+          "trigger_summary_bucket": [10, 99],
           "randomized_trigger_rate": 0.0000033261065791548415
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_one_sources_default_event_three_triggers.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_one_sources_default_event_three_triggers.json
index af6423e..7477e2a 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_one_sources_default_event_three_triggers.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_one_sources_default_event_three_triggers.json
@@ -137,7 +137,7 @@
           "source_event_id": "2",
           "trigger_data": "1",
           "source_type": "event",
-          "trigger_summary_bucket": "[1,2147483646]",
+          "trigger_summary_bucket": [1, 2147483646],
           "randomized_trigger_rate": 0.000002494582008677539
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_one_sources_default_nav_three_triggers.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_one_sources_default_nav_three_triggers.json
index 78301d4..fc7e05e 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_one_sources_default_nav_three_triggers.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_one_sources_default_nav_three_triggers.json
@@ -145,7 +145,7 @@
           "source_event_id": "2",
           "trigger_data": "2",
           "source_type": "navigation",
-          "trigger_summary_bucket": "[1,1]",
+          "trigger_summary_bucket": [1, 1],
           "randomized_trigger_rate": 0.0001371835308365974
         }
       },
@@ -158,7 +158,7 @@
           "source_event_id": "2",
           "trigger_data": "1",
           "source_type": "navigation",
-          "trigger_summary_bucket": "[1,1]",
+          "trigger_summary_bucket": [1, 1],
           "randomized_trigger_rate": 0.0001371835308365974
         }
       },
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_trigger_data_partial_mismatch.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_trigger_data_partial_mismatch.json
index 54d9c49..6018b0f 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_trigger_data_partial_mismatch.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_trigger_data_partial_mismatch.json
@@ -145,7 +145,7 @@
           "source_event_id": "2",
           "trigger_data": "2",
           "source_type": "navigation",
-          "trigger_summary_bucket": "[1,1]",
+          "trigger_summary_bucket": [1, 1],
           "randomized_trigger_rate": 0.0001371835308365974
         }
       },
@@ -158,7 +158,7 @@
           "source_event_id": "2",
           "trigger_data": "1",
           "source_type": "navigation",
-          "trigger_summary_bucket": "[1,1]",
+          "trigger_summary_bucket": [1, 1],
           "randomized_trigger_rate": 0.0001371835308365974
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_two_specs_trigger_priority_wins.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_two_specs_trigger_priority_wins.json
index a09b035..20248b3 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_two_specs_trigger_priority_wins.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_two_specs_trigger_priority_wins.json
@@ -146,7 +146,7 @@
           "source_event_id": "123",
           "trigger_data": "0",
           "source_type": "event",
-          "trigger_summary_bucket": "[1,1]",
+          "trigger_summary_bucket": [1, 1],
           "randomized_trigger_rate": 0.00001746181270119995
         }
       },
@@ -159,7 +159,7 @@
           "source_event_id": "123",
           "trigger_data": "2",
           "source_type": "event",
-          "trigger_summary_bucket": "[1,1]",
+          "trigger_summary_bucket": [1, 1],
           "randomized_trigger_rate": 0.00001746181270119995
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_dedup_key_remove_trigger.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_dedup_key_remove_trigger.json
index c28b506..7fd2690 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_dedup_key_remove_trigger.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_dedup_key_remove_trigger.json
@@ -116,7 +116,7 @@
           "source_event_id": "2",
           "trigger_data": "0",
           "source_type": "navigation",
-          "trigger_summary_bucket": "[10,99]",
+          "trigger_summary_bucket": [10, 99],
           "randomized_trigger_rate": 0.000012472785585841613
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_no_trigger_dedup_key.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_no_trigger_dedup_key.json
index 2e03c47..36fedaf 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_no_trigger_dedup_key.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_no_trigger_dedup_key.json
@@ -138,7 +138,7 @@
           "source_event_id": "2",
           "trigger_data": "1",
           "source_type": "event",
-          "trigger_summary_bucket": "[10,2147483646]",
+          "trigger_summary_bucket": [10, 2147483646],
           "randomized_trigger_rate": 0.000002494582008677539
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_no_trigger_event_data.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_no_trigger_event_data.json
index df8eb9e..894a649 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_no_trigger_event_data.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_no_trigger_event_data.json
@@ -145,7 +145,7 @@
           "source_event_id": "2",
           "trigger_data": "0",
           "source_type": "navigation",
-          "trigger_summary_bucket": "[10,99]",
+          "trigger_summary_bucket": [10, 99],
           "randomized_trigger_rate": 0.000012472785585841613
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_across_report_window.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_across_report_window_debug_report.json
similarity index 72%
rename from adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_across_report_window.json
rename to adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_across_report_window_debug_report.json
index d82870a..e585eec 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_across_report_window.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_across_report_window_debug_report.json
@@ -1,5 +1,5 @@
 {
-  "description": "value sum on navigation source, triggers are across multiple window",
+  "description": "value sum on navigation source, triggers are across multiple window, includes debug report.",
   "phflags_override": {
     "measurement_flexible_event_reporting_api_enabled": "true"
   },
@@ -14,6 +14,7 @@
         "responses": [
           {
             "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
             "response": {
               "Attribution-Reporting-Register-Source": {
                 "source_event_id": "2",
@@ -39,7 +40,8 @@
                     ]
                   }
                 ],
-                "max_event_level_reports": 1
+                "max_event_level_reports": 1,
+                "debug_key": "333"
               },
               "Location": null,
               "Attribution-Reporting-Redirect": null
@@ -58,6 +60,7 @@
         "responses": [
           {
             "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
             "response": {
               "Attribution-Reporting-Register-Trigger": {
                 "event_trigger_data": [
@@ -67,7 +70,9 @@
                     "deduplication_key": "10",
                     "value": "2"
                   }
-                ]
+                ],
+                "debug_key": "444",
+                "debug_reporting": true
               },
               "Location": null,
               "Attribution-Reporting-Redirect": null
@@ -84,6 +89,7 @@
         "responses": [
           {
             "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
             "response": {
               "Attribution-Reporting-Register-Trigger": {
                 "event_trigger_data": [
@@ -93,7 +99,9 @@
                     "deduplication_key": "11",
                     "value": "9"
                   }
-                ]
+                ],
+                "debug_key": "555",
+                "debug_reporting": true
               },
               "Location": null,
               "Attribution-Reporting-Redirect": null
@@ -115,8 +123,27 @@
           "source_event_id": "2",
           "trigger_data": "0",
           "source_type": "navigation",
-          "trigger_summary_bucket": "[10,2147483646]",
-          "randomized_trigger_rate": 0.000004157629766763622
+          "source_debug_key": "333",
+          "trigger_summary_bucket": [10, 2147483646],
+          "randomized_trigger_rate": 0.000004157629766763622,
+          "trigger_debug_keys": ["555", "444"]
+        }
+      }
+    ],
+    "debug_event_level_results": [
+      {
+        "report_time": "1800203600001",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/debug/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "1800599000",
+          "source_event_id": "2",
+          "trigger_data": "0",
+          "source_type": "navigation",
+          "source_debug_key": "333",
+          "trigger_summary_bucket": [10, 2147483646],
+          "randomized_trigger_rate": 0.000004157629766763622,
+          "trigger_debug_keys": ["555", "444"]
         }
       }
     ],
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_base_case_generate_report_one_trigger.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_base_case_generate_report_one_trigger.json
index 98ecc80..10ac7d1 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_base_case_generate_report_one_trigger.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_base_case_generate_report_one_trigger.json
@@ -140,7 +140,7 @@
           "source_event_id": "2",
           "trigger_data": "0",
           "source_type": "event",
-          "trigger_summary_bucket": "[10,2147483646]",
+          "trigger_summary_bucket": [10, 2147483646],
           "randomized_trigger_rate": 0.000002494582008677539
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_multiple_buckets.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_multiple_buckets.json
index 29179a3..d0b94e9 100644
--- a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_multiple_buckets.json
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_event_source_multiple_buckets.json
@@ -116,7 +116,7 @@
           "source_event_id": "2",
           "trigger_data": "0",
           "source_type": "navigation",
-          "trigger_summary_bucket": "[10,99]",
+          "trigger_summary_bucket": [10, 99],
           "randomized_trigger_rate": 0.000012472785585841613
         }
       },
@@ -129,7 +129,7 @@
           "source_event_id": "2",
           "trigger_data": "0",
           "source_type": "navigation",
-          "trigger_summary_bucket": "[100,2147483646]",
+          "trigger_summary_bucket": [100, 2147483646],
           "randomized_trigger_rate": 0.000012472785585841613
         }
       }
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_two_specs_two_report_windows_debug_report.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_two_specs_two_report_windows_debug_report.json
new file mode 100644
index 0000000..a851971
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_two_specs_two_report_windows_debug_report.json
@@ -0,0 +1,469 @@
+{
+  // Source's trigger specs:
+  //
+  // trigger_data: [0, 1]
+  // end_times: [3600, 7200]
+  // summary_buckets: [10, 50]
+  //
+  // trigger_data: [2, 3]
+  // end_times: [5400, 9000]
+  // summary_buckets: [20, 70]
+  //
+  // max_event_level_reports: 4
+  //
+  // Triggers:
+  //
+  // T1 time 1200, data 0, value 9, priority 200, debug key 1
+  // T2 time 2000, data 1, value 10, priority 150
+  // T3 time 2400, data 0, value 30, priority 100, debug key 3
+  // T4 time 3000, data 2, value 18, priority 150, debug key 4
+  //
+  // Report window 1 at 3600:
+  //   Trigger ordering: T1 -> T3 -> T2 -> T4
+  //   * T1 + T3 (data 0) generate report and debug report, bucket at 10 (+29 at priority 100)
+  //   * T2 (data 1) generates report, bucket at 10
+  //   T4 (data 2) does not generate a report, bucket at 18
+  //   Fully contributed: T1, T2
+  //
+  // T5 time 4000, data 2, value 1, priority 150, debug key 5
+  // T6 time 5000, data 3, value 19, priority 130
+  // T7 time 5300, data 2, value 40, priority 400
+  //
+  // Report window 1 at 5400:
+  //   Trigger ordering: T7 -> T4 -> T5 -> T6 -> T3
+  //   * T7 (data 2) generates a report, bucket at 20 (+20 at priority 400)
+  //   T4 (data 2) adds to bucket, bucket at 58
+  //   T5 (data 2) adds to bucket, bucket at 59
+  //   T6 (data 3) does not generate a report, bucket at 19
+  //   T3 (data 0) does not generate a report, bucket at 39
+  //
+  // T8 time 6000, data 0, value 5, priority 90
+  // T9 time 6500, data 2, value 20, priority 50, debug key 9
+  // T10 time 7000, data 0, value 20, priority 50, debug key 10
+  //
+  // Report window 2 at 7200:
+  //   Trigger ordering: T7 -> T4 -> T5 -> T6 -> T3 -> T8 -> T9 -> T10
+  //   T7 + T4 + T5 (data 2) get bucket to 59
+  //   T6 (data 3) does not generate a report, bucket at 19
+  //   T3 + T8 (data 0) get bucket to 44
+  //   * T9 (data 2) crosses bucket to generate a speculative report but no debug report
+  //     because T7 is missing a debug key, bucket at 70 (+9 at priority 50)
+  //   T10 (data 0) would cross bucket to generate a report, but quota was reached
+  //
+  // Report window 2 at 9000:
+  //   Report is sent for T7 + T4 + T5 + T9
+  //   Fully contributed: T4, T5, T7
+  "description": "One event source with two trigger specs, see comment above for full description.",
+  "phflags_override": {
+    "measurement_flex_api_max_information_gain_navigation": 15,
+    "measurement_flexible_event_reporting_api_enabled": "true",
+    "measurement_min_event_report_delay_millis": 0
+  },
+  "input": {
+    "sources": [
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "source_type": "navigation",
+          "registrant": "example.1s1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Source": {
+                "source_event_id": "123",
+                "destination": "android-app://example.2d1.test",
+                "trigger_specs": [
+                  {
+                    "trigger_data": [0, 1],
+                    "event_report_windows": {
+                      "end_times": [3600, 7200]
+                    },
+                    "summary_window_operator": "value_sum",
+                    "summary_buckets": [10, 50]
+                  },
+                  {
+                    "trigger_data": [2, 3],
+                    "event_report_windows": {
+                      "end_times": [5400, 9000]
+                    },
+                    "summary_window_operator": "value_sum",
+                    "summary_buckets": [20, 70]
+                  }
+                ],
+                "max_event_level_reports": 4,
+                "debug_key": "111"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "0"
+      }
+    ],
+    "triggers": [
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "0",
+                    "value": "9",
+                    "priority": "200"
+                  }
+                ],
+                "debug_key": "1"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "1200000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            // Ad ID permission results in a report with the source debug key
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "1",
+                    "value": "10",
+                    "priority": "150"
+                  }
+                ]
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "2000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "0",
+                    "value": "30",
+                    "priority": "100"
+                  }
+                ],
+                "debug_key": "3"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "2400000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "2",
+                    "value": "18",
+                    "priority": "150"
+                  }
+                ],
+                "debug_key": "4"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "3000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "2",
+                    "value": "1",
+                    "priority": "150"
+                  }
+                ],
+                "debug_key": "5"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "4000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "3",
+                    "value": "19",
+                    "priority": "130"
+                  }
+                ]
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "5000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "2",
+                    "value": "40",
+                    "priority": "400"
+                  }
+                ]
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "5300000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "0",
+                    "value": "5",
+                    "priority": "90"
+                  }
+                ]
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "6000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "2",
+                    "value": "20",
+                    "priority": "50"
+                  }
+                ],
+                "debug_key": "9"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "6500000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "0",
+                    "value": "20",
+                    "priority": "50"
+                  }
+                ],
+                "debug_key": "10"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "7000000"
+      }
+    ]
+  },
+  "output": {
+    "event_level_results": [
+      {
+        "report_time": "3600000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "3600",
+          "source_event_id": "123",
+          "trigger_data": "1",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_summary_bucket": [10, 49],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "3600000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "3600",
+          "source_event_id": "123",
+          "trigger_data": "0",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["1", "3"],
+          "trigger_summary_bucket": [10, 49],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "5400000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "5400",
+          "source_event_id": "123",
+          "trigger_data": "2",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_summary_bucket": [20, 69],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "9000000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "9000",
+          "source_event_id": "123",
+          "trigger_data": "2",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_summary_bucket": [70, 2147483646],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      }
+    ],
+    "debug_event_level_results": [
+      {
+        "report_time": "2400000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/debug/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "3600",
+          "source_event_id": "123",
+          "trigger_data": "0",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["1", "3"],
+          "trigger_summary_bucket": [10, 49],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "3000000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/debug/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "3600",
+          "source_event_id": "123",
+          "trigger_data": "0",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["1", "3"],
+          "trigger_summary_bucket": [10, 49],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      }
+    ],
+    "aggregatable_results": []
+  }
+}
diff --git a/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_two_specs_two_report_windows_multiple_debug_keys.json b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_two_specs_two_report_windows_multiple_debug_keys.json
new file mode 100644
index 0000000..7ef78fb
--- /dev/null
+++ b/adservices/tests/unittest/service-core/assets/msmt_e2e_tests/flex_event/flex_event_value_sum_two_specs_two_report_windows_multiple_debug_keys.json
@@ -0,0 +1,515 @@
+{
+  // Source's trigger specs:
+  //
+  // trigger_data: [0, 1]
+  // end_times: [3600, 7200]
+  // summary_buckets: [10, 50]
+  //
+  // trigger_data: [2, 3]
+  // end_times: [5400, 9000]
+  // summary_buckets: [20, 70]
+  //
+  // max_event_level_reports: 4
+  //
+  // Triggers:
+  //
+  // T1 time 1200, data 0, value 9, priority 200, debug key 1
+  // T2 time 2000, data 1, value 10, priority 150
+  // T3 time 2400, data 0, value 30, priority 100, debug key 3
+  // T4 time 3000, data 2, value 18, priority 150, debug key 4
+  //
+  // Report window 1 at 3600:
+  //   Trigger ordering: T1 -> T3 -> T2 -> T4
+  //   * T1 + T3 (data 0) generate report and debug report, bucket at 10 (+29 at priority 100)
+  //   * T2 (data 1) generates report, bucket at 10
+  //   T4 (data 2) does not generate a report, bucket at 18
+  //   Fully contributed: T1, T2
+  //
+  // T5 time 4000, data 2, value 1, priority 150, debug key 5
+  // T6 time 5000, data 3, value 19, priority 130
+  // T7 time 5300, data 2, value 40, priority 400, debug key 7
+  //
+  // Report window 1 at 5400:
+  //   Trigger ordering: T7 -> T4 -> T5 -> T6 -> T3
+  //   * T7 (data 2) generates a report, bucket at 20 (+20 at priority 400)
+  //   T4 (data 2) adds to bucket, bucket at 58
+  //   T5 (data 2) adds to bucket, bucket at 59
+  //   T6 (data 3) does not generate a report, bucket at 19
+  //   T3 (data 0) does not generate a report, bucket at 39
+  //
+  // T8 time 6000, data 0, value 5, priority 90
+  // T9 time 6500, data 2, value 20, priority 50, debug key 9
+  // T10 time 7000, data 0, value 20, priority 50, debug key 10
+  //
+  // Report window 2 at 7200:
+  //   Trigger ordering: T7 -> T4 -> T5 -> T6 -> T3 -> T8 -> T9 -> T10
+  //   T7 + T4 + T5 (data 2) get bucket to 59
+  //   T6 (data 3) does not generate a report, bucket at 19
+  //   T3 + T8 (data 0) get bucket to 44
+  //   * T9 (data 2) crosses bucket to generate a speculative report and debug report,
+  //     bucket at 70 (+9 at priority 50)
+  //   T10 (data 0) would cross bucket to generate a report, but quota was reached
+  //
+  // Report window 2 at 9000:
+  //   Report is sent for T7 + T4 + T5 + T9
+  //   Fully contributed: T4, T5, T7
+  "description": "One event source with two trigger specs, see comment above for full description.",
+  "phflags_override": {
+    "measurement_flex_api_max_information_gain_navigation": 15,
+    "measurement_flexible_event_reporting_api_enabled": "true",
+    "measurement_min_event_report_delay_millis": 0
+  },
+  "input": {
+    "sources": [
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "source_type": "navigation",
+          "registrant": "example.1s1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Source": {
+                "source_event_id": "123",
+                "destination": "android-app://example.2d1.test",
+                "trigger_specs": [
+                  {
+                    "trigger_data": [0, 1],
+                    "event_report_windows": {
+                      "end_times": [3600, 7200]
+                    },
+                    "summary_window_operator": "value_sum",
+                    "summary_buckets": [10, 50]
+                  },
+                  {
+                    "trigger_data": [2, 3],
+                    "event_report_windows": {
+                      "end_times": [5400, 9000]
+                    },
+                    "summary_window_operator": "value_sum",
+                    "summary_buckets": [20, 70]
+                  }
+                ],
+                "max_event_level_reports": 4,
+                "debug_key": "111"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "0"
+      }
+    ],
+    "triggers": [
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "0",
+                    "value": "9",
+                    "priority": "200"
+                  }
+                ],
+                "debug_key": "1"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "1200000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            // No Ad ID permission results in a report without the source debug key
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "1",
+                    "value": "10",
+                    "priority": "150"
+                  }
+                ]
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "2000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "0",
+                    "value": "30",
+                    "priority": "100"
+                  }
+                ],
+                "debug_key": "3"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "2400000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "2",
+                    "value": "18",
+                    "priority": "150"
+                  }
+                ],
+                "debug_key": "4"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "3000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "2",
+                    "value": "1",
+                    "priority": "150"
+                  }
+                ],
+                "debug_key": "5"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "4000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "3",
+                    "value": "19",
+                    "priority": "130"
+                  }
+                ]
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "5000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "2",
+                    "value": "40",
+                    "priority": "400"
+                  }
+                ],
+                "debug_key": "7"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "5300000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "0",
+                    "value": "5",
+                    "priority": "90"
+                  }
+                ]
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "6000000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "2",
+                    "value": "20",
+                    "priority": "50"
+                  }
+                ],
+                "debug_key": "9"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "6500000"
+      },
+      {
+        "registration_request": {
+          "attribution_src_url": "https://www.ad-tech1.test",
+          "registrant": "example.2d1.test"
+        },
+        "responses": [
+          {
+            "url": "https://www.ad-tech1.test",
+            "has_ad_id_permission": true,
+            "response": {
+              "Attribution-Reporting-Register-Trigger": {
+                "event_trigger_data": [
+                  {
+                    "trigger_data": "0",
+                    "value": "20",
+                    "priority": "50"
+                  }
+                ],
+                "debug_key": "10"
+              },
+              "Location": null,
+              "Attribution-Reporting-Redirect": null
+            }
+          }
+        ],
+        "timestamp": "7000000"
+      }
+    ]
+  },
+  "output": {
+    "event_level_results": [
+      {
+        "report_time": "3600000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "3600",
+          "source_event_id": "123",
+          "trigger_data": "1",
+          "source_type": "navigation",
+          "trigger_summary_bucket": [10, 49],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "3600000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "3600",
+          "source_event_id": "123",
+          "trigger_data": "0",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["1", "3"],
+          "trigger_summary_bucket": [10, 49],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "5400000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "5400",
+          "source_event_id": "123",
+          "trigger_data": "2",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["7"],
+          "trigger_summary_bucket": [20, 69],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "9000000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "9000",
+          "source_event_id": "123",
+          "trigger_data": "2",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["7", "4", "5", "9"],
+          "trigger_summary_bucket": [70, 2147483646],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      }
+    ],
+    "debug_event_level_results": [
+      {
+        "report_time": "2400000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/debug/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "3600",
+          "source_event_id": "123",
+          "trigger_data": "0",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["1", "3"],
+          "trigger_summary_bucket": [10, 49],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "3000000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/debug/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "3600",
+          "source_event_id": "123",
+          "trigger_data": "0",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["1", "3"],
+          "trigger_summary_bucket": [10, 49],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "5300000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/debug/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "5400",
+          "source_event_id": "123",
+          "trigger_data": "2",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["7"],
+          "trigger_summary_bucket": [20, 69],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "6500000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/debug/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "9000",
+          "source_event_id": "123",
+          "trigger_data": "2",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["7", "4", "5", "9"],
+          "trigger_summary_bucket": [70, 2147483646],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      },
+      {
+        "report_time": "7000000",
+        "report_url": "https://www.ad-tech1.test/.well-known/attribution-reporting/debug/report-event-attribution",
+        "payload": {
+          "attribution_destination": "android-app://example.2d1.test",
+          "scheduled_report_time": "9000",
+          "source_event_id": "123",
+          "trigger_data": "2",
+          "source_type": "navigation",
+          "source_debug_key": "111",
+          "trigger_debug_keys": ["7", "4", "5", "9"],
+          "trigger_summary_bucket": [70, 2147483646],
+          "randomized_trigger_rate": 0.00030175409301020603
+        }
+      }
+    ],
+    "aggregatable_results": []
+  }
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/MeasurementDaoTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/MeasurementDaoTest.java
index 51b4abc..91071e7 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/MeasurementDaoTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/data/measurement/MeasurementDaoTest.java
@@ -9963,7 +9963,9 @@
                         eventReport.getTriggerData(),
                         eventReport.getTriggerValue(),
                         eventReport.getTriggerTime(),
-                        eventReport.getTriggerDedupKey()));
+                        eventReport.getTriggerDedupKey(),
+                        eventReport.getTriggerDebugKey(),
+                        false));
     }
 
     private static void insertAttributedTrigger(List<AttributedTrigger> attributedTriggers,
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributedTriggerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributedTriggerTest.java
index 9b7dc1e..ed9a8b6 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributedTriggerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/AttributedTriggerTest.java
@@ -41,6 +41,10 @@
     private static final long TRIGGER_TIME2 = 1689564817010L;
     private static final UnsignedLong DEDUP_KEY1 = new UnsignedLong("453");
     private static final UnsignedLong DEDUP_KEY2 = new UnsignedLong("357");
+    private static final UnsignedLong DEBUG_KEY1 = new UnsignedLong("980");
+    private static final UnsignedLong DEBUG_KEY2 = new UnsignedLong("812");
+    private static final boolean HAS_SOURCE_DEBUG_KEY1 = true;
+    private static final boolean HAS_SOURCE_DEBUG_KEY2 = false;
     private static final long CONTRIBUTION = 50L;
 
     @Test
@@ -52,14 +56,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1));
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1));
     }
 
     @Test
@@ -71,14 +79,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1),
                 new AttributedTrigger(
                         TRIGGER_ID2,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1));
+                        DEDUP_KEY1,
+                        DEDUP_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1));
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -86,14 +98,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY2,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1));
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1));
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -101,14 +117,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA2,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1));
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1));
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -116,14 +136,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE2,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1));
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1));
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -131,14 +155,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME2,
-                        DEDUP_KEY1));
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1));
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -146,14 +174,56 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY2));
+                        DEDUP_KEY2,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1));
+        assertNotEquals(
+                new AttributedTrigger(
+                        TRIGGER_ID1,
+                        PRIORITY1,
+                        TRIGGER_DATA1,
+                        VALUE1,
+                        TRIGGER_TIME1,
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1),
+                new AttributedTrigger(
+                        TRIGGER_ID1,
+                        PRIORITY1,
+                        TRIGGER_DATA1,
+                        VALUE1,
+                        TRIGGER_TIME1,
+                        DEDUP_KEY1,
+                        DEBUG_KEY2,
+                        HAS_SOURCE_DEBUG_KEY1));
+        assertNotEquals(
+                new AttributedTrigger(
+                        TRIGGER_ID1,
+                        PRIORITY1,
+                        TRIGGER_DATA1,
+                        VALUE1,
+                        TRIGGER_TIME1,
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1),
+                new AttributedTrigger(
+                        TRIGGER_ID1,
+                        PRIORITY1,
+                        TRIGGER_DATA1,
+                        VALUE1,
+                        TRIGGER_TIME1,
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY2));
     }
 
     @Test
@@ -165,14 +235,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode(),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode(),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode());
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode());
     }
 
     @Test
@@ -184,14 +258,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode(),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode(),
                 new AttributedTrigger(
                         TRIGGER_ID2,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode());
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode());
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -199,14 +277,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode(),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode(),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY2,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode());
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode());
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -214,14 +296,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode(),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode(),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA2,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode());
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode());
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -229,14 +315,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode(),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode(),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE2,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode());
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode());
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -244,14 +334,18 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode(),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode(),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME2,
-                        DEDUP_KEY1).hashCode());
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode());
         assertNotEquals(
                 new AttributedTrigger(
                         TRIGGER_ID1,
@@ -259,14 +353,56 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1).hashCode(),
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode(),
                 new AttributedTrigger(
                         TRIGGER_ID1,
                         PRIORITY1,
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY2).hashCode());
+                        DEDUP_KEY2,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode());
+        assertNotEquals(
+                new AttributedTrigger(
+                        TRIGGER_ID1,
+                        PRIORITY1,
+                        TRIGGER_DATA1,
+                        VALUE1,
+                        TRIGGER_TIME1,
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode(),
+                new AttributedTrigger(
+                        TRIGGER_ID1,
+                        PRIORITY1,
+                        TRIGGER_DATA1,
+                        VALUE1,
+                        TRIGGER_TIME1,
+                        DEDUP_KEY1,
+                        DEBUG_KEY2,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode());
+        assertNotEquals(
+                new AttributedTrigger(
+                        TRIGGER_ID1,
+                        PRIORITY1,
+                        TRIGGER_DATA1,
+                        VALUE1,
+                        TRIGGER_TIME1,
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1).hashCode(),
+                new AttributedTrigger(
+                        TRIGGER_ID1,
+                        PRIORITY1,
+                        TRIGGER_DATA1,
+                        VALUE1,
+                        TRIGGER_TIME1,
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY2).hashCode());
     }
 
     @Test
@@ -278,7 +414,9 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1);
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1);
 
         attributedTrigger.addContribution(CONTRIBUTION);
         assertEquals(CONTRIBUTION, attributedTrigger.getContribution());
@@ -288,6 +426,8 @@
         assertEquals(VALUE1, attributedTrigger.getValue());
         assertEquals(TRIGGER_TIME1, attributedTrigger.getTriggerTime());
         assertEquals(DEDUP_KEY1, attributedTrigger.getDedupKey());
+        assertEquals(DEBUG_KEY1, attributedTrigger.getDebugKey());
+        assertEquals(HAS_SOURCE_DEBUG_KEY1, attributedTrigger.hasSourceDebugKey());
     }
 
     @Test
@@ -299,7 +439,9 @@
                         TRIGGER_DATA1,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1);
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1);
 
         attributedTrigger1.addContribution(CONTRIBUTION);
         assertEquals(-45L, attributedTrigger1.remainingValue());
@@ -311,7 +453,9 @@
                         TRIGGER_DATA2,
                         VALUE2,
                         TRIGGER_TIME2,
-                        DEDUP_KEY2);
+                        DEDUP_KEY2,
+                        DEBUG_KEY2,
+                        HAS_SOURCE_DEBUG_KEY2);
 
         attributedTrigger2.addContribution(CONTRIBUTION);
         assertEquals(16L, attributedTrigger2.remainingValue());
@@ -349,6 +493,8 @@
         triggerObj.put("value", VALUE1);
         triggerObj.put("trigger_time", TRIGGER_TIME1);
         triggerObj.put("dedup_key", DEDUP_KEY1.toString());
+        triggerObj.put("debug_key", DEBUG_KEY1.toString());
+        triggerObj.put("has_source_debug_key", HAS_SOURCE_DEBUG_KEY1);
 
         AttributedTrigger triggerFromJson = new AttributedTrigger(triggerObj);
         JSONObject encodedObj = triggerFromJson.encodeToJsonFlexApi();
@@ -374,7 +520,9 @@
                         /* trigger data */ null,
                         VALUE1,
                         TRIGGER_TIME1,
-                        DEDUP_KEY1);
+                        DEDUP_KEY1,
+                        DEBUG_KEY1,
+                        HAS_SOURCE_DEBUG_KEY1);
 
         assertThrows(
                 NullPointerException.class,
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java
index 5e0ca01..04b0da9 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/E2ETest.java
@@ -127,6 +127,7 @@
                         "trigger_summary_bucket");
         String DOUBLE = "randomized_trigger_rate";
         String STRING_OR_ARRAY = "attribution_destination";
+        String ARRAY = "trigger_debug_keys";
     }
 
     interface AggregateReportPayloadKeys {
@@ -503,7 +504,7 @@
 
     private static int hashForEventReportObject(OutputType outputType, JSONObject obj) {
         int n = EventReportPayloadKeys.STRINGS.size();
-        int numValuesExcludingN = 3;
+        int numValuesExcludingN = 4;
         Object[] objArray = new Object[n + numValuesExcludingN];
         // TODO (b/306863121) add time to hash
         String url = obj.optString(TestFormatJsonMapping.REPORT_TO_KEY, "");
@@ -518,10 +519,12 @@
         if (maybeString != null) {
             objArray[2] = maybeString;
         }
-        JSONArray maybeArray = payload.optJSONArray(EventReportPayloadKeys.STRING_OR_ARRAY);
-        if (maybeArray != null) {
-            objArray[2] = maybeArray;
+        JSONArray maybeArray1 = payload.optJSONArray(EventReportPayloadKeys.STRING_OR_ARRAY);
+        if (maybeArray1 != null) {
+            objArray[2] = maybeArray1;
         }
+        JSONArray maybeArray2 = payload.optJSONArray(EventReportPayloadKeys.ARRAY);
+        objArray[3] = maybeArray2;
         for (int i = 0; i < n; i++) {
             objArray[i + numValuesExcludingN] =
                     payload.optString(EventReportPayloadKeys.STRINGS.get(i), "");
@@ -592,6 +595,24 @@
         return true;
     }
 
+    private static boolean areNullOrEqualJSONArray(JSONArray expected, JSONArray actual)
+            throws JSONException {
+        if (expected == null) {
+            return actual == null;
+        } else if (actual == null) {
+            return false;
+        }
+        if (expected.length() != actual.length()) {
+            return false;
+        }
+        for (int i = 0; i < expected.length(); i++) {
+            if (!expected.getString(i).equals(actual.getString(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private boolean areEqualEventReportJsons(
             ReportType reportType, JSONObject expected, JSONObject actual) throws JSONException {
         JSONObject expectedPayload = expected.getJSONObject(TestFormatJsonMapping.PAYLOAD_KEY);
@@ -604,6 +625,13 @@
         if (!areEqualStringOrJSONArray(
                 expectedPayload.get(EventReportPayloadKeys.STRING_OR_ARRAY),
                 actualPayload.get(EventReportPayloadKeys.STRING_OR_ARRAY))) {
+            log("Event payload string-or-array mismatch. Report type: " + reportType.name());
+            return false;
+        }
+        if (!areNullOrEqualJSONArray(
+                expectedPayload.optJSONArray(EventReportPayloadKeys.ARRAY),
+                actualPayload.optJSONArray(EventReportPayloadKeys.ARRAY))) {
+            log("Event payload array mismatch. Report type: " + reportType.name());
             return false;
         }
         for (String key : EventReportPayloadKeys.STRINGS) {
@@ -895,6 +923,11 @@
             result.append("JSONObject::get failed for EventReportPayloadKeys.STRING_OR_ARRAY "
                     + e + "\n");
         }
+        result.append(EventReportPayloadKeys.ARRAY + ": ")
+                .append(payload1.optJSONArray(EventReportPayloadKeys.ARRAY))
+                .append(" ::: ")
+                .append(payload2.optJSONArray(EventReportPayloadKeys.ARRAY))
+                .append("\n");
         for (String key : EventReportPayloadKeys.STRINGS) {
             result.append(key)
                     .append(": ")
@@ -925,6 +958,10 @@
             result.append("JSONObject::get failed for EventReportPayloadKeys.STRING_OR_ARRAY "
                     + e + "\n");
         }
+        result.append(EventReportPayloadKeys.ARRAY + ": ")
+                .append(pad)
+                .append(payload.optJSONArray(EventReportPayloadKeys.ARRAY))
+                .append("\n");
         for (String key : EventReportPayloadKeys.STRINGS) {
             result.append(key).append(": ").append(pad).append(payload.optString(key)).append("\n");
         }
@@ -999,12 +1036,25 @@
                     expiryTimes.add(
                             MEASUREMENT_MAX_REPORTING_REGISTER_SOURCE_EXPIRATION_IN_SECONDS);
                 }
+                if (sourceJson.has("event_report_windows")) {
+                    expiryTimes.addAll(
+                            getFlexEndTimes(sourceJson.getJSONObject("event_report_windows")));
+                }
             }
         }
 
         return expiryTimes;
     }
 
+    private static Set<Long> getFlexEndTimes(JSONObject eventReportWindows) throws JSONException {
+        Set<Long> endTimes = new HashSet<>();
+        JSONArray endTimesArray = eventReportWindows.getJSONArray("end_times");
+        for (int i = 0; i < endTimesArray.length(); i++) {
+            endTimes.add(endTimesArray.getLong(i));
+        }
+        return endTimes;
+    }
+
     private static long roundSecondsToWholeDays(long seconds) {
         long remainder = seconds % TimeUnit.DAYS.toSeconds(1);
         boolean roundUp = remainder >= TimeUnit.DAYS.toSeconds(1) / 2L;
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java
index a29478b..baa43e8 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/SourceTest.java
@@ -82,7 +82,9 @@
                             /* UnsignedLong triggerData */ new UnsignedLong("89"),
                             /* long value */ 15L,
                             /* long triggerTime */ 1934567890L,
-                            /* UnsignedLong dedupKey */ null));
+                            /* UnsignedLong dedupKey */ null,
+                            /* UnsignedLong debugKey */ null,
+                            /* boolean hasSourceDebugKey */ false));
     @Mock private Flags mFlags;
 
     @Before
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java
index 6ad9943..bd73dca 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/attribution/AttributionJobHandlerTest.java
@@ -4824,36 +4824,45 @@
     /**
      * Status before attribution: 1 trigger attributed and 1 report generated; Incoming trigger
      * status: 2 new reports should be generated; Result: 2 reports written into DB and no
-     * competition condition.
+     * competition condition; debug reports and trigger debug keys populated only when all trigger
+     * contributors have debug keys.
      */
-    public void performAttribution_flexEventReport_insertSecondTriggerNoCompeting()
+    public void performAttribution_flexEventReportTwoNonCompetingTriggersAllDebugReports()
             throws DatastoreException, JSONException {
         // Setup
         long baseTime = System.currentTimeMillis();
+        UnsignedLong triggerData1 = new UnsignedLong(1L);
+        UnsignedLong triggerData2 = new UnsignedLong(2L);
+        UnsignedLong sourceDebugKey = new UnsignedLong(777L);
+        UnsignedLong debugKey1 = new UnsignedLong(4L);
+        UnsignedLong debugKey2 = new UnsignedLong(55L);
         Trigger trigger =
                 TriggerFixture.getValidTriggerBuilder()
                         .setId("triggerId1")
+                        .setDestinationType(EventSurfaceType.APP)
                         .setStatus(Trigger.Status.PENDING)
                         .setEventTriggers(
                                 "[\n"
                                         + "{\n"
-                                        + "  \"trigger_data\": \"2\",\n"
-                                        + "  \"priority\": \"123\",\n"
-                                        + "  \"value\": \"105\",\n"
-                                        + "  \"deduplication_key\": \"123\"\n"
+                                        + "  \"trigger_data\": \"" + triggerData2 + "\","
+                                        + "  \"priority\": \"123\","
+                                        + "  \"value\": \"105\","
+                                        + "  \"deduplication_key\": \"123\""
                                         + "}"
-                                        + "]\n")
+                                        + "]")
                         .setFilters(
-                                "[{\n"
-                                        + "  \"key_1\": [\"value_1\", \"value_2\"],\n"
-                                        + "  \"key_2\": [\"value_1\", \"value_2\"]\n"
-                                        + "}]\n")
+                                "[{"
+                                        + "  \"key_1\": [\"value_1\", \"value_2\"],"
+                                        + "  \"key_2\": [\"value_1\", \"value_2\"]"
+                                        + "}]")
                         .setTriggerTime(
                                 baseTime
                                         + TimeUnit.DAYS.toMillis(1)
                                         + MEASUREMENT_MIN_EVENT_REPORT_DELAY_MILLIS)
                         .setAggregateTriggerData(buildAggregateTriggerData().toString())
                         .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+                        .setAdIdPermission(true)
+                        .setDebugKey(debugKey2)
                         .build();
 
         Pair<Long, Long> firstBucket = Pair.create(10L, 99L);
@@ -4864,7 +4873,7 @@
                         .setId("100")
                         .setSourceEventId(new UnsignedLong(22L))
                         .setEnrollmentId("another-enrollment-id")
-                        .setAttributionDestinations(List.of(Uri.parse("https://bar.test")))
+                        .setAttributionDestinations(List.of(Uri.parse("android-app://com.ignored")))
                         .setReportTime(
                                 baseTime
                                         + TimeUnit.DAYS.toMillis(3)
@@ -4873,11 +4882,12 @@
                         .setSourceType(Source.SourceType.NAVIGATION)
                         .setRegistrationOrigin(WebUtil.validUri("https://adtech2.test"))
                         .setTriggerTime(baseTime + 3000L)
-                        .setTriggerData(new UnsignedLong(1L))
+                        .setTriggerData(triggerData1)
                         .setTriggerPriority(123L)
                         .setTriggerValue(30)
                         .setTriggerSummaryBucket(firstBucket)
                         .setTriggerDedupKey(new UnsignedLong(3L))
+                        .setTriggerDebugKey(debugKey1)
                         .build();
 
         TriggerSpecs templateTriggerSpecs = SourceFixture.getValidTriggerSpecsValueSum();
@@ -4910,6 +4920,8 @@
                         .setEventAttributionStatus(existingAttributes.toString())
                         .setPrivacyParameters(
                                 templateTriggerSpecs.encodePrivacyParametersToJSONString())
+                        .setAdIdPermission(true)
+                        .setDebugKey(sourceDebugKey)
                         .build();
 
         when(mFlags.getMeasurementFlexibleEventReportingApiEnabled()).thenReturn(true);
@@ -4956,9 +4968,24 @@
         assertEquals(firstBucket, insertedEventReports.get(0).getTriggerSummaryBucket());
         assertEquals(firstBucket, insertedEventReports.get(1).getTriggerSummaryBucket());
         assertEquals(secondBucket, insertedEventReports.get(2).getTriggerSummaryBucket());
-        assertEquals(new UnsignedLong(1L), insertedEventReports.get(0).getTriggerData());
-        assertEquals(new UnsignedLong(2L), insertedEventReports.get(1).getTriggerData());
-        assertEquals(new UnsignedLong(2L), insertedEventReports.get(2).getTriggerData());
+        assertEquals(triggerData1, insertedEventReports.get(0).getTriggerData());
+        assertEquals(triggerData2, insertedEventReports.get(1).getTriggerData());
+        assertEquals(triggerData2, insertedEventReports.get(2).getTriggerData());
+        assertEquals(List.of(debugKey1), insertedEventReports.get(0).getTriggerDebugKeys());
+        assertEquals(List.of(debugKey2), insertedEventReports.get(1).getTriggerDebugKeys());
+        assertEquals(List.of(debugKey2), insertedEventReports.get(2).getTriggerDebugKeys());
+        assertNull(insertedEventReports.get(0).getSourceDebugKey());
+        assertEquals(sourceDebugKey, insertedEventReports.get(1).getSourceDebugKey());
+        assertEquals(sourceDebugKey, insertedEventReports.get(2).getSourceDebugKey());
+        assertEquals(
+                EventReport.DebugReportStatus.NONE,
+                insertedEventReports.get(0).getDebugReportStatus());
+        assertEquals(
+                EventReport.DebugReportStatus.PENDING,
+                insertedEventReports.get(1).getDebugReportStatus());
+        assertEquals(
+                EventReport.DebugReportStatus.PENDING,
+                insertedEventReports.get(2).getDebugReportStatus());
         long reportTime = TimeUnit.DAYS.toMillis(2) + MEASUREMENT_MIN_EVENT_REPORT_DELAY_MILLIS;
         assertEquals(reportTime, insertedEventReports.get(0).getReportTime() - baseTime);
         assertEquals(reportTime, insertedEventReports.get(1).getReportTime() - baseTime);
@@ -4972,36 +4999,44 @@
     }
 
     @Test
-    /**
+    /*
      * Status before attribution: 1 trigger attributed and 2 report generated with triggerData 1;
      * Incoming trigger status: 2 reports should be generated for triggerData 2 Result: incoming
-     * trigger has higher priority and one previous report should be deleted
+     * trigger has higher priority and one previous report should be deleted; debug reports and
+     * trigger debug keys populated only when all trigger contributors have debug keys.
      */
-    public void performAttribution_flexEventReport_insertSecondTriggerCompetingHigherPriority()
+    public void performAttribution_flexEventReportSecondTriggerHigherPriorityAllDebugReports()
             throws DatastoreException, JSONException {
         // Setup
         long baseTime = System.currentTimeMillis();
+        UnsignedLong triggerData1 = new UnsignedLong(1L);
+        UnsignedLong triggerData2 = new UnsignedLong(2L);
+        UnsignedLong sourceDebugKey = new UnsignedLong(777L);
+        UnsignedLong debugKey1 = new UnsignedLong(4L);
+        UnsignedLong debugKey2 = new UnsignedLong(55L);
         Trigger trigger =
                 TriggerFixture.getValidTriggerBuilder()
                         .setId("triggerId1")
                         .setStatus(Trigger.Status.PENDING)
                         .setEventTriggers(
-                                "[\n"
-                                        + "{\n"
-                                        + "  \"trigger_data\": \"2\",\n"
-                                        + "  \"priority\": \"123\",\n"
-                                        + "  \"value\": \"105\",\n"
-                                        + "  \"deduplication_key\": \"1234\"\n"
+                                "["
+                                        + "{"
+                                        + "  \"trigger_data\": \"" + triggerData2 + "\","
+                                        + "  \"priority\": \"123\","
+                                        + "  \"value\": \"105\","
+                                        + "  \"deduplication_key\": \"1234\""
                                         + "}"
-                                        + "]\n")
+                                        + "]")
                         .setFilters(
-                                "[{\n"
-                                        + "  \"key_1\": [\"value_1\", \"value_2\"],\n"
-                                        + "  \"key_2\": [\"value_1\", \"value_2\"]\n"
-                                        + "}]\n")
+                                "[{"
+                                        + "  \"key_1\": [\"value_1\", \"value_2\"],"
+                                        + "  \"key_2\": [\"value_1\", \"value_2\"]"
+                                        + "}]")
                         .setTriggerTime(baseTime + TimeUnit.DAYS.toMillis(1) + 4800000)
                         .setAggregateTriggerData(buildAggregateTriggerData().toString())
                         .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+                        .setAdIdPermission(true)
+                        .setDebugKey(debugKey2)
                         .build();
 
         final EventReport.Builder eventReportBuilder =
@@ -5018,10 +5053,11 @@
                         .setSourceType(Source.SourceType.NAVIGATION)
                         .setRegistrationOrigin(WebUtil.validUri("https://adtech2.test"))
                         .setTriggerTime(baseTime + TimeUnit.DAYS.toMillis(1))
-                        .setTriggerData(new UnsignedLong(1L))
+                        .setTriggerData(triggerData1)
                         .setTriggerPriority(121L)
                         .setTriggerValue(101)
-                        .setTriggerDedupKey(new UnsignedLong(3L));
+                        .setTriggerDedupKey(new UnsignedLong(3L))
+                        .setTriggerDebugKey(debugKey1);
 
         TriggerSpecs templateTriggerSpecs = SourceFixture.getValidTriggerSpecsValueSum();
         JSONArray existingAttributes = new JSONArray();
@@ -5037,10 +5073,10 @@
                         .setAggregateSource(
                                 "{\"campaignCounts\" : \"0x159\", \"geoValue\" : \"0x5\"}")
                         .setFilterData(
-                                "{\n"
-                                        + "  \"key_1\": [\"value_1\", \"value_2\"],\n"
-                                        + "  \"key_2\": [\"value_1\", \"value_2\"]\n"
-                                        + "}\n")
+                                "{"
+                                        + "  \"key_1\": [\"value_1\", \"value_2\"],"
+                                        + "  \"key_2\": [\"value_1\", \"value_2\"]"
+                                        + "}")
                         .setEventTime(baseTime)
                         .setEventReportWindow(
                                 baseTime
@@ -5055,6 +5091,8 @@
                         .setEventAttributionStatus(existingAttributes.toString())
                         .setPrivacyParameters(
                                 templateTriggerSpecs.encodePrivacyParametersToJSONString())
+                        .setAdIdPermission(true)
+                        .setDebugKey(sourceDebugKey)
                         .build();
 
         Pair<Long, Long> firstBucket = Pair.create(10L, 99L);
@@ -5113,9 +5151,24 @@
         assertEquals(firstBucket, insertedEventReports.get(0).getTriggerSummaryBucket());
         assertEquals(secondBucket, insertedEventReports.get(1).getTriggerSummaryBucket());
         assertEquals(firstBucket, insertedEventReports.get(2).getTriggerSummaryBucket());
-        assertEquals(new UnsignedLong(2L), insertedEventReports.get(0).getTriggerData());
-        assertEquals(new UnsignedLong(2L), insertedEventReports.get(1).getTriggerData());
-        assertEquals(new UnsignedLong(1L), insertedEventReports.get(2).getTriggerData());
+        assertEquals(triggerData2, insertedEventReports.get(0).getTriggerData());
+        assertEquals(triggerData2, insertedEventReports.get(1).getTriggerData());
+        assertEquals(triggerData1, insertedEventReports.get(2).getTriggerData());
+        assertEquals(List.of(debugKey2), insertedEventReports.get(0).getTriggerDebugKeys());
+        assertEquals(List.of(debugKey2), insertedEventReports.get(1).getTriggerDebugKeys());
+        assertEquals(List.of(debugKey1), insertedEventReports.get(2).getTriggerDebugKeys());
+        assertEquals(sourceDebugKey, insertedEventReports.get(0).getSourceDebugKey());
+        assertEquals(sourceDebugKey, insertedEventReports.get(1).getSourceDebugKey());
+        assertNull(insertedEventReports.get(2).getSourceDebugKey());
+        assertEquals(
+                EventReport.DebugReportStatus.PENDING,
+                insertedEventReports.get(0).getDebugReportStatus());
+        assertEquals(
+                EventReport.DebugReportStatus.PENDING,
+                insertedEventReports.get(1).getDebugReportStatus());
+        assertEquals(
+                EventReport.DebugReportStatus.NONE,
+                insertedEventReports.get(2).getDebugReportStatus());
         assertEquals(2, deletedReportsCaptor.getAllValues().size());
         long reportTime = TimeUnit.DAYS.toMillis(2) + MEASUREMENT_MIN_EVENT_REPORT_DELAY_MILLIS;
         assertEquals(reportTime, insertedEventReports.get(0).getReportTime() - baseTime);
@@ -5128,41 +5181,48 @@
     }
 
     @Test
-    /**
+    /*
      * Status before attribution: 1 trigger attributed and 2 report generated with triggerData 1;
      * Incoming trigger status: 2 reports should be generated for triggerData 2 Result: incoming
      * trigger has lower priority, no previous report should be deleted and only 1 new report
-     * inserted into DB
+     * inserted into DB; debug reports and trigger debug keys populated only when all trigger
+     * contributors have debug keys.
      */
-    public void performAttribution_flexEventReport_insertSecondTriggerCompetingLowerPriority()
+    public void performAttribution_flexEventReportSecondTriggerLowerPrioritySomeDebugReports()
             throws DatastoreException, JSONException {
 
         // Setup
         long baseTime = System.currentTimeMillis();
+        UnsignedLong triggerData1 = new UnsignedLong(1L);
+        UnsignedLong triggerData2 = new UnsignedLong(2L);
+        UnsignedLong sourceDebugKey = new UnsignedLong(777L);
+        UnsignedLong debugKey2 = new UnsignedLong(55L);
         Trigger trigger =
                 TriggerFixture.getValidTriggerBuilder()
                         .setId("triggerId1")
                         .setStatus(Trigger.Status.PENDING)
                         .setEventTriggers(
-                                "[\n"
-                                        + "{\n"
-                                        + "  \"trigger_data\": \"2\",\n"
-                                        + "  \"priority\": \"123\",\n"
-                                        + "  \"value\": \"105\",\n"
-                                        + "  \"deduplication_key\": \"1\"\n"
+                                "["
+                                        + "{"
+                                        + "  \"trigger_data\": \"" + triggerData2 + "\","
+                                        + "  \"priority\": \"123\","
+                                        + "  \"value\": \"105\","
+                                        + "  \"deduplication_key\": \"1\""
                                         + "}"
-                                        + "]\n")
+                                        + "]")
                         .setFilters(
-                                "[{\n"
-                                        + "  \"key_1\": [\"value_1\", \"value_2\"],\n"
-                                        + "  \"key_2\": [\"value_1\", \"value_2\"]\n"
-                                        + "}]\n")
+                                "[{"
+                                        + "  \"key_1\": [\"value_1\", \"value_2\"],"
+                                        + "  \"key_2\": [\"value_1\", \"value_2\"]"
+                                        + "}]")
                         .setTriggerTime(
                                 baseTime
                                         + TimeUnit.DAYS.toMillis(1)
                                         + MEASUREMENT_MIN_EVENT_REPORT_DELAY_MILLIS)
                         .setAggregateTriggerData(buildAggregateTriggerData().toString())
                         .setAggregateValues("{\"campaignCounts\":32768,\"geoValue\":1644}")
+                        .setAdIdPermission(true)
+                        .setDebugKey(debugKey2)
                         .build();
 
         Pair<Long, Long> firstBucket = Pair.create(10L, 99L);
@@ -5183,8 +5243,9 @@
                         .setSourceType(Source.SourceType.NAVIGATION)
                         .setRegistrationOrigin(WebUtil.validUri("https://adtech2.test"))
                         .setTriggerTime(baseTime + 3000)
-                        .setTriggerData(new UnsignedLong(1L))
+                        .setTriggerData(triggerData1)
                         .setTriggerPriority(124L)
+                        .setSourceDebugKey(sourceDebugKey)
                         .setTriggerValue(103)
                         .setTriggerDedupKey(new UnsignedLong(3L));
 
@@ -5220,6 +5281,8 @@
                         .setEventAttributionStatus(existingAttributes.toString())
                         .setPrivacyParameters(
                                 templateTriggerSpecs.encodePrivacyParametersToJSONString())
+                        .setAdIdPermission(true)
+                        .setDebugKey(sourceDebugKey)
                         .build();
 
         when(mFlags.getMeasurementFlexibleEventReportingApiEnabled()).thenReturn(true);
@@ -5273,9 +5336,24 @@
         assertEquals(firstBucket, insertedEventReports.get(0).getTriggerSummaryBucket());
         assertEquals(secondBucket, insertedEventReports.get(1).getTriggerSummaryBucket());
         assertEquals(firstBucket, insertedEventReports.get(2).getTriggerSummaryBucket());
-        assertEquals(new UnsignedLong(1L), insertedEventReports.get(0).getTriggerData());
-        assertEquals(new UnsignedLong(1L), insertedEventReports.get(1).getTriggerData());
-        assertEquals(new UnsignedLong(2L), insertedEventReports.get(2).getTriggerData());
+        assertEquals(triggerData1, insertedEventReports.get(0).getTriggerData());
+        assertEquals(triggerData1, insertedEventReports.get(1).getTriggerData());
+        assertEquals(triggerData2, insertedEventReports.get(2).getTriggerData());
+        assertEquals(Collections.emptyList(), insertedEventReports.get(0).getTriggerDebugKeys());
+        assertEquals(Collections.emptyList(), insertedEventReports.get(1).getTriggerDebugKeys());
+        assertEquals(List.of(debugKey2), insertedEventReports.get(2).getTriggerDebugKeys());
+        assertEquals(sourceDebugKey, insertedEventReports.get(0).getSourceDebugKey());
+        assertEquals(sourceDebugKey, insertedEventReports.get(1).getSourceDebugKey());
+        assertEquals(sourceDebugKey, insertedEventReports.get(2).getSourceDebugKey());
+        assertEquals(
+                EventReport.DebugReportStatus.NONE,
+                insertedEventReports.get(0).getDebugReportStatus());
+        assertEquals(
+                EventReport.DebugReportStatus.NONE,
+                insertedEventReports.get(1).getDebugReportStatus());
+        assertEquals(
+                EventReport.DebugReportStatus.PENDING,
+                insertedEventReports.get(2).getDebugReportStatus());
         long reportTime = TimeUnit.DAYS.toMillis(2) + MEASUREMENT_MIN_EVENT_REPORT_DELAY_MILLIS;
         assertEquals(reportTime, insertedEventReports.get(0).getReportTime() - baseTime);
         assertEquals(reportTime, insertedEventReports.get(1).getReportTime() - baseTime);
@@ -6317,6 +6395,10 @@
         triggerRecord.put("trigger_time", eventReport.getTriggerTime());
         triggerRecord.put("trigger_data", eventReport.getTriggerData());
         triggerRecord.put("dedup_key", eventReport.getTriggerDedupKey());
+        if (eventReport.getTriggerDebugKey() != null) {
+            triggerRecord.put("debug_key", eventReport.getTriggerDebugKey());
+        }
+        triggerRecord.put("has_source_debug_key", eventReport.getSourceDebugKey() != null);
         return triggerRecord;
     }
 
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java
index c27b955..cf97585 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/EventReportPayloadTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.net.Uri;
+import android.util.Pair;
 
 import com.android.adservices.service.measurement.util.UnsignedLong;
 
@@ -46,6 +47,7 @@
     private static final double RANDOMIZED_TRIGGER_RATE = 0.0024;
     private static final UnsignedLong SOURCE_DEBUG_KEY = new UnsignedLong(3894783L);
     private static final UnsignedLong TRIGGER_DEBUG_KEY = new UnsignedLong(2387222L);
+    private static final Pair<Long, Long> TRIGGER_SUMMARY_BUCKET = Pair.create(5L, 37L);
     private static final List<UnsignedLong> TRIGGER_DEBUG_KEYS = List.of(
             new UnsignedLong(2387222L), new UnsignedLong("9223372036854775809"));
 
@@ -85,7 +87,7 @@
                 attributionDestinations);
     }
 
-    private static EventReportPayload createEventReportPayload(
+    private static EventReportPayload.Builder createEventReportPayloadBuilder(
             UnsignedLong triggerData,
             UnsignedLong sourceDebugKey,
             UnsignedLong triggerDebugKey,
@@ -101,19 +103,36 @@
                 .setRandomizedTriggerRate(RANDOMIZED_TRIGGER_RATE)
                 .setSourceDebugKey(sourceDebugKey)
                 .setTriggerDebugKey(triggerDebugKey)
-                .setTriggerDebugKeys(triggerDebugKeys)
-                .build();
+                .setTriggerDebugKeys(triggerDebugKeys);
+    }
+
+    private static EventReportPayload createEventReportPayload(
+            UnsignedLong triggerData,
+            UnsignedLong sourceDebugKey,
+            UnsignedLong triggerDebugKey,
+            List<UnsignedLong> triggerDebugKeys,
+            List<Uri> destinations) {
+        return createEventReportPayloadBuilder(
+                triggerData,
+                sourceDebugKey,
+                triggerDebugKey,
+                triggerDebugKeys,
+                destinations)
+                        .build();
     }
 
     @Test
     public void toJson_success() throws JSONException {
         EventReportPayload eventReport =
-                createEventReportPayload(
+                createEventReportPayloadBuilder(
                         TRIGGER_DATA,
                         SOURCE_DEBUG_KEY,
                         TRIGGER_DEBUG_KEY,
                         TRIGGER_DEBUG_KEYS,
-                        ATTRIBUTION_DESTINATIONS);
+                        ATTRIBUTION_DESTINATIONS)
+                                .setTriggerSummaryBucket(TRIGGER_SUMMARY_BUCKET)
+                                .build();
+
         JSONObject eventPayloadReportJson = eventReport.toJson();
 
         Object destinationsObj = eventPayloadReportJson.get("attribution_destination");
@@ -134,6 +153,16 @@
                 ((JSONArray) triggerDebugKeysObj).getString(0));
         assertEquals(TRIGGER_DEBUG_KEYS.get(1).toString(),
                 ((JSONArray) triggerDebugKeysObj).getString(1));
+        // Trigger summary bucket
+        assertTrue(eventPayloadReportJson.get("trigger_summary_bucket") instanceof JSONArray);
+        JSONArray triggerSummaryBucket =
+                eventPayloadReportJson.getJSONArray("trigger_summary_bucket");
+        Object first = triggerSummaryBucket.get(0);
+        assertTrue(first instanceof Number);
+        assertEquals(TRIGGER_SUMMARY_BUCKET.first, Long.valueOf((long) first));
+        Object second = triggerSummaryBucket.get(1);
+        assertTrue(second instanceof Number);
+        assertEquals(TRIGGER_SUMMARY_BUCKET.second, Long.valueOf((long) second));
     }
 
     @Test
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/ReportUtilTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/ReportUtilTest.java
index 8e3356c..9021571 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/ReportUtilTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/measurement/reporting/ReportUtilTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
 
 import android.net.Uri;
 import android.util.Pair;
@@ -25,6 +26,7 @@
 import com.android.adservices.service.measurement.util.UnsignedLong;
 
 import org.json.JSONArray;
+import org.json.JSONException;
 import org.junit.Test;
 
 import java.util.List;
@@ -68,14 +70,26 @@
     }
 
     @Test
-    public void serializeSummaryBucket_baseCase_returnsExpectedFormat() {
+    public void serializeSummaryBucket_baseCase_returnsExpectedFormat() throws JSONException {
         Pair<Long, Long> summaryBucket = new Pair<>(1L, 5L);
-        assertEquals("[1,5]", ReportUtil.serializeSummaryBucket(summaryBucket));
+        JSONArray result = ReportUtil.serializeSummaryBucket(summaryBucket);
+        Object first = result.get(0);
+        assertTrue(first instanceof Number);
+        assertEquals(summaryBucket.first, Long.valueOf((long) first));
+        Object second = result.get(1);
+        assertTrue(second instanceof Number);
+        assertEquals(summaryBucket.second, Long.valueOf((long) second));
     }
 
     @Test
-    public void serializeSummaryBucket_largestBucket_returnsExpectedFormat() {
+    public void serializeSummaryBucket_largestBucket_returnsExpectedFormat() throws JSONException {
         Pair<Long, Long> summaryBucket = new Pair<>(100L, Long.MAX_VALUE);
-        assertEquals("[100,9223372036854775807]", ReportUtil.serializeSummaryBucket(summaryBucket));
+        JSONArray result = ReportUtil.serializeSummaryBucket(summaryBucket);
+        Object first = result.get(0);
+        assertTrue(first instanceof Number);
+        assertEquals(summaryBucket.first, Long.valueOf((long) first));
+        Object second = result.get(1);
+        assertTrue(second instanceof Number);
+        assertEquals(summaryBucket.second, Long.valueOf((long) second));
     }
 }