• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.registration;
18 
19 
20 import android.content.ContentProviderClient;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.net.Uri;
24 import android.os.RemoteException;
25 
26 import com.android.adservices.LogUtil;
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.FlagsFactory;
33 import com.android.adservices.service.measurement.Attribution;
34 import com.android.adservices.service.measurement.EventReport;
35 import com.android.adservices.service.measurement.EventSurfaceType;
36 import com.android.adservices.service.measurement.KeyValueData;
37 import com.android.adservices.service.measurement.KeyValueData.DataType;
38 import com.android.adservices.service.measurement.PrivacyParams;
39 import com.android.adservices.service.measurement.Source;
40 import com.android.adservices.service.measurement.SystemHealthParams;
41 import com.android.adservices.service.measurement.Trigger;
42 import com.android.adservices.service.measurement.attribution.TriggerContentProvider;
43 import com.android.adservices.service.measurement.noising.SourceNoiseHandler;
44 import com.android.adservices.service.measurement.reporting.DebugReportApi;
45 import com.android.adservices.service.measurement.util.BaseUriExtractor;
46 import com.android.adservices.service.measurement.util.Web;
47 import com.android.adservices.service.stats.AdServicesLoggerImpl;
48 import com.android.internal.annotations.VisibleForTesting;
49 
50 import java.util.HashSet;
51 import java.util.List;
52 import java.util.Objects;
53 import java.util.Optional;
54 import java.util.Set;
55 import java.util.UUID;
56 import java.util.stream.Collectors;
57 
58 /** Runner for servicing queued registration requests */
59 public class AsyncRegistrationQueueRunner {
60     private static AsyncRegistrationQueueRunner sAsyncRegistrationQueueRunner;
61     private final DatastoreManager mDatastoreManager;
62     private final AsyncSourceFetcher mAsyncSourceFetcher;
63     private final AsyncTriggerFetcher mAsyncTriggerFetcher;
64     private final ContentResolver mContentResolver;
65     private final DebugReportApi mDebugReportApi;
66     private final SourceNoiseHandler mSourceNoiseHandler;
67 
AsyncRegistrationQueueRunner(Context context)68     private AsyncRegistrationQueueRunner(Context context) {
69         mDatastoreManager = DatastoreManagerFactory.getDatastoreManager(context);
70         mAsyncSourceFetcher = new AsyncSourceFetcher(context);
71         mAsyncTriggerFetcher = new AsyncTriggerFetcher(context);
72         mContentResolver = context.getContentResolver();
73         mDebugReportApi = new DebugReportApi(context, FlagsFactory.getFlags());
74         mSourceNoiseHandler = new SourceNoiseHandler(FlagsFactory.getFlags());
75     }
76 
77     @VisibleForTesting
AsyncRegistrationQueueRunner( ContentResolver contentResolver, AsyncSourceFetcher asyncSourceFetcher, AsyncTriggerFetcher asyncTriggerFetcher, DatastoreManager datastoreManager, DebugReportApi debugReportApi, SourceNoiseHandler sourceNoiseHandler)78     public AsyncRegistrationQueueRunner(
79             ContentResolver contentResolver,
80             AsyncSourceFetcher asyncSourceFetcher,
81             AsyncTriggerFetcher asyncTriggerFetcher,
82             DatastoreManager datastoreManager,
83             DebugReportApi debugReportApi,
84             SourceNoiseHandler sourceNoiseHandler) {
85         mAsyncSourceFetcher = asyncSourceFetcher;
86         mAsyncTriggerFetcher = asyncTriggerFetcher;
87         mDatastoreManager = datastoreManager;
88         mContentResolver = contentResolver;
89         mDebugReportApi = debugReportApi;
90         mSourceNoiseHandler = sourceNoiseHandler;
91     }
92 
93     /**
94      * Returns an instance of AsyncRegistrationQueueRunner.
95      *
96      * @param context the current {@link Context}.
97      */
getInstance(Context context)98     public static synchronized AsyncRegistrationQueueRunner getInstance(Context context) {
99         Objects.requireNonNull(context);
100         if (sAsyncRegistrationQueueRunner == null) {
101             sAsyncRegistrationQueueRunner = new AsyncRegistrationQueueRunner(context);
102         }
103         return sAsyncRegistrationQueueRunner;
104     }
105 
106     /** Processes records in the AsyncRegistration Queue table. */
runAsyncRegistrationQueueWorker()107     public void runAsyncRegistrationQueueWorker() {
108         Flags flags = FlagsFactory.getFlags();
109         int recordServiceLimit = flags.getMeasurementMaxRegistrationsPerJobInvocation();
110         int retryLimit = flags.getMeasurementMaxRetriesPerRegistrationRequest();
111 
112         Set<Uri> failedOrigins = new HashSet<>();
113         for (int i = 0; i < recordServiceLimit; i++) {
114             Optional<AsyncRegistration> optAsyncRegistration =
115                     mDatastoreManager.runInTransactionWithResult(
116                             (dao) ->
117                                     dao.fetchNextQueuedAsyncRegistration(
118                                             retryLimit, failedOrigins));
119 
120             AsyncRegistration asyncRegistration;
121             if (optAsyncRegistration.isPresent()) {
122                 asyncRegistration = optAsyncRegistration.get();
123             } else {
124                 LogUtil.d("AsyncRegistrationQueueRunner: no async registration fetched.");
125                 return;
126             }
127 
128             if (asyncRegistration.isSourceRequest()) {
129                 LogUtil.d("AsyncRegistrationQueueRunner:" + " processing source");
130                 processSourceRegistration(asyncRegistration, failedOrigins);
131             } else {
132                 LogUtil.d("AsyncRegistrationQueueRunner:" + " processing trigger");
133                 processTriggerRegistration(asyncRegistration, failedOrigins);
134             }
135         }
136     }
137 
processSourceRegistration( AsyncRegistration asyncRegistration, Set<Uri> failedOrigins)138     private void processSourceRegistration(
139             AsyncRegistration asyncRegistration, Set<Uri> failedOrigins) {
140         AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
141         AsyncRedirect asyncRedirect = new AsyncRedirect();
142         long startTime = asyncRegistration.getRequestTime();
143         Optional<Source> resultSource =
144                 mAsyncSourceFetcher.fetchSource(asyncRegistration, asyncFetchStatus, asyncRedirect);
145         long endTime = System.currentTimeMillis();
146         asyncFetchStatus.setRegistrationDelay(endTime - startTime);
147 
148         boolean transactionResult =
149                 mDatastoreManager.runInTransaction(
150                         (dao) -> {
151                             if (asyncFetchStatus.isRequestSuccess()) {
152                                 if (resultSource.isPresent()) {
153                                     storeSource(resultSource.get(), asyncRegistration, dao);
154                                 }
155                                 handleSuccess(
156                                         asyncRegistration, asyncFetchStatus, asyncRedirect, dao);
157                             } else {
158                                 handleFailure(
159                                         asyncRegistration, asyncFetchStatus, failedOrigins, dao);
160                             }
161                         });
162 
163         if (!transactionResult) {
164             asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.STORAGE_ERROR);
165         }
166 
167         FetcherUtil.emitHeaderMetrics(
168                 FlagsFactory.getFlags(),
169                 AdServicesLoggerImpl.getInstance(),
170                 asyncRegistration,
171                 asyncFetchStatus);
172     }
173 
174     /** Visible only for testing. */
175     @VisibleForTesting
storeSource( Source source, AsyncRegistration asyncRegistration, IMeasurementDao dao)176     public void storeSource(
177             Source source, AsyncRegistration asyncRegistration, IMeasurementDao dao)
178             throws DatastoreException {
179         Uri topOrigin =
180                 asyncRegistration.getType() == AsyncRegistration.RegistrationType.WEB_SOURCE
181                         ? asyncRegistration.getTopOrigin()
182                         : getPublisher(asyncRegistration);
183         @EventSurfaceType
184         int publisherType =
185                 asyncRegistration.getType() == AsyncRegistration.RegistrationType.WEB_SOURCE
186                         ? EventSurfaceType.WEB
187                         : EventSurfaceType.APP;
188         if (isSourceAllowedToInsert(source, topOrigin, publisherType, dao, mDebugReportApi)) {
189             insertSourceFromTransaction(source, dao);
190             mDebugReportApi.scheduleSourceSuccessDebugReport(source, dao);
191         }
192     }
193 
processTriggerRegistration( AsyncRegistration asyncRegistration, Set<Uri> failedOrigins)194     private void processTriggerRegistration(
195             AsyncRegistration asyncRegistration, Set<Uri> failedOrigins) {
196         AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
197         AsyncRedirect asyncRedirect = new AsyncRedirect();
198         long startTime = asyncRegistration.getRequestTime();
199         Optional<Trigger> resultTrigger = mAsyncTriggerFetcher.fetchTrigger(
200                 asyncRegistration, asyncFetchStatus, asyncRedirect);
201         long endTime = System.currentTimeMillis();
202         asyncFetchStatus.setRegistrationDelay(endTime - startTime);
203 
204         boolean transactionResult =
205                 mDatastoreManager.runInTransaction(
206                         (dao) -> {
207                             if (asyncFetchStatus.isRequestSuccess()) {
208                                 if (resultTrigger.isPresent()) {
209                                     storeTrigger(resultTrigger.get(), dao);
210                                 }
211                                 handleSuccess(
212                                         asyncRegistration, asyncFetchStatus, asyncRedirect, dao);
213                             } else {
214                                 handleFailure(
215                                         asyncRegistration, asyncFetchStatus, failedOrigins, dao);
216                             }
217                         });
218 
219         if (!transactionResult) {
220             asyncFetchStatus.setEntityStatus(AsyncFetchStatus.EntityStatus.STORAGE_ERROR);
221         }
222 
223         FetcherUtil.emitHeaderMetrics(
224                 FlagsFactory.getFlags(),
225                 AdServicesLoggerImpl.getInstance(),
226                 asyncRegistration,
227                 asyncFetchStatus);
228     }
229 
230     /** Visible only for testing. */
231     @VisibleForTesting
storeTrigger(Trigger trigger, IMeasurementDao dao)232     public void storeTrigger(Trigger trigger, IMeasurementDao dao) throws DatastoreException {
233         if (isTriggerAllowedToInsert(dao, trigger)) {
234             try {
235                 dao.insertTrigger(trigger);
236             } catch (DatastoreException e) {
237                 mDebugReportApi.scheduleTriggerNoMatchingSourceDebugReport(
238                         trigger, dao, DebugReportApi.Type.TRIGGER_UNKNOWN_ERROR);
239                 LogUtil.e(e, "Insert trigger to DB error, generate trigger-unknown-error report");
240                 throw new DatastoreException(
241                         "Insert trigger to DB error, generate trigger-unknown-error report");
242             }
243             notifyTriggerContentProvider();
244         }
245     }
246 
247     /** Visible only for testing. */
248     @VisibleForTesting
isSourceAllowedToInsert( Source source, Uri topOrigin, @EventSurfaceType int publisherType, IMeasurementDao dao, DebugReportApi debugReportApi)249     public static boolean isSourceAllowedToInsert(
250             Source source,
251             Uri topOrigin,
252             @EventSurfaceType int publisherType,
253             IMeasurementDao dao,
254             DebugReportApi debugReportApi)
255             throws DatastoreException {
256         long windowStartTime = source.getEventTime() - PrivacyParams.RATE_LIMIT_WINDOW_MILLISECONDS;
257         Optional<Uri> publisher = getTopLevelPublisher(topOrigin, publisherType);
258         if (!publisher.isPresent()) {
259             LogUtil.d("insertSources: getTopLevelPublisher failed", topOrigin);
260             return false;
261         }
262         long numOfSourcesPerPublisher =
263                 dao.getNumSourcesPerPublisher(
264                         BaseUriExtractor.getBaseUri(topOrigin), publisherType);
265 
266         if (numOfSourcesPerPublisher >= SystemHealthParams.getMaxSourcesPerPublisher()) {
267             LogUtil.d(
268                     "insertSources: Max limit of %s sources for publisher - %s reached.",
269                     SystemHealthParams.getMaxSourcesPerPublisher(), publisher);
270             debugReportApi.scheduleSourceStorageLimitDebugReport(
271                     source, String.valueOf(numOfSourcesPerPublisher), dao);
272             return false;
273         }
274         int numOfOriginExcludingRegistrationOrigin =
275                 dao.countSourcesPerPublisherXEnrollmentExcludingRegOrigin(
276                         source.getRegistrationOrigin(),
277                         publisher.get(),
278                         publisherType,
279                         source.getEnrollmentId(),
280                         source.getEventTime(),
281                         PrivacyParams.MIN_REPORTING_ORIGIN_UPDATE_WINDOW);
282         if (numOfOriginExcludingRegistrationOrigin > 0) {
283             LogUtil.d(
284                     "insertSources: Max limit of 1 reporting origin for publisher - %s and"
285                             + " enrollment - %s reached.",
286                     publisher, source.getEnrollmentId());
287             return false;
288         }
289         if (source.getAppDestinations() != null
290                 && !isDestinationWithinBounds(
291                         debugReportApi,
292                         source,
293                         publisher.get(),
294                         publisherType,
295                         source.getEnrollmentId(),
296                         source.getAppDestinations(),
297                         EventSurfaceType.APP,
298                         windowStartTime,
299                         source.getEventTime(),
300                         dao)) {
301             return false;
302         }
303 
304         if (source.getWebDestinations() != null
305                 && !isDestinationWithinBounds(
306                         debugReportApi,
307                         source,
308                         publisher.get(),
309                         publisherType,
310                         source.getEnrollmentId(),
311                         source.getWebDestinations(),
312                         EventSurfaceType.WEB,
313                         windowStartTime,
314                         source.getEventTime(),
315                         dao)) {
316             return false;
317         }
318         return true;
319     }
320 
isDestinationWithinBounds( DebugReportApi debugReportApi, Source source, Uri publisher, @EventSurfaceType int publisherType, String enrollmentId, List<Uri> destinations, @EventSurfaceType int destinationType, long windowStartTime, long requestTime, IMeasurementDao dao)321     private static boolean isDestinationWithinBounds(
322             DebugReportApi debugReportApi,
323             Source source,
324             Uri publisher,
325             @EventSurfaceType int publisherType,
326             String enrollmentId,
327             List<Uri> destinations,
328             @EventSurfaceType int destinationType,
329             long windowStartTime,
330             long requestTime,
331             IMeasurementDao dao)
332             throws DatastoreException {
333         int destinationCount =
334                 dao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
335                         publisher,
336                         publisherType,
337                         enrollmentId,
338                         destinations,
339                         destinationType,
340                         windowStartTime,
341                         requestTime);
342         int maxDistinctDestinations =
343                 FlagsFactory.getFlags().getMeasurementMaxDistinctDestinationsInActiveSource();
344         if (destinationCount + destinations.size() > maxDistinctDestinations) {
345             LogUtil.d(
346                     "AsyncRegistrationQueueRunner: "
347                             + (destinationType == EventSurfaceType.APP ? "App" : "Web")
348                             + " destination count >= "
349                             + "MaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource");
350             debugReportApi.scheduleSourceDestinationLimitDebugReport(
351                     source, String.valueOf(maxDistinctDestinations), dao);
352             return false;
353         }
354 
355         int destinationEnrollmentCount =
356                 dao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
357                         publisher,
358                         publisherType,
359                         destinations,
360                         enrollmentId,
361                         windowStartTime,
362                         requestTime);
363         if (destinationEnrollmentCount
364                 >= PrivacyParams.getMaxDistinctEnrollmentsPerPublisherXDestinationInSource()) {
365             debugReportApi.scheduleSourceSuccessDebugReport(source, dao);
366             LogUtil.d(
367                     "AsyncRegistrationQueueRunner: "
368                             + (destinationType == EventSurfaceType.APP ? "App" : "Web")
369                             + " enrollment count >= "
370                             + "MaxDistinctEnrollmentsPerPublisherXDestinationInSource");
371             return false;
372         }
373         return true;
374     }
375 
376     @VisibleForTesting
isTriggerAllowedToInsert(IMeasurementDao dao, Trigger trigger)377     static boolean isTriggerAllowedToInsert(IMeasurementDao dao, Trigger trigger) {
378         long triggerInsertedPerDestination;
379         try {
380             triggerInsertedPerDestination =
381                     dao.getNumTriggersPerDestination(
382                             trigger.getAttributionDestination(), trigger.getDestinationType());
383         } catch (DatastoreException e) {
384             LogUtil.e("Unable to fetch number of triggers currently registered per destination.");
385             return false;
386         }
387 
388         return triggerInsertedPerDestination < SystemHealthParams.getMaxTriggersPerDestination();
389     }
390 
createAsyncRegistrationFromRedirect( AsyncRegistration asyncRegistration, Uri redirectUri)391     private static AsyncRegistration createAsyncRegistrationFromRedirect(
392             AsyncRegistration asyncRegistration, Uri redirectUri) {
393         return new AsyncRegistration.Builder()
394                 .setId(UUID.randomUUID().toString())
395                 .setRegistrationUri(redirectUri)
396                 .setWebDestination(asyncRegistration.getWebDestination())
397                 .setOsDestination(asyncRegistration.getOsDestination())
398                 .setRegistrant(asyncRegistration.getRegistrant())
399                 .setVerifiedDestination(asyncRegistration.getVerifiedDestination())
400                 .setTopOrigin(asyncRegistration.getTopOrigin())
401                 .setType(asyncRegistration.getType())
402                 .setSourceType(asyncRegistration.getSourceType())
403                 .setRequestTime(asyncRegistration.getRequestTime())
404                 .setRetryCount(0)
405                 .setDebugKeyAllowed(asyncRegistration.getDebugKeyAllowed())
406                 .setAdIdPermission(asyncRegistration.hasAdIdPermission())
407                 .setRegistrationId(asyncRegistration.getRegistrationId())
408                 .build();
409     }
410 
generateFakeEventReports(Source source)411     private List<EventReport> generateFakeEventReports(Source source) {
412         List<Source.FakeReport> fakeReports =
413                 mSourceNoiseHandler.assignAttributionModeAndGenerateFakeReports(source);
414         return fakeReports.stream()
415                 .map(
416                         fakeReport ->
417                                 new EventReport.Builder()
418                                         .setSourceEventId(source.getEventId())
419                                         .setReportTime(fakeReport.getReportingTime())
420                                         .setTriggerData(fakeReport.getTriggerData())
421                                         .setAttributionDestinations(fakeReport.getDestinations())
422                                         .setEnrollmentId(source.getEnrollmentId())
423                                         // The query for attribution check is from
424                                         // (triggerTime - 30 days) to triggerTime and max expiry is
425                                         // 30 days, so it's safe to choose triggerTime as source
426                                         // event time so that it gets considered when the query is
427                                         // fired for attribution rate limit check.
428                                         .setTriggerTime(source.getEventTime())
429                                         .setTriggerPriority(0L)
430                                         .setTriggerDedupKey(null)
431                                         .setSourceType(source.getSourceType())
432                                         .setStatus(EventReport.Status.PENDING)
433                                         .setRandomizedTriggerRate(
434                                                 mSourceNoiseHandler.getRandomAttributionProbability(
435                                                         source))
436                                         .setRegistrationOrigin(source.getRegistrationOrigin())
437                                         .build())
438                 .collect(Collectors.toList());
439     }
440 
441     @VisibleForTesting
insertSourceFromTransaction(Source source, IMeasurementDao dao)442     void insertSourceFromTransaction(Source source, IMeasurementDao dao) throws DatastoreException {
443         List<EventReport> eventReports = generateFakeEventReports(source);
444         if (!eventReports.isEmpty()) {
445             mDebugReportApi.scheduleSourceNoisedDebugReport(source, dao);
446         }
447         try {
448             dao.insertSource(source);
449         } catch (DatastoreException e) {
450             mDebugReportApi.scheduleSourceUnknownErrorDebugReport(source, dao);
451             LogUtil.e(e, "Insert source to DB error, generate source-unknown-error report");
452             throw new DatastoreException(
453                     "Insert source to DB error, generate source-unknown-error report");
454         }
455         for (EventReport report : eventReports) {
456             dao.insertEventReport(report);
457         }
458         // We want to account for attribution if fake report generation was considered
459         // based on the probability. In that case the attribution mode will be NEVER
460         // (empty fake reports state) or FALSELY (non-empty fake reports).
461         if (source.getAttributionMode() != Source.AttributionMode.TRUTHFULLY) {
462             // Attribution rate limits for app and web destinations are counted
463             // separately, so add a fake report entry for each type of destination if
464             // non-null.
465             if (!Objects.isNull(source.getAppDestinations())) {
466                 for (Uri destination : source.getAppDestinations()) {
467                     dao.insertAttribution(createFakeAttributionRateLimit(source, destination));
468                 }
469             }
470 
471             if (!Objects.isNull(source.getWebDestinations())) {
472                 for (Uri destination : source.getWebDestinations()) {
473                     dao.insertAttribution(createFakeAttributionRateLimit(source, destination));
474                 }
475             }
476         }
477     }
478 
handleSuccess( AsyncRegistration asyncRegistration, AsyncFetchStatus asyncFetchStatus, AsyncRedirect asyncRedirect, IMeasurementDao dao)479     private void handleSuccess(
480             AsyncRegistration asyncRegistration,
481             AsyncFetchStatus asyncFetchStatus,
482             AsyncRedirect asyncRedirect,
483             IMeasurementDao dao)
484             throws DatastoreException {
485         // deleteAsyncRegistration will throw an exception & rollback the transaction if the record
486         // is already deleted. This can happen if both fallback & regular job are running at the
487         // same time or if deletion job deletes the records.
488         dao.deleteAsyncRegistration(asyncRegistration.getId());
489         if (asyncRedirect.getRedirects().isEmpty()) {
490             return;
491         }
492         int maxRedirects = FlagsFactory.getFlags().getMeasurementMaxRegistrationRedirects();
493         KeyValueData keyValueData =
494                 dao.getKeyValueData(
495                         asyncRegistration.getRegistrationId(),
496                         DataType.REGISTRATION_REDIRECT_COUNT);
497         int currentCount = keyValueData.getRegistrationRedirectCount();
498         if (currentCount == maxRedirects) {
499             asyncFetchStatus.setRedirectError(true);
500             return;
501         }
502         for (Uri uri : asyncRedirect.getRedirects()) {
503             if (currentCount >= maxRedirects) {
504                 break;
505             }
506             dao.insertAsyncRegistration(
507                     createAsyncRegistrationFromRedirect(asyncRegistration, uri));
508             currentCount++;
509         }
510         keyValueData.setRegistrationRedirectCount(currentCount);
511         dao.insertOrUpdateKeyValueData(keyValueData);
512     }
513 
handleFailure( AsyncRegistration asyncRegistration, AsyncFetchStatus asyncFetchStatus, Set<Uri> failedOrigins, IMeasurementDao dao)514     private void handleFailure(
515             AsyncRegistration asyncRegistration,
516             AsyncFetchStatus asyncFetchStatus,
517             Set<Uri> failedOrigins,
518             IMeasurementDao dao)
519             throws DatastoreException {
520         if (asyncFetchStatus.canRetry()) {
521             LogUtil.d(
522                     "AsyncRegistrationQueueRunner: "
523                             + "async "
524                             + asyncRegistration.getType()
525                             + " registration will be queued for retry "
526                             + "Fetch Status : "
527                             + asyncFetchStatus.getResponseStatus());
528             failedOrigins.add(BaseUriExtractor.getBaseUri(asyncRegistration.getRegistrationUri()));
529             asyncRegistration.incrementRetryCount();
530             dao.updateRetryCount(asyncRegistration);
531         } else {
532             LogUtil.d(
533                     "AsyncRegistrationQueueRunner: "
534                             + "async "
535                             + asyncRegistration.getType()
536                             + " registration will not be queued for retry. "
537                             + "Fetch Status : "
538                             + asyncFetchStatus.getResponseStatus());
539             dao.deleteAsyncRegistration(asyncRegistration.getId());
540         }
541     }
542 
543     /**
544      * {@link Attribution} generated from here will only be used for fake report attribution.
545      *
546      * @param source source to derive parameters from
547      * @param destination destination for attribution
548      * @return a fake {@link Attribution}
549      */
createFakeAttributionRateLimit(Source source, Uri destination)550     private Attribution createFakeAttributionRateLimit(Source source, Uri destination) {
551         Optional<Uri> topLevelPublisher =
552                 getTopLevelPublisher(source.getPublisher(), source.getPublisherType());
553 
554         if (!topLevelPublisher.isPresent()) {
555             throw new IllegalArgumentException(
556                     String.format(
557                             "insertAttributionRateLimit: getSourceAndDestinationTopPrivateDomains"
558                                     + " failed. Publisher: %s; Attribution destination: %s",
559                             source.getPublisher(), destination));
560         }
561 
562         return new Attribution.Builder()
563                 .setSourceSite(topLevelPublisher.get().toString())
564                 .setSourceOrigin(source.getPublisher().toString())
565                 .setDestinationSite(destination.toString())
566                 .setDestinationOrigin(destination.toString())
567                 .setEnrollmentId(source.getEnrollmentId())
568                 .setTriggerTime(source.getEventTime())
569                 .setRegistrant(source.getRegistrant().toString())
570                 .setSourceId(source.getId())
571                 // Intentionally kept it as null because it's a fake attribution
572                 .setTriggerId(null)
573                 .build();
574     }
575 
getTopLevelPublisher( Uri topOrigin, @EventSurfaceType int publisherType)576     private static Optional<Uri> getTopLevelPublisher(
577             Uri topOrigin, @EventSurfaceType int publisherType) {
578         return publisherType == EventSurfaceType.APP
579                 ? Optional.of(topOrigin)
580                 : Web.topPrivateDomainAndScheme(topOrigin);
581     }
582 
getPublisher(AsyncRegistration request)583     private Uri getPublisher(AsyncRegistration request) {
584         return request.getRegistrant();
585     }
586 
notifyTriggerContentProvider()587     private void notifyTriggerContentProvider() {
588         try (ContentProviderClient contentProviderClient =
589                 mContentResolver.acquireContentProviderClient(TriggerContentProvider.TRIGGER_URI)) {
590             if (contentProviderClient != null) {
591                 contentProviderClient.insert(TriggerContentProvider.TRIGGER_URI, null);
592             }
593         } catch (RemoteException e) {
594             LogUtil.e(e, "Trigger Content Provider invocation failed.");
595         }
596     }
597 }
598