summaryrefslogtreecommitdiff
path: root/adservices/service-core/java
diff options
context:
space:
mode:
authorAditya Gupta <adigupt@google.com>2023-08-24 12:59:56 +0000
committerAditya Gupta <adigupt@google.com>2023-08-30 10:01:41 +0000
commit59f4e3aace94eb88aab7eb40411917bdb4bb4e0e (patch)
treedceb7f5d41fdc68099f0b7468b6f81827b14e586 /adservices/service-core/java
parent2841c570b45b7dba5d722a9baccfef835c9028f2 (diff)
downloadAdServices-59f4e3aace94eb88aab7eb40411917bdb4bb4e0e.tar.gz
adding background job to send ad selection debug reports in a batch
Test: atest Bug: 293867930 Change-Id: Ifefed807cf988ffcd43f1c95fa3e536e671ffe35
Diffstat (limited to 'adservices/service-core/java')
-rw-r--r--adservices/service-core/java/com/android/adservices/service/Flags.java59
-rw-r--r--adservices/service-core/java/com/android/adservices/service/FlagsConstants.java12
-rw-r--r--adservices/service-core/java/com/android/adservices/service/FlagsFactory.java5
-rw-r--r--adservices/service-core/java/com/android/adservices/service/PhFlags.java95
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/DebugReportSenderJobService.java268
-rw-r--r--adservices/service-core/java/com/android/adservices/service/adselection/DebugReportSenderWorker.java212
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java10
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/FledgeMaintenanceTasksWorker.java14
-rw-r--r--adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java2
-rw-r--r--adservices/service-core/java/com/android/adservices/spe/AdservicesJobInfo.java3
10 files changed, 676 insertions, 4 deletions
diff --git a/adservices/service-core/java/com/android/adservices/service/Flags.java b/adservices/service-core/java/com/android/adservices/service/Flags.java
index 009cb47ab4..c4aa2cccbb 100644
--- a/adservices/service-core/java/com/android/adservices/service/Flags.java
+++ b/adservices/service-core/java/com/android/adservices/service/Flags.java
@@ -1083,6 +1083,15 @@ public interface Flags {
return FLEDGE_EVENT_LEVEL_DEBUG_REPORTING_ENABLED;
}
+ boolean FLEDGE_EVENT_LEVEL_DEBUG_REPORT_SEND_IMMEDIATELY = false;
+
+ /**
+ * @return whether to call remote URLs for debug reporting.
+ */
+ default boolean getFledgeEventLevelDebugReportSendImmediately() {
+ return FLEDGE_EVENT_LEVEL_DEBUG_REPORT_SEND_IMMEDIATELY;
+ }
+
int FLEDGE_EVENT_LEVEL_DEBUG_REPORTING_BATCH_DELAY_SECONDS = 60 * 15;
/**
@@ -1101,6 +1110,56 @@ public interface Flags {
return FLEDGE_EVENT_LEVEL_DEBUG_REPORTING_MAX_ITEMS_PER_BATCH;
}
+ int FLEDGE_DEBUG_REPORT_SENDER_JOB_NETWORK_CONNECT_TIMEOUT_MS = 5 * 1000; // 5 seconds
+
+ /**
+ * @return the maximum time in milliseconds allowed for a network call to open its initial
+ * connection during the FLEDGE debug report sender job.
+ */
+ default int getFledgeDebugReportSenderJobNetworkConnectionTimeoutMs() {
+ return FLEDGE_DEBUG_REPORT_SENDER_JOB_NETWORK_CONNECT_TIMEOUT_MS;
+ }
+
+ int FLEDGE_DEBUG_REPORT_SENDER_JOB_NETWORK_READ_TIMEOUT_MS = 30 * 1000; // 30 seconds
+
+ /**
+ * @return the maximum time in milliseconds allowed for a network call to read a response from a
+ * target server during the FLEDGE debug report sender job.
+ */
+ default int getFledgeDebugReportSenderJobNetworkReadTimeoutMs() {
+ return FLEDGE_DEBUG_REPORT_SENDER_JOB_NETWORK_READ_TIMEOUT_MS;
+ }
+
+ long FLEDGE_DEBUG_REPORT_SENDER_JOB_MAX_RUNTIME_MS = 10L * 60L * 1000L; // 5 minutes
+
+ /**
+ * @return the maximum amount of time (in milliseconds) each FLEDGE debug report sender job is
+ * allowed to run.
+ */
+ default long getFledgeDebugReportSenderJobMaxRuntimeMs() {
+ return FLEDGE_DEBUG_REPORT_SENDER_JOB_MAX_RUNTIME_MS;
+ }
+
+ long FLEDGE_DEBUG_REPORT_SENDER_JOB_PERIOD_MS = TimeUnit.MINUTES.toMillis(10);
+
+ /**
+ * @return the best effort max time (in milliseconds) between each FLEDGE debug report sender
+ * job run.
+ */
+ default long getFledgeDebugReportSenderJobPeriodMs() {
+ return FLEDGE_DEBUG_REPORT_SENDER_JOB_PERIOD_MS;
+ }
+
+ long FLEDGE_DEBUG_REPORT_SENDER_JOB_FLEX_MS = TimeUnit.MINUTES.toMillis(2);
+
+ /**
+ * @return the amount of flex (in milliseconds) around the end of each period to run each FLEDGE
+ * debug report sender job.
+ */
+ default long getFledgeDebugReportSenderJobFlexMs() {
+ return FLEDGE_DEBUG_REPORT_SENDER_JOB_FLEX_MS;
+ }
+
boolean FLEDGE_AD_SELECTION_OFF_DEVICE_REQUEST_COMPRESSION_ENABLED = true;
/** Returns whether to compress requests sent off device for ad selection. */
diff --git a/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java b/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java
index 28fdf3241e..d31b514f4f 100644
--- a/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java
+++ b/adservices/service-core/java/com/android/adservices/service/FlagsConstants.java
@@ -357,10 +357,22 @@ public final class FlagsConstants {
// Event-level debug reporting for Protected Audience.
public static final String KEY_FLEDGE_EVENT_LEVEL_DEBUG_REPORTING_ENABLED =
"fledge_event_level_debug_reporting_enabled";
+ public static final String KEY_FLEDGE_EVENT_LEVEL_DEBUG_REPORT_SEND_IMMEDIATELY =
+ "fledge_event_level_debug_report_send_immediately";
public static final String KEY_FLEDGE_EVENT_LEVEL_DEBUG_REPORTING_BATCH_DELAY_SECONDS =
"fledge_event_level_debug_reporting_batch_delay_seconds";
public static final String KEY_FLEDGE_EVENT_LEVEL_DEBUG_REPORTING_MAX_ITEMS_PER_BATCH =
"fledge_event_level_debug_reporting_max_items_per_batch";
+ public static final String KEY_FLEDGE_DEBUG_REPORTI_SENDER_JOB_NETWORK_CONNECT_TIMEOUT_MS =
+ "fledge_debug_report_sender_job_network_connect_timeout_ms";
+ public static final String KEY_FLEDGE_DEBUG_REPORTI_SENDER_JOB_NETWORK_READ_TIMEOUT_MS =
+ "fledge_debug_report_sender_job_network_read_timeout_ms";
+ public static final String KEY_FLEDGE_DEBUG_REPORTI_SENDER_JOB_MAX_TIMEOUT_MS =
+ "fledge_debug_report_sender_job_max_timeout_ms";
+ public static final String KEY_FLEDGE_DEBUG_REPORT_SENDER_JOB_PERIOD_MS =
+ "fledge_debug_report_sender_job_period_ms";
+ public static final String KEY_FLEDGE_DEBUG_REPORT_SENDER_JOB_FLEX_MS =
+ "fledge_debug_report_sender_job_flex_ms";
// Server-auction flags for Protected Audience.
public static final String KEY_FLEDGE_AUCTION_SERVER_ENABLED = "fledge_auction_server_enabled";
diff --git a/adservices/service-core/java/com/android/adservices/service/FlagsFactory.java b/adservices/service-core/java/com/android/adservices/service/FlagsFactory.java
index 4ed6c1dc1b..03a159708c 100644
--- a/adservices/service-core/java/com/android/adservices/service/FlagsFactory.java
+++ b/adservices/service-core/java/com/android/adservices/service/FlagsFactory.java
@@ -89,6 +89,11 @@ public class FlagsFactory {
public boolean getFledgeAuctionServerEnabled() {
return true;
}
+
+ @Override
+ public boolean getFledgeEventLevelDebugReportingEnabled() {
+ return true;
+ }
};
}
}
diff --git a/adservices/service-core/java/com/android/adservices/service/PhFlags.java b/adservices/service-core/java/com/android/adservices/service/PhFlags.java
index 07a74688d5..dff746a2c9 100644
--- a/adservices/service-core/java/com/android/adservices/service/PhFlags.java
+++ b/adservices/service-core/java/com/android/adservices/service/PhFlags.java
@@ -48,7 +48,6 @@ import java.util.stream.Collectors;
/** Flags Implementation that delegates to DeviceConfig. */
// TODO(b/228037065): Add validation logics for Feature flags read from PH.
public final class PhFlags implements Flags {
-
private static final PhFlags sSingleton = new PhFlags();
/** Returns the singleton instance of the PhFlags. */
@@ -2283,6 +2282,14 @@ public final class PhFlags implements Flags {
}
@Override
+ public boolean getFledgeEventLevelDebugReportSendImmediately() {
+ return DeviceConfig.getBoolean(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* flagName */ FlagsConstants.KEY_FLEDGE_EVENT_LEVEL_DEBUG_REPORT_SEND_IMMEDIATELY,
+ /* defaultValue */ FLEDGE_EVENT_LEVEL_DEBUG_REPORT_SEND_IMMEDIATELY);
+ }
+
+ @Override
public int getFledgeEventLevelDebugReportingBatchDelaySeconds() {
return DeviceConfig.getInt(
FlagsConstants.NAMESPACE_ADSERVICES,
@@ -2303,6 +2310,53 @@ public final class PhFlags implements Flags {
}
@Override
+ public int getFledgeDebugReportSenderJobNetworkConnectionTimeoutMs() {
+ return DeviceConfig.getInt(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* flagName */ FlagsConstants
+ .KEY_FLEDGE_DEBUG_REPORTI_SENDER_JOB_NETWORK_CONNECT_TIMEOUT_MS,
+ /* defaultValue */
+ FLEDGE_DEBUG_REPORT_SENDER_JOB_NETWORK_CONNECT_TIMEOUT_MS);
+ }
+
+ @Override
+ public int getFledgeDebugReportSenderJobNetworkReadTimeoutMs() {
+ return DeviceConfig.getInt(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* flagName */ FlagsConstants
+ .KEY_FLEDGE_DEBUG_REPORTI_SENDER_JOB_NETWORK_READ_TIMEOUT_MS,
+ /* defaultValue */
+ FLEDGE_DEBUG_REPORT_SENDER_JOB_NETWORK_READ_TIMEOUT_MS);
+ }
+
+ @Override
+ public long getFledgeDebugReportSenderJobMaxRuntimeMs() {
+ return DeviceConfig.getLong(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* flagName */ FlagsConstants.KEY_FLEDGE_DEBUG_REPORTI_SENDER_JOB_MAX_TIMEOUT_MS,
+ /* defaultValue */
+ FLEDGE_DEBUG_REPORT_SENDER_JOB_MAX_RUNTIME_MS);
+ }
+
+ @Override
+ public long getFledgeDebugReportSenderJobPeriodMs() {
+ return DeviceConfig.getLong(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* flagName */ FlagsConstants.KEY_FLEDGE_DEBUG_REPORT_SENDER_JOB_PERIOD_MS,
+ /* defaultValue */
+ FLEDGE_DEBUG_REPORT_SENDER_JOB_PERIOD_MS);
+ }
+
+ @Override
+ public long getFledgeDebugReportSenderJobFlexMs() {
+ return DeviceConfig.getLong(
+ FlagsConstants.NAMESPACE_ADSERVICES,
+ /* flagName */ FlagsConstants.KEY_FLEDGE_DEBUG_REPORT_SENDER_JOB_FLEX_MS,
+ /* defaultValue */
+ FLEDGE_DEBUG_REPORT_SENDER_JOB_FLEX_MS);
+ }
+
+ @Override
public boolean getEnforceForegroundStatusForFledgeRunAdSelection() {
return DeviceConfig.getBoolean(
FlagsConstants.NAMESPACE_ADSERVICES,
@@ -4017,6 +4071,12 @@ public final class PhFlags implements Flags {
writer.println(
"\t"
+ + FlagsConstants.KEY_FLEDGE_EVENT_LEVEL_DEBUG_REPORT_SEND_IMMEDIATELY
+ + " = "
+ + getFledgeEventLevelDebugReportSendImmediately());
+
+ writer.println(
+ "\t"
+ FlagsConstants.KEY_FLEDGE_EVENT_LEVEL_DEBUG_REPORTING_BATCH_DELAY_SECONDS
+ " = "
+ getFledgeEventLevelDebugReportingBatchDelaySeconds());
@@ -4025,11 +4085,44 @@ public final class PhFlags implements Flags {
+ FlagsConstants.KEY_FLEDGE_EVENT_LEVEL_DEBUG_REPORTING_MAX_ITEMS_PER_BATCH
+ " = "
+ getFledgeEventLevelDebugReportingMaxItemsPerBatch());
+
+ writer.println(
+ "\t"
+ + FlagsConstants
+ .KEY_FLEDGE_DEBUG_REPORTI_SENDER_JOB_NETWORK_CONNECT_TIMEOUT_MS
+ + " = "
+ + getFledgeDebugReportSenderJobNetworkConnectionTimeoutMs());
+
+ writer.println(
+ "\t"
+ + FlagsConstants.KEY_FLEDGE_DEBUG_REPORTI_SENDER_JOB_NETWORK_READ_TIMEOUT_MS
+ + " = "
+ + getFledgeDebugReportSenderJobNetworkReadTimeoutMs());
+
+ writer.println(
+ "\t"
+ + FlagsConstants.KEY_FLEDGE_DEBUG_REPORTI_SENDER_JOB_MAX_TIMEOUT_MS
+ + " = "
+ + getFledgeDebugReportSenderJobMaxRuntimeMs());
+
+ writer.println(
+ "\t"
+ + FlagsConstants.KEY_FLEDGE_DEBUG_REPORT_SENDER_JOB_PERIOD_MS
+ + " = "
+ + getFledgeDebugReportSenderJobPeriodMs());
+
+ writer.println(
+ "\t"
+ + FlagsConstants.KEY_FLEDGE_DEBUG_REPORT_SENDER_JOB_FLEX_MS
+ + " = "
+ + getFledgeDebugReportSenderJobFlexMs());
+
writer.println(
"\t"
+ FlagsConstants.KEY_PROTECTED_SIGNALS_SERVICE_KILL_SWITCH
+ " = "
+ getProtectedSignalsServiceKillSwitch());
+
writer.println("==== AdServices PH Flags Throttling Related Flags ====");
writer.println(
"\t"
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/DebugReportSenderJobService.java b/adservices/service-core/java/com/android/adservices/service/adselection/DebugReportSenderJobService.java
new file mode 100644
index 0000000000..844a9f04dc
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/DebugReportSenderJobService.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2023 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.adselection;
+
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_EXTSERVICES_JOB_ON_TPLUS;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
+import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_USER_CONSENT_REVOKED;
+import static com.android.adservices.spe.AdservicesJobInfo.FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB;
+
+import android.annotation.SuppressLint;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.adservices.LogUtil;
+import com.android.adservices.LoggerFactory;
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.compat.ServiceCompatUtils;
+import com.android.adservices.service.consent.AdServicesApiType;
+import com.android.adservices.service.consent.ConsentManager;
+import com.android.adservices.spe.AdservicesJobServiceLogger;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.util.concurrent.FutureCallback;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Debug report sender for FLEDGE Select Ads API, executing periodic pinging of debug reports and
+ * cleanup..
+ */
+// TODO(b/269798827): Enable for R.
+@SuppressLint("LineLength")
+@RequiresApi(Build.VERSION_CODES.S)
+public class DebugReportSenderJobService extends JobService {
+ private static final int FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID =
+ FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB.getJobId();
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ // Always ensure that the first thing this job does is check if it should be running, and
+ // cancel itself if it's not supposed to be.
+ if (ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(this)) {
+ LogUtil.d(
+ "Disabling DebugReportSenderJobService job because it's running in "
+ + " ExtServices on T+");
+ return skipAndCancelKeyFetchJob(
+ params,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_EXTSERVICES_JOB_ON_TPLUS,
+ false);
+ }
+ LoggerFactory.getFledgeLogger().d("DebugReportSenderJobService.onStartJob");
+
+ AdservicesJobServiceLogger.getInstance(this)
+ .recordOnStartJob(FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID);
+
+ if (FlagsFactory.getFlags().getFledgeSelectAdsKillSwitch()) {
+ LoggerFactory.getFledgeLogger()
+ .d("FLEDGE Ad Selection API is disabled ; skipping and cancelling job");
+ return skipAndCancelKeyFetchJob(
+ params,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON,
+ true);
+ }
+
+ if (!FlagsFactory.getFlags().getFledgeEventLevelDebugReportingEnabled()) {
+ LoggerFactory.getFledgeLogger()
+ .d(
+ "FLEDGE Ad Selection Debug Reporting is disabled ; skipping and"
+ + " cancelling job");
+ return skipAndCancelKeyFetchJob(
+ params,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON,
+ true);
+ }
+
+ // Skip the execution and cancel the job if user consent is revoked. Use the aggregated
+ // consent with Beta UX and use the per-API consent with GA UX.
+ if ((FlagsFactory.getFlags().getGaUxFeatureEnabled()
+ && (!ConsentManager.getInstance(this)
+ .getConsent(AdServicesApiType.FLEDGE)
+ .isGiven()))
+ || (!FlagsFactory.getFlags().getGaUxFeatureEnabled()
+ && !ConsentManager.getInstance(this).getConsent().isGiven())) {
+ LoggerFactory.getFledgeLogger()
+ .d("User Consent is revoked ; skipping and cancelling job");
+ return skipAndCancelKeyFetchJob(
+ params,
+ AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_USER_CONSENT_REVOKED,
+ true);
+ }
+
+ // TODO(b/235841960): Consider using com.android.adservices.service.stats.Clock instead of
+ // Java Clock
+ Instant jobStartTime = Clock.systemUTC().instant();
+ LoggerFactory.getFledgeLogger()
+ .d(
+ "Starting FLEDGE DebugReportSenderJobService job at %s",
+ jobStartTime.toString());
+
+ DebugReportSenderWorker.getInstance(this)
+ .runDebugReportSender()
+ .addCallback(
+ new FutureCallback<Void>() {
+ // Never manually reschedule the background fetch job, since it is
+ // already scheduled periodically and should try again multiple times
+ // per day
+ @Override
+ public void onSuccess(Void result) {
+ boolean shouldRetry = false;
+ AdservicesJobServiceLogger.getInstance(
+ DebugReportSenderJobService.this)
+ .recordJobFinished(
+ FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID,
+ /* isSuccessful= */ true,
+ shouldRetry);
+
+ jobFinished(params, shouldRetry);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logExceptionMessage(t);
+ boolean shouldRetry = false;
+ AdservicesJobServiceLogger.getInstance(
+ DebugReportSenderJobService.this)
+ .recordJobFinished(
+ FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID,
+ /* isSuccessful= */ false,
+ shouldRetry);
+
+ jobFinished(params, shouldRetry);
+ }
+ },
+ AdServicesExecutors.getLightWeightExecutor());
+ return true;
+ }
+
+ private void logExceptionMessage(Throwable t) {
+ if (t instanceof InterruptedException) {
+ LoggerFactory.getFledgeLogger()
+ .e(t, "FLEDGE DebugReport Sender JobService interrupted");
+ } else if (t instanceof ExecutionException) {
+ LoggerFactory.getFledgeLogger()
+ .e(t, "FLEDGE DebugReport Sender JobService failed due to internal error");
+ } else if (t instanceof TimeoutException) {
+ LoggerFactory.getFledgeLogger()
+ .e(t, "FLEDGE DebugReport Sender JobService timeout exceeded");
+ } else {
+ LoggerFactory.getFledgeLogger()
+ .e(t, "FLEDGE DebugReport Sender JobService failed due to unexpected error");
+ }
+ }
+
+ private boolean skipAndCancelKeyFetchJob(
+ final JobParameters params, int skipReason, boolean doRecord) {
+ this.getSystemService(JobScheduler.class)
+ .cancel(FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID);
+ if (doRecord) {
+ AdservicesJobServiceLogger.getInstance(this)
+ .recordJobSkipped(FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID, skipReason);
+ }
+ jobFinished(params, false);
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ LoggerFactory.getFledgeLogger().d("DebugReportSenderJobService.onStopJob");
+ DebugReportSenderWorker.getInstance(this).stopWork();
+
+ boolean shouldRetry = true;
+
+ AdservicesJobServiceLogger.getInstance(this)
+ .recordOnStopJob(
+ params, FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID, shouldRetry);
+ return shouldRetry;
+ }
+
+ /**
+ * Attempts to schedule the FLEDGE Ad Selection debug report sender as a singleton periodic job
+ * if it is not already scheduled.
+ *
+ * <p>The debug report sender job primarily sends debug reports generated for ad selections. It
+ * also prunes the ad selection debug report database of any expired data.
+ */
+ public static void scheduleIfNeeded(Context context, boolean forceSchedule) {
+ Flags flags = FlagsFactory.getFlags();
+ if (!flags.getFledgeEventLevelDebugReportingEnabled()) {
+ LoggerFactory.getFledgeLogger()
+ .v("FLEDGE Ad selection Debug reporting is disabled; skipping schedule");
+ return;
+ }
+
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+
+ // Scheduling a job can be expensive, and forcing a schedule could interrupt a job that is
+ // already in progress
+ // TODO(b/221837833): Intelligently decide when to overwrite a scheduled job
+ if ((jobScheduler.getPendingJob(FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID) == null)
+ || forceSchedule) {
+ schedule(context, flags);
+ LoggerFactory.getFledgeLogger()
+ .d("Scheduled FLEDGE ad selection Debug report sender job");
+ } else {
+ LoggerFactory.getFledgeLogger()
+ .v(
+ "FLEDGE ad selection Debug report sender job already scheduled,"
+ + " skipping reschedule");
+ }
+ }
+
+ /**
+ * Actually schedules the FLEDGE Ad Selection Debug Report sender job as a singleton periodic
+ * job.
+ *
+ * <p>Split out from {@link #scheduleIfNeeded(Context, boolean)} for mockable testing without
+ * pesky permissions.
+ */
+ @VisibleForTesting
+ protected static void schedule(Context context, Flags flags) {
+ if (!flags.getFledgeEventLevelDebugReportingEnabled()) {
+ LoggerFactory.getFledgeLogger()
+ .v("FLEDGE Ad selection Debug reporting is disabled; skipping schedule");
+ return;
+ }
+
+ final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ final JobInfo job =
+ new JobInfo.Builder(
+ FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID,
+ new ComponentName(context, DebugReportSenderJobService.class))
+ .setRequiresBatteryNotLow(true)
+ .setRequiresDeviceIdle(true)
+ .setPeriodic(
+ flags.getFledgeDebugReportSenderJobPeriodMs(),
+ flags.getFledgeDebugReportSenderJobFlexMs())
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+ .setPersisted(true)
+ .build();
+ jobScheduler.schedule(job);
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/adselection/DebugReportSenderWorker.java b/adservices/service-core/java/com/android/adservices/service/adselection/DebugReportSenderWorker.java
new file mode 100644
index 0000000000..c6f15538cc
--- /dev/null
+++ b/adservices/service-core/java/com/android/adservices/service/adselection/DebugReportSenderWorker.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2023 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.adselection;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.adservices.LoggerFactory;
+import com.android.adservices.concurrency.AdServicesExecutors;
+import com.android.adservices.data.adselection.AdSelectionDebugReportDao;
+import com.android.adservices.data.adselection.AdSelectionDebugReportingDatabase;
+import com.android.adservices.data.adselection.DBAdSelectionDebugReport;
+import com.android.adservices.service.Flags;
+import com.android.adservices.service.FlagsFactory;
+import com.android.adservices.service.common.SingletonRunner;
+import com.android.adservices.service.common.httpclient.AdServicesHttpsClient;
+import com.android.adservices.service.devapi.DevContext;
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.FluentFuture;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/** Worker class to send and clean debug reports generated for ad selection. */
+public class DebugReportSenderWorker {
+ private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
+ public static final String JOB_DESCRIPTION = "Ad selection debug report sender job";
+ private static final Object SINGLETON_LOCK = new Object();
+ private static volatile DebugReportSenderWorker sDebugReportSenderWorker;
+ @NonNull private final AdSelectionDebugReportDao mAdSelectionDebugReportDao;
+ @NonNull private final AdServicesHttpsClient mAdServicesHttpsClient;
+ @NonNull private final Flags mFlags;
+ @NonNull private final Clock mClock;
+ private final SingletonRunner<Void> mSingletonRunner =
+ new SingletonRunner<>(JOB_DESCRIPTION, this::doRun);
+
+ @VisibleForTesting
+ protected DebugReportSenderWorker(
+ @NonNull AdSelectionDebugReportDao adSelectionDebugReportDao,
+ @NonNull AdServicesHttpsClient adServicesHttpsClient,
+ @NonNull Flags flags,
+ @NonNull Clock clock) {
+ Objects.requireNonNull(adSelectionDebugReportDao);
+ Objects.requireNonNull(adServicesHttpsClient);
+ Objects.requireNonNull(flags);
+ Objects.requireNonNull(clock);
+
+ mAdSelectionDebugReportDao = adSelectionDebugReportDao;
+ mAdServicesHttpsClient = adServicesHttpsClient;
+ mClock = clock;
+ mFlags = flags;
+ }
+
+ /**
+ * Gets an instance of a {@link DebugReportSenderWorker}. If an instance hasn't been
+ * initialized, a new singleton will be created and returned.
+ */
+ @NonNull
+ public static DebugReportSenderWorker getInstance(@NonNull Context context) {
+ Objects.requireNonNull(context);
+
+ if (sDebugReportSenderWorker == null) {
+ synchronized (SINGLETON_LOCK) {
+ if (sDebugReportSenderWorker == null) {
+ AdSelectionDebugReportDao adSelectionDebugReportDao =
+ AdSelectionDebugReportingDatabase.getInstance(context)
+ .getAdSelectionDebugReportDao();
+ Flags flags = FlagsFactory.getFlags();
+ AdServicesHttpsClient adServicesHttpsClient =
+ new AdServicesHttpsClient(
+ AdServicesExecutors.getBlockingExecutor(),
+ flags.getFledgeDebugReportSenderJobNetworkConnectionTimeoutMs(),
+ flags.getFledgeDebugReportSenderJobNetworkReadTimeoutMs(),
+ AdServicesHttpsClient.DEFAULT_MAX_BYTES);
+ sDebugReportSenderWorker =
+ new DebugReportSenderWorker(
+ adSelectionDebugReportDao,
+ adServicesHttpsClient,
+ flags,
+ Clock.systemUTC());
+ }
+ }
+ }
+ return sDebugReportSenderWorker;
+ }
+
+ /**
+ * Runs the debug report sender job for Ad Selection Debug Reports.
+ *
+ * @return A future to be used to check when the task has completed.
+ */
+ public FluentFuture<Void> runDebugReportSender() {
+ sLogger.d("Starting %s", JOB_DESCRIPTION);
+ return mSingletonRunner.runSingleInstance();
+ }
+
+ /** Requests that any ongoing work be stopped gracefully and waits for work to be stopped. */
+ public void stopWork() {
+ mSingletonRunner.stopWork();
+ }
+
+ private FluentFuture<List<DBAdSelectionDebugReport>> getDebugReports(
+ @NonNull Supplier<Boolean> shouldStop, @NonNull Instant jobStartTime) {
+ if (shouldStop.get()) {
+ sLogger.d("Stopping " + JOB_DESCRIPTION);
+ return FluentFuture.from(Futures.immediateFuture(ImmutableList.of()));
+ }
+ int batchSizeForDebugReports = mFlags.getFledgeEventLevelDebugReportingMaxItemsPerBatch();
+ sLogger.v("Getting %d debug reports from database", batchSizeForDebugReports);
+ return FluentFuture.from(
+ AdServicesExecutors.getBackgroundExecutor()
+ .submit(
+ () -> {
+ List<DBAdSelectionDebugReport> debugReports =
+ mAdSelectionDebugReportDao.getDebugReportsBeforeTime(
+ jobStartTime, batchSizeForDebugReports);
+ if (debugReports == null) {
+ sLogger.v("no debug reports to send");
+ return Collections.emptyList();
+ }
+ sLogger.v(
+ "found %d debug reports from database",
+ debugReports.size());
+ return debugReports;
+ }));
+ }
+
+ private FluentFuture<Void> cleanupDebugReportsData(Instant jobStartTime) {
+ sLogger.v(
+ "cleaning up old debug reports from the database at time %s",
+ jobStartTime.toString());
+ return FluentFuture.from(
+ AdServicesExecutors.getBackgroundExecutor()
+ .submit(
+ () -> {
+ mAdSelectionDebugReportDao.deleteDebugReportsBeforeTime(
+ jobStartTime);
+ return null;
+ }));
+ }
+
+ private ListenableFuture<Void> sendDebugReports(
+ @NonNull List<DBAdSelectionDebugReport> dbAdSelectionDebugReports) {
+
+ if (dbAdSelectionDebugReports.isEmpty()) {
+ sLogger.d("No debug reports found to send");
+ return FluentFuture.from(Futures.immediateVoidFuture());
+ }
+
+ sLogger.d("Sending %d debug reports", dbAdSelectionDebugReports.size());
+ List<ListenableFuture<Void>> futures =
+ dbAdSelectionDebugReports.stream()
+ .map(this::sendDebugReport)
+ .collect(Collectors.toList());
+ return Futures.whenAllComplete(futures)
+ .call(() -> null, AdServicesExecutors.getBlockingExecutor());
+ }
+
+ private ListenableFuture<Void> sendDebugReport(
+ DBAdSelectionDebugReport dbAdSelectionDebugReport) {
+ Uri debugReportUri = dbAdSelectionDebugReport.getDebugReportUri();
+ DevContext devContext =
+ DevContext.builder()
+ .setDevOptionsEnabled(dbAdSelectionDebugReport.getDevOptionsEnabled())
+ .build();
+ sLogger.v("Sending debug report %s", debugReportUri.toString());
+ try {
+ return mAdServicesHttpsClient.getAndReadNothing(debugReportUri, devContext);
+ } catch (Exception ignored) {
+ sLogger.v("Failed to send debug report %s", debugReportUri.toString());
+ return Futures.immediateVoidFuture();
+ }
+ }
+
+ private FluentFuture<Void> doRun(@NonNull Supplier<Boolean> shouldStop) {
+ Instant jobStartTime = mClock.instant();
+ return getDebugReports(shouldStop, jobStartTime)
+ .transform(this::sendDebugReports, AdServicesExecutors.getBackgroundExecutor())
+ .transformAsync(
+ ignored -> cleanupDebugReportsData(jobStartTime),
+ AdServicesExecutors.getBackgroundExecutor())
+ .withTimeout(
+ mFlags.getFledgeDebugReportSenderJobMaxRuntimeMs(),
+ TimeUnit.MILLISECONDS,
+ AdServicesExecutors.getScheduler());
+ }
+}
diff --git a/adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java b/adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java
index 671ab81de4..25e492e1c1 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/BackgroundJobsManager.java
@@ -18,6 +18,7 @@ package com.android.adservices.service.common;
import static com.android.adservices.spe.AdservicesJobInfo.COBALT_LOGGING_JOB;
import static com.android.adservices.spe.AdservicesJobInfo.CONSENT_NOTIFICATION_JOB;
+import static com.android.adservices.spe.AdservicesJobInfo.FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB;
import static com.android.adservices.spe.AdservicesJobInfo.FLEDGE_BACKGROUND_FETCH_JOB;
import static com.android.adservices.spe.AdservicesJobInfo.MAINTENANCE_JOB;
import static com.android.adservices.spe.AdservicesJobInfo.MEASUREMENT_AGGREGATE_FALLBACK_REPORTING_JOB;
@@ -43,8 +44,10 @@ import androidx.annotation.RequiresApi;
import com.android.adservices.cobalt.CobaltJobService;
import com.android.adservices.download.MddJobService;
+import com.android.adservices.service.Flags;
import com.android.adservices.service.FlagsFactory;
import com.android.adservices.service.MaintenanceJobService;
+import com.android.adservices.service.adselection.DebugReportSenderJobService;
import com.android.adservices.service.consent.AdServicesApiType;
import com.android.adservices.service.measurement.DeleteExpiredJobService;
import com.android.adservices.service.measurement.DeleteUninstalledJobService;
@@ -130,8 +133,12 @@ public class BackgroundJobsManager {
* @param context application context.
*/
public static void scheduleFledgeBackgroundJobs(@NonNull Context context) {
- if (!FlagsFactory.getFlags().getFledgeSelectAdsKillSwitch()) {
+ Flags flags = FlagsFactory.getFlags();
+ if (!flags.getFledgeSelectAdsKillSwitch()) {
MaintenanceJobService.scheduleIfNeeded(context, /* forceSchedule= */ false);
+ if (flags.getFledgeEventLevelDebugReportingEnabled()) {
+ DebugReportSenderJobService.scheduleIfNeeded(context, false);
+ }
}
}
@@ -287,6 +294,7 @@ public class BackgroundJobsManager {
Objects.requireNonNull(jobScheduler);
jobScheduler.cancel(FLEDGE_BACKGROUND_FETCH_JOB.getJobId());
+ jobScheduler.cancel(FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB.getJobId());
}
/**
diff --git a/adservices/service-core/java/com/android/adservices/service/common/FledgeMaintenanceTasksWorker.java b/adservices/service-core/java/com/android/adservices/service/common/FledgeMaintenanceTasksWorker.java
index 19643e8bd0..a24d8bffcc 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/FledgeMaintenanceTasksWorker.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/FledgeMaintenanceTasksWorker.java
@@ -23,6 +23,8 @@ import android.content.pm.PackageManager;
import com.android.adservices.LoggerFactory;
import com.android.adservices.data.adselection.AdSelectionDatabase;
+import com.android.adservices.data.adselection.AdSelectionDebugReportDao;
+import com.android.adservices.data.adselection.AdSelectionDebugReportingDatabase;
import com.android.adservices.data.adselection.AdSelectionEntryDao;
import com.android.adservices.data.adselection.AdSelectionServerDatabase;
import com.android.adservices.data.adselection.EncryptionContextDao;
@@ -41,6 +43,7 @@ import java.util.Objects;
public class FledgeMaintenanceTasksWorker {
private static final LoggerFactory.Logger sLogger = LoggerFactory.getFledgeLogger();
@NonNull private final AdSelectionEntryDao mAdSelectionEntryDao;
+ @NonNull private final AdSelectionDebugReportDao mAdSelectionDebugReportDao;
@NonNull private final FrequencyCapDao mFrequencyCapDao;
@NonNull private final EnrollmentDao mEnrollmentDao;
@NonNull private final EncryptionContextDao mEncryptionContextDao;
@@ -54,6 +57,7 @@ public class FledgeMaintenanceTasksWorker {
@NonNull FrequencyCapDao frequencyCapDao,
@NonNull EnrollmentDao enrollmentDao,
@NonNull EncryptionContextDao encryptionContextDao,
+ @NonNull AdSelectionDebugReportDao adSelectionDebugReportDao,
@NonNull Clock clock) {
Objects.requireNonNull(flags);
Objects.requireNonNull(adSelectionEntryDao);
@@ -61,6 +65,7 @@ public class FledgeMaintenanceTasksWorker {
Objects.requireNonNull(enrollmentDao);
Objects.requireNonNull(clock);
Objects.requireNonNull(encryptionContextDao);
+ Objects.requireNonNull(adSelectionDebugReportDao);
mFlags = flags;
mAdSelectionEntryDao = adSelectionEntryDao;
@@ -68,6 +73,7 @@ public class FledgeMaintenanceTasksWorker {
mEnrollmentDao = enrollmentDao;
mEncryptionContextDao = encryptionContextDao;
mClock = clock;
+ mAdSelectionDebugReportDao = adSelectionDebugReportDao;
}
private FledgeMaintenanceTasksWorker(@NonNull Context context) {
@@ -79,6 +85,9 @@ public class FledgeMaintenanceTasksWorker {
mEncryptionContextDao =
AdSelectionServerDatabase.getInstance(context).encryptionContextDao();
mClock = Clock.systemUTC();
+ mAdSelectionDebugReportDao =
+ AdSelectionDebugReportingDatabase.getInstance(context)
+ .getAdSelectionDebugReportDao();
}
/** Creates a new instance of {@link FledgeMaintenanceTasksWorker}. */
@@ -114,6 +123,11 @@ public class FledgeMaintenanceTasksWorker {
sLogger.v("Clearing expired Encryption Context");
mEncryptionContextDao.removeExpiredEncryptionContext(expirationTime);
}
+
+ if (mFlags.getFledgeEventLevelDebugReportingEnabled()) {
+ sLogger.v("Clearing expired debug reports ");
+ mAdSelectionDebugReportDao.deleteDebugReportsBeforeTime(expirationTime);
+ }
}
/**
diff --git a/adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java b/adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java
index 61d927d1bb..ef60d31b93 100644
--- a/adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java
+++ b/adservices/service-core/java/com/android/adservices/service/common/httpclient/AdServicesHttpsClient.java
@@ -79,9 +79,9 @@ import javax.net.ssl.X509TrustManager;
*/
public class AdServicesHttpsClient {
+ public static final long DEFAULT_MAX_BYTES = 1048576;
private static final int DEFAULT_TIMEOUT_MS = 5000;
// Setting default max content size to 1024 * 1024 which is ~ 1MB
- private static final long DEFAULT_MAX_BYTES = 1048576;
private static final String CONTENT_SIZE_ERROR = "Content size exceeds limit!";
private static final String RETRY_AFTER_HEADER_FIELD = "Retry-After";
private final int mConnectTimeoutMs;
diff --git a/adservices/service-core/java/com/android/adservices/spe/AdservicesJobInfo.java b/adservices/service-core/java/com/android/adservices/spe/AdservicesJobInfo.java
index 19575dc7db..465a41bdcf 100644
--- a/adservices/service-core/java/com/android/adservices/spe/AdservicesJobInfo.java
+++ b/adservices/service-core/java/com/android/adservices/spe/AdservicesJobInfo.java
@@ -72,7 +72,8 @@ public enum AdservicesJobInfo {
MEASUREMENT_VERBOSE_DEBUG_REPORTING_FALLBACK_JOB(
"MEASUREMENT_VERBOSE_DEBUG_REPORTING_FALLBACK_JOB", 24),
- MEASUREMENT_DEBUG_REPORTING_FALLBACK_JOB("MEASUREMENT_DEBUG_REPORTING_FALLBACK_JOB", 25);
+ MEASUREMENT_DEBUG_REPORTING_FALLBACK_JOB("MEASUREMENT_DEBUG_REPORTING_FALLBACK_JOB", 25),
+ FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB("FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB", 26);
private final String mJobServiceName;
private final int mJobId;