1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.adservices.service.measurement.reporting;
18 
19 import android.annotation.NonNull;
20 import android.content.Context;
21 import android.net.Uri;
22 import android.util.Pair;
23 
24 import androidx.annotation.Nullable;
25 
26 import com.android.adservices.LoggerFactory;
27 import com.android.adservices.data.measurement.DatastoreException;
28 import com.android.adservices.data.measurement.DatastoreManager;
29 import com.android.adservices.data.measurement.DatastoreManagerFactory;
30 import com.android.adservices.data.measurement.IMeasurementDao;
31 import com.android.adservices.service.Flags;
32 import com.android.adservices.service.common.WebAddresses;
33 import com.android.adservices.service.measurement.EventSurfaceType;
34 import com.android.adservices.service.measurement.Source;
35 import com.android.adservices.service.measurement.Trigger;
36 import com.android.adservices.service.measurement.noising.SourceNoiseHandler;
37 import com.android.adservices.service.measurement.util.UnsignedLong;
38 import com.android.internal.annotations.VisibleForTesting;
39 
40 import org.json.JSONException;
41 import org.json.JSONObject;
42 
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Objects;
47 import java.util.Optional;
48 import java.util.UUID;
49 import java.util.concurrent.TimeUnit;
50 
51 /** Class used to send debug reports to Ad-Tech {@link DebugReport} */
52 public class DebugReportApi {
53 
54     /** Define different verbose debug report types. */
55     public interface Type {
56         String SOURCE_DESTINATION_LIMIT = "source-destination-limit";
57         String SOURCE_DESTINATION_RATE_LIMIT = "source-destination-rate-limit";
58         String SOURCE_NOISED = "source-noised";
59         String SOURCE_STORAGE_LIMIT = "source-storage-limit";
60         String SOURCE_SUCCESS = "source-success";
61         String SOURCE_UNKNOWN_ERROR = "source-unknown-error";
62         String SOURCE_FLEXIBLE_EVENT_REPORT_VALUE_ERROR =
63                 "source-flexible-event-report-value-error";
64         String TRIGGER_AGGREGATE_DEDUPLICATED = "trigger-aggregate-deduplicated";
65         String TRIGGER_AGGREGATE_INSUFFICIENT_BUDGET = "trigger-aggregate-insufficient-budget";
66         String TRIGGER_AGGREGATE_NO_CONTRIBUTIONS = "trigger-aggregate-no-contributions";
67         String TRIGGER_AGGREGATE_REPORT_WINDOW_PASSED = "trigger-aggregate-report-window-passed";
68         String TRIGGER_ATTRIBUTIONS_PER_SOURCE_DESTINATION_LIMIT =
69                 "trigger-attributions-per-source-destination-limit";
70         String TRIGGER_EVENT_ATTRIBUTIONS_PER_SOURCE_DESTINATION_LIMIT =
71                 "trigger-event-attributions-per-source-destination-limit";
72         String TRIGGER_AGGREGATE_ATTRIBUTIONS_PER_SOURCE_DESTINATION_LIMIT =
73                 "trigger-aggregate-attributions-per-source-destination-limit";
74         String TRIGGER_EVENT_DEDUPLICATED = "trigger-event-deduplicated";
75         String TRIGGER_EVENT_EXCESSIVE_REPORTS = "trigger-event-excessive-reports";
76         String TRIGGER_EVENT_LOW_PRIORITY = "trigger-event-low-priority";
77         String TRIGGER_EVENT_NO_MATCHING_CONFIGURATIONS =
78                 "trigger-event-no-matching-configurations";
79         String TRIGGER_EVENT_NOISE = "trigger-event-noise";
80         String TRIGGER_EVENT_REPORT_WINDOW_PASSED = "trigger-event-report-window-passed";
81         String TRIGGER_NO_MATCHING_FILTER_DATA = "trigger-no-matching-filter-data";
82         String TRIGGER_NO_MATCHING_SOURCE = "trigger-no-matching-source";
83         String TRIGGER_REPORTING_ORIGIN_LIMIT = "trigger-reporting-origin-limit";
84         String TRIGGER_EVENT_STORAGE_LIMIT = "trigger-event-storage-limit";
85         String TRIGGER_UNKNOWN_ERROR = "trigger-unknown-error";
86         String TRIGGER_AGGREGATE_STORAGE_LIMIT = "trigger-aggregate-storage-limit";
87         String TRIGGER_AGGREGATE_EXCESSIVE_REPORTS = "trigger-aggregate-excessive-reports";
88         String TRIGGER_EVENT_REPORT_WINDOW_NOT_STARTED = "trigger-event-report-window-not-started";
89         String TRIGGER_EVENT_NO_MATCHING_TRIGGER_DATA = "trigger-event-no-matching-trigger-data";
90         String HEADER_PARSING_ERROR = "header-parsing-error";
91     }
92 
93     /** Defines different verbose debug report body parameters. */
94     @VisibleForTesting
95     public interface Body {
96         String ATTRIBUTION_DESTINATION = "attribution_destination";
97         String LIMIT = "limit";
98         String RANDOMIZED_TRIGGER_RATE = "randomized_trigger_rate";
99         String SCHEDULED_REPORT_TIME = "scheduled_report_time";
100         String SOURCE_DEBUG_KEY = "source_debug_key";
101         String SOURCE_EVENT_ID = "source_event_id";
102         String SOURCE_SITE = "source_site";
103         String SOURCE_TYPE = "source_type";
104         String TRIGGER_DATA = "trigger_data";
105         String TRIGGER_DEBUG_KEY = "trigger_debug_key";
106         String SOURCE_DESTINATION_LIMIT = "source_destination_limit";
107         String CONTEXT_SITE = "context_site";
108         String HEADER = "header";
109         String VALUE = "value";
110         String ERROR = "error";
111     }
112 
113     private enum PermissionState {
114         GRANTED,
115         DENIED,
116         NONE
117     }
118 
119     private final Context mContext;
120     private final Flags mFlags;
121     private final DatastoreManager mDatastoreManager;
122     private final EventReportWindowCalcDelegate mEventReportWindowCalcDelegate;
123     private final SourceNoiseHandler mSourceNoiseHandler;
124 
DebugReportApi(Context context, Flags flags)125     public DebugReportApi(Context context, Flags flags) {
126         this(
127                 context,
128                 flags,
129                 new EventReportWindowCalcDelegate(flags),
130                 new SourceNoiseHandler(flags));
131     }
132 
133     // TODO(b/292009320): Keep only one constructor in DebugReportApi
134     @VisibleForTesting
DebugReportApi( Context context, Flags flags, EventReportWindowCalcDelegate eventReportWindowCalcDelegate, SourceNoiseHandler sourceNoiseHandler)135     DebugReportApi(
136             Context context,
137             Flags flags,
138             EventReportWindowCalcDelegate eventReportWindowCalcDelegate,
139             SourceNoiseHandler sourceNoiseHandler) {
140         mContext = context;
141         mFlags = flags;
142         mDatastoreManager = DatastoreManagerFactory.getDatastoreManager(context);
143         mEventReportWindowCalcDelegate = eventReportWindowCalcDelegate;
144         mSourceNoiseHandler = sourceNoiseHandler;
145     }
146 
147     @VisibleForTesting
DebugReportApi( Context context, Flags flags, EventReportWindowCalcDelegate eventReportWindowCalcDelegate, SourceNoiseHandler sourceNoiseHandler, DatastoreManager datastoreManager)148     public DebugReportApi(
149             Context context,
150             Flags flags,
151             EventReportWindowCalcDelegate eventReportWindowCalcDelegate,
152             SourceNoiseHandler sourceNoiseHandler,
153             DatastoreManager datastoreManager) {
154         mContext = context;
155         mFlags = flags;
156         mEventReportWindowCalcDelegate = eventReportWindowCalcDelegate;
157         mSourceNoiseHandler = sourceNoiseHandler;
158         mDatastoreManager = datastoreManager;
159     }
160 
161     /** Schedules the Source Success Debug Report */
scheduleSourceSuccessDebugReport( Source source, IMeasurementDao dao, @Nullable Map<String, String> additionalBodyParams)162     public void scheduleSourceSuccessDebugReport(
163             Source source,
164             IMeasurementDao dao,
165             @Nullable Map<String, String> additionalBodyParams) {
166         if (isSourceDebugFlagDisabled(Type.SOURCE_SUCCESS)) {
167             return;
168         }
169         if (isAdTechNotOptIn(source.isDebugReporting(), Type.SOURCE_SUCCESS)) {
170             return;
171         }
172         if (!isSourcePermissionGranted(source)) {
173             LoggerFactory.getMeasurementLogger().d("Skipping debug report %s", Type.SOURCE_SUCCESS);
174             return;
175         }
176         scheduleReport(
177                 Type.SOURCE_SUCCESS,
178                 generateSourceDebugReportBody(source, additionalBodyParams),
179                 source.getEnrollmentId(),
180                 source.getRegistrationOrigin(),
181                 source.getRegistrant(),
182                 dao);
183     }
184 
185     /** Schedules the Source Destination limit Debug Report */
scheduleSourceDestinationLimitDebugReport( Source source, String limit, IMeasurementDao dao)186     public void scheduleSourceDestinationLimitDebugReport(
187             Source source, String limit, IMeasurementDao dao) {
188         scheduleSourceDestinationLimitDebugReport(
189                 source, limit, Type.SOURCE_DESTINATION_LIMIT, dao);
190     }
191 
192     /** Schedules the Source Destination rate-limit Debug Report */
scheduleSourceDestinationRateLimitDebugReport( Source source, String limit, IMeasurementDao dao)193     public void scheduleSourceDestinationRateLimitDebugReport(
194             Source source, String limit, IMeasurementDao dao) {
195         scheduleSourceDestinationLimitDebugReport(
196                 source, limit, Type.SOURCE_DESTINATION_RATE_LIMIT, dao);
197     }
198 
199     /** Schedules the Source Noised Debug Report */
scheduleSourceNoisedDebugReport( Source source, IMeasurementDao dao, @Nullable Map<String, String> additionalDebugReportParams)200     public void scheduleSourceNoisedDebugReport(
201             Source source,
202             IMeasurementDao dao,
203             @Nullable Map<String, String> additionalDebugReportParams) {
204         if (isSourceDebugFlagDisabled(Type.SOURCE_NOISED)) {
205             return;
206         }
207         if (isAdTechNotOptIn(source.isDebugReporting(), Type.SOURCE_NOISED)) {
208             return;
209         }
210         if (!isSourcePermissionGranted(source)) {
211             LoggerFactory.getMeasurementLogger().d("Skipping debug report %s", Type.SOURCE_NOISED);
212             return;
213         }
214         scheduleReport(
215                 Type.SOURCE_NOISED,
216                 generateSourceDebugReportBody(source, additionalDebugReportParams),
217                 source.getEnrollmentId(),
218                 source.getRegistrationOrigin(),
219                 source.getRegistrant(),
220                 dao);
221     }
222 
223     /** Schedules Source Storage Limit Debug Report */
scheduleSourceStorageLimitDebugReport( Source source, String limit, IMeasurementDao dao)224     public void scheduleSourceStorageLimitDebugReport(
225             Source source, String limit, IMeasurementDao dao) {
226         if (isSourceDebugFlagDisabled(Type.SOURCE_STORAGE_LIMIT)) {
227             return;
228         }
229         if (isAdTechNotOptIn(source.isDebugReporting(), Type.SOURCE_STORAGE_LIMIT)) {
230             return;
231         }
232         if (!isSourcePermissionGranted(source)) {
233             LoggerFactory.getMeasurementLogger()
234                     .d("Skipping debug report %s", Type.SOURCE_STORAGE_LIMIT);
235             return;
236         }
237         scheduleReport(
238                 Type.SOURCE_STORAGE_LIMIT,
239                 generateSourceDebugReportBody(source, Map.of(Body.LIMIT, String.valueOf(limit))),
240                 source.getEnrollmentId(),
241                 source.getRegistrationOrigin(),
242                 source.getRegistrant(),
243                 dao);
244     }
245 
246     /** Schedules Source Flexible Event API Debug Report */
scheduleSourceFlexibleEventReportApiDebugReport( Source source, IMeasurementDao dao)247     public void scheduleSourceFlexibleEventReportApiDebugReport(
248             Source source, IMeasurementDao dao) {
249         if (isSourceDebugFlagDisabled(Type.SOURCE_FLEXIBLE_EVENT_REPORT_VALUE_ERROR)) {
250             return;
251         }
252         if (isAdTechNotOptIn(
253                 source.isDebugReporting(), Type.SOURCE_FLEXIBLE_EVENT_REPORT_VALUE_ERROR)) {
254             return;
255         }
256         if (!isSourcePermissionGranted(source)) {
257             LoggerFactory.getMeasurementLogger()
258                     .d("Skipping debug report %s", Type.SOURCE_FLEXIBLE_EVENT_REPORT_VALUE_ERROR);
259             return;
260         }
261         scheduleReport(
262                 Type.SOURCE_FLEXIBLE_EVENT_REPORT_VALUE_ERROR,
263                 generateSourceDebugReportBody(source, null),
264                 source.getEnrollmentId(),
265                 source.getRegistrationOrigin(),
266                 source.getRegistrant(),
267                 dao);
268     }
269 
270     /** Schedules the Source Unknown Error Debug Report */
scheduleSourceUnknownErrorDebugReport(Source source, IMeasurementDao dao)271     public void scheduleSourceUnknownErrorDebugReport(Source source, IMeasurementDao dao) {
272         if (isSourceDebugFlagDisabled(Type.SOURCE_UNKNOWN_ERROR)) {
273             return;
274         }
275         if (isAdTechNotOptIn(source.isDebugReporting(), Type.SOURCE_UNKNOWN_ERROR)) {
276             return;
277         }
278         if (!isSourcePermissionGranted(source)) {
279             LoggerFactory.getMeasurementLogger()
280                     .d("Skipping debug report %s", Type.SOURCE_UNKNOWN_ERROR);
281             return;
282         }
283         scheduleReport(
284                 Type.SOURCE_UNKNOWN_ERROR,
285                 generateSourceDebugReportBody(source, null),
286                 source.getEnrollmentId(),
287                 source.getRegistrationOrigin(),
288                 source.getRegistrant(),
289                 dao);
290     }
291 
292     /**
293      * Schedules trigger-no-matching-source and trigger-unknown-error debug reports when trigger
294      * doesn't have related source.
295      */
scheduleTriggerNoMatchingSourceDebugReport( Trigger trigger, IMeasurementDao dao, String type)296     public void scheduleTriggerNoMatchingSourceDebugReport(
297             Trigger trigger, IMeasurementDao dao, String type) throws DatastoreException {
298         if (isTriggerDebugFlagDisabled(type)) {
299             return;
300         }
301         if (isAdTechNotOptIn(trigger.isDebugReporting(), type)) {
302             return;
303         }
304         if (!isTriggerPermissionGranted(trigger)) {
305             LoggerFactory.getMeasurementLogger().d("Skipping trigger debug report %s", type);
306             return;
307         }
308         Pair<UnsignedLong, UnsignedLong> debugKeyPair =
309                 new DebugKeyAccessor(dao).getDebugKeysForVerboseTriggerDebugReport(null, trigger);
310         scheduleReport(
311                 type,
312                 generateTriggerDebugReportBody(null, trigger, null, debugKeyPair, true),
313                 trigger.getEnrollmentId(),
314                 trigger.getRegistrationOrigin(),
315                 trigger.getRegistrant(),
316                 dao);
317     }
318 
319     /** Schedules Trigger Debug Reports with/without limit, pass in Type for different types. */
scheduleTriggerDebugReport( Source source, Trigger trigger, @Nullable String limit, IMeasurementDao dao, String type)320     public void scheduleTriggerDebugReport(
321             Source source,
322             Trigger trigger,
323             @Nullable String limit,
324             IMeasurementDao dao,
325             String type)
326             throws DatastoreException {
327         if (isTriggerDebugFlagDisabled(type)) {
328             return;
329         }
330         if (isAdTechNotOptIn(trigger.isDebugReporting(), type)) {
331             return;
332         }
333         if (!isSourceAndTriggerPermissionsGranted(source, trigger)) {
334             LoggerFactory.getMeasurementLogger().d("Skipping trigger debug report %s", type);
335             return;
336         }
337         Pair<UnsignedLong, UnsignedLong> debugKeyPair =
338                 new DebugKeyAccessor(dao).getDebugKeysForVerboseTriggerDebugReport(source, trigger);
339         scheduleReport(
340                 type,
341                 generateTriggerDebugReportBody(source, trigger, limit, debugKeyPair, false),
342                 source.getEnrollmentId(),
343                 trigger.getRegistrationOrigin(),
344                 source.getRegistrant(),
345                 dao);
346     }
347 
348     /**
349      * Schedules Trigger Debug Report with all body fields, Used for trigger-low-priority report and
350      * trigger-event-excessive-reports.
351      */
scheduleTriggerDebugReportWithAllFields( Source source, Trigger trigger, UnsignedLong triggerData, IMeasurementDao dao, String type)352     public void scheduleTriggerDebugReportWithAllFields(
353             Source source,
354             Trigger trigger,
355             UnsignedLong triggerData,
356             IMeasurementDao dao,
357             String type)
358             throws DatastoreException {
359         if (isTriggerDebugFlagDisabled(type)) {
360             return;
361         }
362         if (isAdTechNotOptIn(trigger.isDebugReporting(), type)) {
363             return;
364         }
365         if (!isSourceAndTriggerPermissionsGranted(source, trigger)) {
366             LoggerFactory.getMeasurementLogger().d("Skipping trigger debug report %s", type);
367             return;
368         }
369         Pair<UnsignedLong, UnsignedLong> debugKeyPair =
370                 new DebugKeyAccessor(dao).getDebugKeysForVerboseTriggerDebugReport(source, trigger);
371         scheduleReport(
372                 type,
373                 generateTriggerDebugReportBodyWithAllFields(
374                         source, trigger, triggerData, debugKeyPair),
375                 source.getEnrollmentId(),
376                 trigger.getRegistrationOrigin(),
377                 source.getRegistrant(),
378                 dao);
379     }
380 
381     /** Schedules the Source Destination limit type Debug Report */
scheduleSourceDestinationLimitDebugReport( Source source, String limit, String type, IMeasurementDao dao)382     private void scheduleSourceDestinationLimitDebugReport(
383             Source source, String limit, String type, IMeasurementDao dao) {
384         if (isSourceDebugFlagDisabled(Type.SOURCE_DESTINATION_LIMIT)) {
385             return;
386         }
387         if (isAdTechNotOptIn(source.isDebugReporting(), Type.SOURCE_DESTINATION_LIMIT)) {
388             return;
389         }
390         try {
391             JSONObject body = new JSONObject();
392             body.put(Body.SOURCE_EVENT_ID, source.getEventId().toString());
393             body.put(Body.ATTRIBUTION_DESTINATION, generateSourceDestinations(source));
394             body.put(Body.SOURCE_SITE, generateSourceSite(source));
395             body.put(Body.LIMIT, limit);
396             if (getAdIdPermissionFromSource(source) == PermissionState.GRANTED
397                     || getArDebugPermissionFromSource(source) == PermissionState.GRANTED) {
398                 body.put(Body.SOURCE_DEBUG_KEY, source.getDebugKey());
399             }
400             scheduleReport(
401                     type,
402                     body,
403                     source.getEnrollmentId(),
404                     source.getRegistrationOrigin(),
405                     source.getRegistrant(),
406                     dao);
407         } catch (JSONException e) {
408             LoggerFactory.getMeasurementLogger().e(e, "Json error in debug report %s", type);
409         }
410     }
411 
412     /** Schedule header parsing and validation errors verbose debug reports. */
scheduleHeaderErrorReport( Uri registrationUri, Uri registrant, String headerName, String enrollmentId, String errorMessage, String originalHeader, IMeasurementDao dao)413     public void scheduleHeaderErrorReport(
414             Uri registrationUri,
415             Uri registrant,
416             String headerName,
417             String enrollmentId,
418             String errorMessage,
419             String originalHeader,
420             IMeasurementDao dao) {
421         try {
422             JSONObject body = new JSONObject();
423             body.put(Body.CONTEXT_SITE, registrationUri);
424             body.put(Body.HEADER, headerName);
425             body.put(Body.VALUE, originalHeader);
426             body.put(Body.ERROR, errorMessage);
427             scheduleReport(
428                     Type.HEADER_PARSING_ERROR,
429                     body,
430                     enrollmentId,
431                     registrationUri,
432                     registrant,
433                     dao);
434         } catch (JSONException e) {
435             LoggerFactory.getMeasurementLogger()
436                     .e(e, "Json error in debug report %s", Type.HEADER_PARSING_ERROR);
437         }
438     }
439 
440     /**
441      * Schedules the Debug Report to be sent
442      *
443      * @param type The type of the debug report
444      * @param body The body of the debug report
445      * @param enrollmentId Ad Tech enrollment ID
446      * @param registrationOrigin Reporting origin of the report
447      * @param dao Measurement DAO
448      * @param registrant App Registrant
449      */
scheduleReport( @onNull String type, @NonNull JSONObject body, @NonNull String enrollmentId, @NonNull Uri registrationOrigin, @Nullable Uri registrant, @NonNull IMeasurementDao dao)450     private void scheduleReport(
451             @NonNull String type,
452             @NonNull JSONObject body,
453             @NonNull String enrollmentId,
454             @NonNull Uri registrationOrigin,
455             @Nullable Uri registrant,
456             @NonNull IMeasurementDao dao) {
457         Objects.requireNonNull(type);
458         Objects.requireNonNull(body);
459         Objects.requireNonNull(enrollmentId);
460         Objects.requireNonNull(dao);
461         if (type.isEmpty() || body.length() == 0) {
462             LoggerFactory.getMeasurementLogger().d("Empty debug report found %s", type);
463             return;
464         }
465         if (enrollmentId.isEmpty()) {
466             LoggerFactory.getMeasurementLogger().d("Empty enrollment found %s", type);
467             return;
468         }
469 
470         DebugReport debugReport =
471                 new DebugReport.Builder()
472                         .setId(UUID.randomUUID().toString())
473                         .setType(type)
474                         .setBody(body)
475                         .setEnrollmentId(enrollmentId)
476                         .setRegistrationOrigin(registrationOrigin)
477                         .setInsertionTime(System.currentTimeMillis())
478                         .setRegistrant(registrant)
479                         .build();
480         try {
481             dao.insertDebugReport(debugReport);
482         } catch (DatastoreException e) {
483             LoggerFactory.getMeasurementLogger().e(e, "Failed to insert debug report %s", type);
484         }
485 
486         VerboseDebugReportingJobService.scheduleIfNeeded(mContext, /*forceSchedule=*/ false);
487     }
488 
489     /** Get AdIdPermission State from Source */
getAdIdPermissionFromSource(Source source)490     private PermissionState getAdIdPermissionFromSource(Source source) {
491         if (source.getPublisherType() == EventSurfaceType.APP) {
492             if (source.hasAdIdPermission()) {
493                 return PermissionState.GRANTED;
494             } else {
495                 LoggerFactory.getMeasurementLogger().d("Source doesn't have AdId permission");
496                 return PermissionState.DENIED;
497             }
498         }
499         return PermissionState.NONE;
500     }
501 
502     /** Get ArDebugPermission State from Source */
getArDebugPermissionFromSource(Source source)503     private PermissionState getArDebugPermissionFromSource(Source source) {
504         if (source.getPublisherType() == EventSurfaceType.WEB) {
505             if (source.hasArDebugPermission()) {
506                 return PermissionState.GRANTED;
507             } else {
508                 LoggerFactory.getMeasurementLogger().d("Source doesn't have ArDebug permission");
509                 return PermissionState.DENIED;
510             }
511         }
512         return PermissionState.NONE;
513     }
514 
getAdIdPermissionFromTrigger(Trigger trigger)515     private PermissionState getAdIdPermissionFromTrigger(Trigger trigger) {
516         if (trigger.getDestinationType() == EventSurfaceType.APP) {
517             if (trigger.hasAdIdPermission()) {
518                 return PermissionState.GRANTED;
519             } else {
520                 LoggerFactory.getMeasurementLogger().d("Trigger doesn't have AdId permission");
521                 return PermissionState.DENIED;
522             }
523         }
524         return PermissionState.NONE;
525     }
526 
getArDebugPermissionFromTrigger(Trigger trigger)527     private PermissionState getArDebugPermissionFromTrigger(Trigger trigger) {
528         if (trigger.getDestinationType() == EventSurfaceType.WEB) {
529             if (trigger.hasArDebugPermission()) {
530                 return PermissionState.GRANTED;
531             } else {
532                 LoggerFactory.getMeasurementLogger().d("Trigger doesn't have ArDebug permission");
533                 return PermissionState.DENIED;
534             }
535         }
536         return PermissionState.NONE;
537     }
538 
539     /**
540      * Check AdId and ArDebug permissions for both source and trigger. Return true if all of them
541      * are not in {@link PermissionState#DENIED} state.
542      */
isSourceAndTriggerPermissionsGranted(Source source, Trigger trigger)543     private boolean isSourceAndTriggerPermissionsGranted(Source source, Trigger trigger) {
544         return isSourcePermissionGranted(source) && isTriggerPermissionGranted(trigger);
545     }
546 
isSourcePermissionGranted(Source source)547     private boolean isSourcePermissionGranted(Source source) {
548         return getAdIdPermissionFromSource(source) != PermissionState.DENIED
549                 && getArDebugPermissionFromSource(source) != PermissionState.DENIED;
550     }
551 
isTriggerPermissionGranted(Trigger trigger)552     private boolean isTriggerPermissionGranted(Trigger trigger) {
553         return getAdIdPermissionFromTrigger(trigger) != PermissionState.DENIED
554                 && getArDebugPermissionFromTrigger(trigger) != PermissionState.DENIED;
555     }
556 
557     /** Get is Ad tech not op-in and log */
isAdTechNotOptIn(boolean optIn, String type)558     private boolean isAdTechNotOptIn(boolean optIn, String type) {
559         if (!optIn) {
560             LoggerFactory.getMeasurementLogger()
561                     .d("Ad-tech not opt-in. Skipping debug report %s", type);
562         }
563         return !optIn;
564     }
565 
566     /** Generates source debug report body */
generateSourceDebugReportBody( Source source, @Nullable Map<String, String> additionalBodyParams)567     private JSONObject generateSourceDebugReportBody(
568             Source source, @Nullable Map<String, String> additionalBodyParams) {
569         JSONObject body = new JSONObject();
570         try {
571             body.put(Body.SOURCE_EVENT_ID, source.getEventId().toString());
572             body.put(Body.ATTRIBUTION_DESTINATION, generateSourceDestinations(source));
573             body.put(Body.SOURCE_SITE, generateSourceSite(source));
574             body.put(Body.SOURCE_DEBUG_KEY, source.getDebugKey());
575             if (additionalBodyParams != null) {
576                 for (Map.Entry<String, String> entry : additionalBodyParams.entrySet()) {
577                     body.put(entry.getKey(), entry.getValue());
578                 }
579             }
580 
581         } catch (JSONException e) {
582             LoggerFactory.getMeasurementLogger()
583                     .e(e, "Json error while generating source debug report body.");
584         }
585         return body;
586     }
587 
generateSourceDestinations(Source source)588     private static Object generateSourceDestinations(Source source) throws JSONException {
589         List<Uri> destinations = new ArrayList<>();
590         Optional.ofNullable(source.getAppDestinations()).ifPresent(destinations::addAll);
591         List<Uri> webDestinations = source.getWebDestinations();
592         if (webDestinations != null) {
593             for (Uri webDestination : webDestinations) {
594                 Optional<Uri> webUri = WebAddresses.topPrivateDomainAndScheme(webDestination);
595                 webUri.ifPresent(destinations::add);
596             }
597         }
598         return ReportUtil.serializeAttributionDestinations(destinations);
599     }
600 
generateSourceSite(Source source)601     private static Uri generateSourceSite(Source source) {
602         if (source.getPublisherType() == EventSurfaceType.APP) {
603             return source.getPublisher();
604         } else {
605             return WebAddresses.topPrivateDomainAndScheme(source.getPublisher()).orElse(null);
606         }
607     }
608 
609     /** Generates trigger debug report body */
generateTriggerDebugReportBody( @ullable Source source, @NonNull Trigger trigger, @Nullable String limit, @NonNull Pair<UnsignedLong, UnsignedLong> debugKeyPair, boolean isTriggerNoMatchingSource)610     private JSONObject generateTriggerDebugReportBody(
611             @Nullable Source source,
612             @NonNull Trigger trigger,
613             @Nullable String limit,
614             @NonNull Pair<UnsignedLong, UnsignedLong> debugKeyPair,
615             boolean isTriggerNoMatchingSource) {
616         JSONObject body = new JSONObject();
617         try {
618             body.put(Body.ATTRIBUTION_DESTINATION, trigger.getAttributionDestinationBaseUri());
619             body.put(Body.TRIGGER_DEBUG_KEY, debugKeyPair.second);
620             if (isTriggerNoMatchingSource) {
621                 return body;
622             }
623             body.put(Body.LIMIT, limit);
624             body.put(Body.SOURCE_DEBUG_KEY, debugKeyPair.first);
625             body.put(Body.SOURCE_EVENT_ID, source.getEventId().toString());
626             body.put(Body.SOURCE_SITE, generateSourceSite(source));
627         } catch (JSONException e) {
628             LoggerFactory.getMeasurementLogger()
629                     .e(e, "Json error while generating trigger debug report body.");
630         }
631         return body;
632     }
633 
634     /**
635      * Generates trigger debug report body with all fields in event-level attribution report. Used
636      * for trigger-low-priority, trigger-event-excessive-reports debug reports.
637      */
generateTriggerDebugReportBodyWithAllFields( @onNull Source source, @NonNull Trigger trigger, @Nullable UnsignedLong triggerData, @NonNull Pair<UnsignedLong, UnsignedLong> debugKeyPair)638     private JSONObject generateTriggerDebugReportBodyWithAllFields(
639             @NonNull Source source,
640             @NonNull Trigger trigger,
641             @Nullable UnsignedLong triggerData,
642             @NonNull Pair<UnsignedLong, UnsignedLong> debugKeyPair) {
643         JSONObject body = new JSONObject();
644         try {
645             body.put(Body.ATTRIBUTION_DESTINATION, trigger.getAttributionDestinationBaseUri());
646             body.put(
647                     Body.SCHEDULED_REPORT_TIME,
648                     String.valueOf(
649                             TimeUnit.MILLISECONDS.toSeconds(
650                                     mEventReportWindowCalcDelegate.getReportingTime(
651                                             source,
652                                             trigger.getTriggerTime(),
653                                             trigger.getDestinationType()))));
654             body.put(Body.SOURCE_EVENT_ID, source.getEventId());
655             body.put(Body.SOURCE_TYPE, source.getSourceType().getValue());
656             body.put(
657                     Body.RANDOMIZED_TRIGGER_RATE,
658                     mSourceNoiseHandler.getRandomizedTriggerRate(source));
659             if (triggerData != null) {
660                 body.put(Body.TRIGGER_DATA, triggerData.toString());
661             }
662             if (debugKeyPair.first != null) {
663                 body.put(Body.SOURCE_DEBUG_KEY, debugKeyPair.first);
664             }
665             if (debugKeyPair.second != null) {
666                 body.put(Body.TRIGGER_DEBUG_KEY, debugKeyPair.second);
667             }
668         } catch (JSONException e) {
669             LoggerFactory.getMeasurementLogger()
670                     .e(e, "Json error while generating trigger debug report body with all fields.");
671         }
672         return body;
673     }
674 
675     /** Checks flags for source debug reports. */
isSourceDebugFlagDisabled(String type)676     private boolean isSourceDebugFlagDisabled(String type) {
677         if (!mFlags.getMeasurementEnableDebugReport()
678                 || !mFlags.getMeasurementEnableSourceDebugReport()) {
679             LoggerFactory.getMeasurementLogger()
680                     .d("Source flag is disabled for %s debug report", type);
681             return true;
682         }
683         return false;
684     }
685 
686     /** Checks flags for trigger debug reports. */
isTriggerDebugFlagDisabled(String type)687     private boolean isTriggerDebugFlagDisabled(String type) {
688         if (!mFlags.getMeasurementEnableDebugReport()
689                 || !mFlags.getMeasurementEnableTriggerDebugReport()) {
690             LoggerFactory.getMeasurementLogger()
691                     .d("Trigger flag is disabled for %s debug report", type);
692             return true;
693         }
694         return false;
695     }
696 }
697