summaryrefslogtreecommitdiff
path: root/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java
diff options
context:
space:
mode:
Diffstat (limited to 'adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java')
-rw-r--r--adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java687
1 files changed, 687 insertions, 0 deletions
diff --git a/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java b/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java
new file mode 100644
index 0000000000..e1378ae113
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/measurement/AsyncRegistrationQueueRunner.java
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.adservices.service.measurement;
+
+import static com.android.adservices.service.measurement.attribution.TriggerContentProvider.TRIGGER_URI;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.data.enrollment.EnrollmentDao;
+import com.android.adservices.data.measurement.DatastoreException;
+import com.android.adservices.data.measurement.DatastoreManager;
+import com.android.adservices.data.measurement.DatastoreManagerFactory;
+import com.android.adservices.data.measurement.IMeasurementDao;
+import com.android.adservices.service.measurement.registration.AsyncSourceFetcher;
+import com.android.adservices.service.measurement.registration.AsyncTriggerFetcher;
+import com.android.adservices.service.measurement.util.AsyncFetchStatus;
+import com.android.adservices.service.measurement.util.AsyncRedirect;
+import com.android.adservices.service.measurement.util.Enrollment;
+import com.android.adservices.service.measurement.util.Web;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+/** Runner for servicing queued registration requests */
+public class AsyncRegistrationQueueRunner {
+ private static AsyncRegistrationQueueRunner sAsyncRegistrationQueueRunner;
+ private DatastoreManager mDatastoreManager;
+ private AsyncSourceFetcher mAsyncSourceFetcher;
+ private AsyncTriggerFetcher mAsyncTriggerFetcher;
+ private EnrollmentDao mEnrollmentDao;
+ private final ContentResolver mContentResolver;
+
+ private AsyncRegistrationQueueRunner(Context context) {
+ mDatastoreManager = DatastoreManagerFactory.getDatastoreManager(context);
+ mAsyncSourceFetcher = new AsyncSourceFetcher(context);
+ mAsyncTriggerFetcher = new AsyncTriggerFetcher(context);
+ mEnrollmentDao = EnrollmentDao.getInstance(context);
+ mContentResolver = context.getContentResolver();
+ }
+
+ @VisibleForTesting
+ AsyncRegistrationQueueRunner(
+ ContentResolver contentResolver,
+ AsyncSourceFetcher asyncSourceFetcher,
+ AsyncTriggerFetcher asyncTriggerFetcher,
+ EnrollmentDao enrollmentDao,
+ DatastoreManager datastoreManager) {
+ mAsyncSourceFetcher = asyncSourceFetcher;
+ mAsyncTriggerFetcher = asyncTriggerFetcher;
+ mDatastoreManager = datastoreManager;
+ mEnrollmentDao = enrollmentDao;
+ mContentResolver = contentResolver;
+ }
+
+ /**
+ * Returns an instance of AsyncRegistrationQueueRunner.
+ *
+ * @param context the current {@link Context}.
+ */
+ public static synchronized AsyncRegistrationQueueRunner getInstance(Context context) {
+ Objects.requireNonNull(context);
+ if (sAsyncRegistrationQueueRunner == null) {
+ sAsyncRegistrationQueueRunner = new AsyncRegistrationQueueRunner(context);
+ }
+ return sAsyncRegistrationQueueRunner;
+ }
+
+ /**
+ * Service records in the AsyncRegistration Queue table.
+ *
+ * @param recordServiceLimit a long representing how many records will be serviced during this
+ * run.
+ * @param retryLimit represents the amount of retries that will be allowed for each record.
+ */
+ public void runAsyncRegistrationQueueWorker(long recordServiceLimit, short retryLimit) {
+ Set<String> failedAdTechEnrollmentIds = new HashSet<>();
+ for (int i = 0; i < recordServiceLimit; i++) {
+ Optional<AsyncRegistration> optionalAsyncRegistration =
+ mDatastoreManager.runInTransactionWithResult(
+ (dao) -> {
+ List<String> failedAdTechEnrollmentIdsList =
+ new ArrayList<String>();
+ failedAdTechEnrollmentIdsList.addAll(failedAdTechEnrollmentIds);
+ return dao.fetchNextQueuedAsyncRegistration(
+ retryLimit, failedAdTechEnrollmentIdsList);
+ });
+
+ AsyncRegistration asyncRegistration;
+ if (optionalAsyncRegistration.isPresent()) {
+ asyncRegistration = optionalAsyncRegistration.get();
+ } else {
+ LogUtil.d("AsyncRegistrationQueueRunner:" + " no async registration fetched.");
+ return;
+ }
+
+ if (asyncRegistration.getType() == AsyncRegistration.RegistrationType.APP_SOURCE
+ || asyncRegistration.getType()
+ == AsyncRegistration.RegistrationType.WEB_SOURCE) {
+ LogUtil.d("AsyncRegistrationQueueRunner:" + " processing source");
+ processSourceRegistration(asyncRegistration, failedAdTechEnrollmentIds);
+ } else if (asyncRegistration.getType() == AsyncRegistration.RegistrationType.APP_TRIGGER
+ || asyncRegistration.getType()
+ == AsyncRegistration.RegistrationType.WEB_TRIGGER) {
+ LogUtil.d("AsyncRegistrationQueueRunner:" + " processing trigger");
+ processTriggerRegistration(asyncRegistration, failedAdTechEnrollmentIds);
+ }
+ }
+ }
+
+ private void processSourceRegistration(
+ AsyncRegistration asyncRegistration, Set<String> failedAdTechEnrollmentIds) {
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ Optional<Source> resultSource =
+ mAsyncSourceFetcher.fetchSource(asyncRegistration, asyncFetchStatus, asyncRedirect);
+
+ mDatastoreManager.runInTransaction(
+ (dao) -> {
+ if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE
+ || asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.NETWORK_ERROR) {
+ failedAdTechEnrollmentIds.add(asyncRegistration.getEnrollmentId());
+ asyncRegistration.incrementRetryCount();
+ dao.updateRetryCount(asyncRegistration);
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "source registration will be queued for retry "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ } else if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.PARSING_ERROR
+ || asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT) {
+ dao.deleteAsyncRegistration(asyncRegistration.getId());
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "source registration will not be queued for retry. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ } else if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.SUCCESS) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "source registration success case. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ if (resultSource.isPresent()) {
+ Source source = resultSource.get();
+ Uri topOrigin =
+ asyncRegistration.getType()
+ == AsyncRegistration.RegistrationType.WEB_SOURCE
+ ? asyncRegistration.getTopOrigin()
+ : getPublisher(asyncRegistration);
+ @EventSurfaceType
+ int publisherType =
+ asyncRegistration.getType()
+ == AsyncRegistration.RegistrationType.WEB_SOURCE
+ ? EventSurfaceType.WEB
+ : EventSurfaceType.APP;
+ if (isSourceAllowedToInsert(source, topOrigin, publisherType, dao)) {
+ insertSourcesFromTransaction(source, dao);
+ }
+ Uri osDestination =
+ asyncRegistration.getRedirectType()
+ != AsyncRegistration.RedirectType.ANY
+ ? asyncRegistration.getOsDestination()
+ : source.getAppDestination();
+ Uri webDestination =
+ asyncRegistration.getRedirectType()
+ != AsyncRegistration.RedirectType.ANY
+ ? asyncRegistration.getWebDestination()
+ : source.getWebDestination();
+ if (asyncRegistration.shouldProcessRedirects()) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "source registration; processing redirects. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ processRedirects(
+ asyncRegistration,
+ asyncRedirect,
+ webDestination,
+ osDestination,
+ dao);
+ }
+ }
+ dao.deleteAsyncRegistration(asyncRegistration.getId());
+ }
+ });
+ }
+
+ private void processTriggerRegistration(
+ AsyncRegistration asyncRegistration, Set<String> failedAdTechEnrollmentIds) {
+ AsyncFetchStatus asyncFetchStatus = new AsyncFetchStatus();
+ AsyncRedirect asyncRedirect = new AsyncRedirect();
+ Optional<Trigger> resultTrigger = mAsyncTriggerFetcher.fetchTrigger(
+ asyncRegistration, asyncFetchStatus, asyncRedirect);
+ boolean status =
+ mDatastoreManager.runInTransaction(
+ (dao) -> {
+ if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.SERVER_UNAVAILABLE
+ || asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.NETWORK_ERROR) {
+ failedAdTechEnrollmentIds.add(asyncRegistration.getEnrollmentId());
+ asyncRegistration.incrementRetryCount();
+ dao.updateRetryCount(asyncRegistration);
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "trigger registration will be queued for retry "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ } else if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.PARSING_ERROR
+ || asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.INVALID_ENROLLMENT) {
+ dao.deleteAsyncRegistration(asyncRegistration.getId());
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: async trigger "
+ + "registration will not be queued for retry. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ } else if (asyncFetchStatus.getStatus()
+ == AsyncFetchStatus.ResponseStatus.SUCCESS) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + "async "
+ + "trigger registration success case. "
+ + "Fetch Status : "
+ + asyncFetchStatus.getStatus());
+ if (resultTrigger.isPresent()) {
+ Trigger trigger = resultTrigger.get();
+ if (asyncRegistration.shouldProcessRedirects()) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: async trigger"
+ + " registration; processing redirects. Fetch"
+ + " Status : "
+ + asyncFetchStatus.getStatus());
+ processRedirects(
+ asyncRegistration,
+ asyncRedirect,
+ asyncRegistration.getWebDestination(),
+ asyncRegistration.getOsDestination(),
+ dao);
+ }
+ dao.insertTrigger(trigger);
+ }
+ dao.deleteAsyncRegistration(asyncRegistration.getId());
+ }
+ });
+ if (status && asyncFetchStatus.getStatus() == AsyncFetchStatus.ResponseStatus.SUCCESS) {
+ notifyTriggerContentProvider();
+ }
+ }
+
+ private static Integer countDistinctDestinationsPerPublisher(
+ Uri publisher,
+ @EventSurfaceType int publisherType,
+ String enrollmentId,
+ Uri destination,
+ @EventSurfaceType int destinationType,
+ long windowStartTime,
+ long requestTime,
+ IMeasurementDao dao) {
+
+ try {
+ return dao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource(
+ publisher,
+ publisherType,
+ enrollmentId,
+ destination,
+ destinationType,
+ windowStartTime,
+ requestTime);
+ } catch (DatastoreException e) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + " countDistinctDestinationsPerPublisher failed: "
+ + e);
+ return null;
+ }
+ }
+
+ private static Integer countDistinctEnrollmentsPerPublisher(
+ Uri publisher,
+ @EventSurfaceType int publisherType,
+ Uri destination,
+ String enrollmentId,
+ long windowStartTime,
+ long requestTime,
+ IMeasurementDao dao) {
+
+ try {
+ return dao.countDistinctEnrollmentsPerPublisherXDestinationInSource(
+ publisher,
+ publisherType,
+ destination,
+ enrollmentId,
+ windowStartTime,
+ requestTime);
+ } catch (DatastoreException e) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: "
+ + " countDistinctEnrollmentsPerPublisher failed "
+ + e);
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ static boolean isSourceAllowedToInsert(
+ Source source,
+ Uri topOrigin,
+ @EventSurfaceType int publisherType,
+ IMeasurementDao dao) {
+ long windowStartTime = source.getEventTime() - PrivacyParams.RATE_LIMIT_WINDOW_MILLISECONDS;
+ Optional<Uri> publisher = getTopLevelPublisher(topOrigin, publisherType);
+ if (!publisher.isPresent()) {
+ LogUtil.d("insertSources: getTopLevelPublisher failed", topOrigin);
+ return false;
+ }
+ Long numOfSourcesPerPublisher;
+
+ try {
+ numOfSourcesPerPublisher =
+ dao.getNumSourcesPerPublisher(publisher.get(), publisherType);
+ } catch (DatastoreException e) {
+ LogUtil.d("insertSources: getNumSourcesPerPublisher failed", topOrigin);
+ return false;
+ }
+
+ if (numOfSourcesPerPublisher == null) {
+ LogUtil.d("insertSources: getNumSourcesPerPublisher failed", publisher.get());
+ return false;
+ }
+
+ if (numOfSourcesPerPublisher >= SystemHealthParams.MAX_SOURCES_PER_PUBLISHER) {
+ LogUtil.d(
+ "insertSources: Max limit of %s sources for publisher - %s reached.",
+ SystemHealthParams.MAX_SOURCES_PER_PUBLISHER, publisher);
+ return false;
+ }
+ if (source.getAppDestination() != null) {
+ Integer optionalAppDestinationCount =
+ countDistinctDestinationsPerPublisher(
+ publisher.get(),
+ publisherType,
+ source.getEnrollmentId(),
+ source.getAppDestination(),
+ EventSurfaceType.APP,
+ windowStartTime,
+ source.getEventTime(),
+ dao);
+ if (optionalAppDestinationCount != null) {
+ if (optionalAppDestinationCount >= PrivacyParams
+ .getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource()) {
+ return false;
+ }
+ } else {
+ LogUtil.e(
+ "isDestinationWithinPrivacyBounds:"
+ + " dao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource"
+ + " not present. %s ::: %s ::: %s ::: %s ::: %s",
+ source.getPublisher(),
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime());
+ return false;
+ }
+ Integer optionalAppEnrollmentsCount =
+ countDistinctEnrollmentsPerPublisher(
+ publisher.get(),
+ publisherType,
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime(),
+ dao);
+ if (optionalAppEnrollmentsCount != null) {
+ if (optionalAppEnrollmentsCount >= PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInSource()) {
+ return false;
+ }
+ } else {
+ LogUtil.e(
+ "isAdTechWithinPrivacyBounds: "
+ + "dao.countDistinctEnrollmentsPerPublisherXDestinationInSource"
+ + " not present"
+ + ". %s ::: %s ::: %s ::: %s ::: $s",
+ source.getPublisher(),
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime());
+ return false;
+ }
+ }
+ if (source.getWebDestination() != null) {
+ Integer optionalDestinationCountWeb =
+ countDistinctDestinationsPerPublisher(
+ publisher.get(),
+ publisherType,
+ source.getEnrollmentId(),
+ source.getWebDestination(),
+ EventSurfaceType.WEB,
+ windowStartTime,
+ source.getEventTime(),
+ dao);
+ if (optionalDestinationCountWeb != null) {
+ if (optionalDestinationCountWeb >= PrivacyParams
+ .getMaxDistinctDestinationsPerPublisherXEnrollmentInActiveSource()) {
+ return false;
+ }
+ } else {
+ LogUtil.e(
+ "isDestinationWithinPrivacyBounds:"
+ + " dao.countDistinctDestinationsPerPublisherXEnrollmentInActiveSource"
+ + " not present. %s ::: %s ::: %s ::: %s ::: %s",
+ source.getPublisher(),
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime());
+ return false;
+ }
+ Integer optionalWebEnrollmentsCount =
+ countDistinctEnrollmentsPerPublisher(
+ publisher.get(),
+ publisherType,
+ source.getWebDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime(),
+ dao);
+
+ if (optionalWebEnrollmentsCount != null) {
+ if (optionalWebEnrollmentsCount >= PrivacyParams
+ .getMaxDistinctEnrollmentsPerPublisherXDestinationInSource()) {
+ return false;
+ }
+ } else {
+ LogUtil.e(
+ "isAdTechWithinPrivacyBounds: "
+ + "dao.countDistinctEnrollmentsPerPublisherXDestinationInSource"
+ + " not present"
+ + ". %s ::: %s ::: %s ::: %s ::: $s",
+ source.getPublisher(),
+ source.getAppDestination(),
+ source.getEnrollmentId(),
+ windowStartTime,
+ source.getEventTime());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static AsyncRegistration createAsyncRegistrationRedirect(
+ String id,
+ String enrollmentId,
+ Uri registrationUri,
+ Uri webDestination,
+ Uri osDestination,
+ Uri registrant,
+ Uri verifiedDestination,
+ Uri topOrigin,
+ AsyncRegistration.RegistrationType registrationType,
+ Source.SourceType sourceType,
+ long requestTime,
+ long retryCount,
+ long lastProcessingTime,
+ @AsyncRegistration.RedirectType int redirectType,
+ int redirectCount,
+ boolean debugKeyAllowed) {
+ return new AsyncRegistration.Builder()
+ .setId(id)
+ .setEnrollmentId(enrollmentId)
+ .setRegistrationUri(registrationUri)
+ .setWebDestination(webDestination)
+ .setOsDestination(osDestination)
+ .setRegistrant(registrant)
+ .setVerifiedDestination(verifiedDestination)
+ .setTopOrigin(topOrigin)
+ .setType(registrationType.ordinal())
+ .setSourceType(
+ registrationType == AsyncRegistration.RegistrationType.APP_SOURCE
+ || registrationType
+ == AsyncRegistration.RegistrationType.WEB_SOURCE
+ ? sourceType
+ : null)
+ .setRequestTime(requestTime)
+ .setRetryCount(retryCount)
+ .setLastProcessingTime(lastProcessingTime)
+ .setRedirectType(redirectType)
+ .setRedirectCount(redirectCount)
+ .setDebugKeyAllowed(debugKeyAllowed)
+ .build();
+ }
+
+ private static void insertAsyncRegistrationFromTransaction(
+ AsyncRegistration asyncRegistration, IMeasurementDao dao) throws DatastoreException {
+ dao.insertAsyncRegistration(asyncRegistration);
+ }
+
+ @VisibleForTesting
+ List<EventReport> generateFakeEventReports(Source source) {
+ List<Source.FakeReport> fakeReports = source.assignAttributionModeAndGenerateFakeReports();
+ return fakeReports.stream()
+ .map(
+ fakeReport ->
+ new EventReport.Builder()
+ .setSourceEventId(source.getEventId())
+ .setReportTime(fakeReport.getReportingTime())
+ .setTriggerData(fakeReport.getTriggerData())
+ .setAttributionDestination(fakeReport.getDestination())
+ .setEnrollmentId(source.getEnrollmentId())
+ // The query for attribution check is from
+ // (triggerTime - 30 days) to triggerTime and max expiry is
+ // 30 days, so it's safe to choose triggerTime as source
+ // event time so that it gets considered when the query is
+ // fired for attribution rate limit check.
+ .setTriggerTime(source.getEventTime())
+ .setTriggerPriority(0L)
+ .setTriggerDedupKey(null)
+ .setSourceType(source.getSourceType())
+ .setStatus(EventReport.Status.PENDING)
+ .setRandomizedTriggerRate(
+ source.getRandomAttributionProbability())
+ .build())
+ .collect(Collectors.toList());
+ }
+
+
+ @VisibleForTesting
+ void insertSourcesFromTransaction(Source source, IMeasurementDao dao)
+ throws DatastoreException {
+ List<EventReport> er = generateFakeEventReports(source);
+ dao.insertSource(source);
+ for (EventReport report : er) {
+ dao.insertEventReport(report);
+ }
+ // We want to account for attribution if fake report generation was considered
+ // based on the probability. In that case the attribution mode will be NEVER
+ // (empty fake reports state) or FALSELY (non-empty fake reports).
+ if (source.getAttributionMode() != Source.AttributionMode.TRUTHFULLY) {
+ // Attribution rate limits for app and web destinations are counted
+ // separately, so add a fake report entry for each type of destination if
+ // non-null.
+ if (!Objects.isNull(source.getAppDestination())) {
+ dao.insertAttribution(
+ createFakeAttributionRateLimit(source, source.getAppDestination()));
+ }
+
+ if (!Objects.isNull(source.getWebDestination())) {
+ dao.insertAttribution(
+ createFakeAttributionRateLimit(source, source.getWebDestination()));
+ }
+ }
+ }
+
+ private void processRedirects(
+ AsyncRegistration asyncRegistration,
+ AsyncRedirect redirectsAndType,
+ Uri webDestination,
+ Uri osDestination,
+ IMeasurementDao dao)
+ throws DatastoreException {
+ for (Uri redirectUri : redirectsAndType.getRedirects()) {
+ Optional<String> enrollmentData =
+ Enrollment.maybeGetEnrollmentId(redirectUri, mEnrollmentDao);
+ if (enrollmentData == null || enrollmentData.isEmpty()) {
+ LogUtil.d(
+ "AsyncRegistrationQueueRunner: Invalid enrollment data while "
+ + "processing redirects");
+ return;
+ }
+ String enrollmentId = enrollmentData.get();
+ insertAsyncRegistrationFromTransaction(
+ createAsyncRegistrationRedirect(
+ UUID.randomUUID().toString(),
+ enrollmentId,
+ redirectUri,
+ webDestination,
+ osDestination,
+ asyncRegistration.getRegistrant(),
+ asyncRegistration.getVerifiedDestination(),
+ asyncRegistration.getTopOrigin(),
+ asyncRegistration.getType(),
+ asyncRegistration.getSourceType(),
+ asyncRegistration.getRequestTime(),
+ /* mRetryCount */ 0,
+ System.currentTimeMillis(),
+ redirectsAndType.getRedirectType(),
+ asyncRegistration.getNextRedirectCount(),
+ asyncRegistration.getDebugKeyAllowed()),
+ dao);
+ }
+ }
+
+ /**
+ * {@link Attribution} generated from here will only be used for fake report attribution.
+ *
+ * @param source source to derive parameters from
+ * @param destination destination for attribution
+ * @return a fake {@link Attribution}
+ */
+ private Attribution createFakeAttributionRateLimit(Source source, Uri destination) {
+ Optional<Uri> topLevelPublisher =
+ getTopLevelPublisher(source.getPublisher(), source.getPublisherType());
+
+ if (!topLevelPublisher.isPresent()) {
+ throw new IllegalArgumentException(
+ String.format(
+ "insertAttributionRateLimit: getSourceAndDestinationTopPrivateDomains"
+ + " failed. Publisher: %s; Attribution destination: %s",
+ source.getPublisher(), destination));
+ }
+
+ return new Attribution.Builder()
+ .setSourceSite(topLevelPublisher.get().toString())
+ .setSourceOrigin(source.getPublisher().toString())
+ .setDestinationSite(destination.toString())
+ .setDestinationOrigin(destination.toString())
+ .setEnrollmentId(source.getEnrollmentId())
+ .setTriggerTime(source.getEventTime())
+ .setRegistrant(source.getRegistrant().toString())
+ .setSourceId(source.getId())
+ // Intentionally kept it as null because it's a fake attribution
+ .setTriggerId(null)
+ .build();
+ }
+
+ private static Optional<Uri> getTopLevelPublisher(
+ Uri topOrigin, @EventSurfaceType int publisherType) {
+ return publisherType == EventSurfaceType.APP
+ ? Optional.of(topOrigin)
+ : Web.topPrivateDomainAndScheme(topOrigin);
+ }
+
+ private Uri getPublisher(AsyncRegistration request) {
+ return request.getRegistrant();
+ }
+
+ private void notifyTriggerContentProvider() {
+ try (ContentProviderClient contentProviderClient =
+ mContentResolver.acquireContentProviderClient(TRIGGER_URI)) {
+ if (contentProviderClient != null) {
+ contentProviderClient.insert(TRIGGER_URI, null);
+ }
+ } catch (RemoteException e) {
+ LogUtil.e(e, "Trigger Content Provider invocation failed.");
+ }
+ }
+}