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