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.adselection; 18 19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_EXTSERVICES_JOB_ON_TPLUS; 20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON; 21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_USER_CONSENT_REVOKED; 22 import static com.android.adservices.spe.AdServicesJobInfo.FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB; 23 24 import android.annotation.SuppressLint; 25 import android.app.job.JobInfo; 26 import android.app.job.JobParameters; 27 import android.app.job.JobScheduler; 28 import android.app.job.JobService; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.os.Build; 32 33 import androidx.annotation.RequiresApi; 34 35 import com.android.adservices.LogUtil; 36 import com.android.adservices.LoggerFactory; 37 import com.android.adservices.concurrency.AdServicesExecutors; 38 import com.android.adservices.service.Flags; 39 import com.android.adservices.service.FlagsFactory; 40 import com.android.adservices.service.common.compat.ServiceCompatUtils; 41 import com.android.adservices.service.consent.AdServicesApiType; 42 import com.android.adservices.service.consent.ConsentManager; 43 import com.android.adservices.spe.AdServicesJobServiceLogger; 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import com.google.common.util.concurrent.FutureCallback; 47 48 import java.time.Clock; 49 import java.time.Instant; 50 import java.util.concurrent.ExecutionException; 51 import java.util.concurrent.TimeoutException; 52 53 /** 54 * Debug report sender for FLEDGE Select Ads API, executing periodic pinging of debug reports and 55 * cleanup.. 56 */ 57 @SuppressLint("LineLength") 58 @RequiresApi(Build.VERSION_CODES.S) 59 public class DebugReportSenderJobService extends JobService { 60 private static final int FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID = 61 FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB.getJobId(); 62 63 @Override onStartJob(JobParameters params)64 public boolean onStartJob(JobParameters params) { 65 // Always ensure that the first thing this job does is check if it should be running, and 66 // cancel itself if it's not supposed to be. 67 if (ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(this)) { 68 LogUtil.d( 69 "Disabling DebugReportSenderJobService job because it's running in " 70 + " ExtServices on T+"); 71 return skipAndCancelKeyFetchJob( 72 params, 73 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_EXTSERVICES_JOB_ON_TPLUS, 74 false); 75 } 76 LoggerFactory.getFledgeLogger().d("DebugReportSenderJobService.onStartJob"); 77 78 AdServicesJobServiceLogger.getInstance() 79 .recordOnStartJob(FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID); 80 81 if (FlagsFactory.getFlags().getFledgeSelectAdsKillSwitch()) { 82 LoggerFactory.getFledgeLogger() 83 .d("FLEDGE Ad Selection API is disabled ; skipping and cancelling job"); 84 return skipAndCancelKeyFetchJob( 85 params, 86 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON, 87 true); 88 } 89 90 if (!FlagsFactory.getFlags().getFledgeEventLevelDebugReportingEnabled()) { 91 LoggerFactory.getFledgeLogger() 92 .d( 93 "FLEDGE Ad Selection Debug Reporting is disabled ; skipping and" 94 + " cancelling job"); 95 return skipAndCancelKeyFetchJob( 96 params, 97 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON, 98 true); 99 } 100 101 // Skip the execution and cancel the job if user consent is revoked. 102 // Use the per-API consent with GA UX. 103 if (!ConsentManager.getInstance().getConsent(AdServicesApiType.FLEDGE).isGiven()) { 104 LoggerFactory.getFledgeLogger() 105 .d("User Consent is revoked ; skipping and cancelling job"); 106 return skipAndCancelKeyFetchJob( 107 params, 108 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_USER_CONSENT_REVOKED, 109 true); 110 } 111 112 // TODO(b/235841960): Consider using com.android.adservices.service.stats.Clock instead of 113 // Java Clock 114 Instant jobStartTime = Clock.systemUTC().instant(); 115 LoggerFactory.getFledgeLogger() 116 .d( 117 "Starting FLEDGE DebugReportSenderJobService job at %s", 118 jobStartTime.toString()); 119 120 DebugReportSenderWorker.getInstance(this) 121 .runDebugReportSender() 122 .addCallback( 123 new FutureCallback<Void>() { 124 // Never manually reschedule the background fetch job, since it is 125 // already scheduled periodically and should try again multiple times 126 // per day 127 @Override 128 public void onSuccess(Void result) { 129 boolean shouldRetry = false; 130 AdServicesJobServiceLogger.getInstance() 131 .recordJobFinished( 132 FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID, 133 /* isSuccessful= */ true, 134 shouldRetry); 135 136 jobFinished(params, shouldRetry); 137 } 138 139 @Override 140 public void onFailure(Throwable t) { 141 logExceptionMessage(t); 142 boolean shouldRetry = false; 143 AdServicesJobServiceLogger.getInstance() 144 .recordJobFinished( 145 FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID, 146 /* isSuccessful= */ false, 147 shouldRetry); 148 149 jobFinished(params, shouldRetry); 150 } 151 }, 152 AdServicesExecutors.getLightWeightExecutor()); 153 return true; 154 } 155 logExceptionMessage(Throwable t)156 private void logExceptionMessage(Throwable t) { 157 if (t instanceof InterruptedException) { 158 LoggerFactory.getFledgeLogger() 159 .e(t, "FLEDGE DebugReport Sender JobService interrupted"); 160 } else if (t instanceof ExecutionException) { 161 LoggerFactory.getFledgeLogger() 162 .e(t, "FLEDGE DebugReport Sender JobService failed due to internal error"); 163 } else if (t instanceof TimeoutException) { 164 LoggerFactory.getFledgeLogger() 165 .e(t, "FLEDGE DebugReport Sender JobService timeout exceeded"); 166 } else { 167 LoggerFactory.getFledgeLogger() 168 .e(t, "FLEDGE DebugReport Sender JobService failed due to unexpected error"); 169 } 170 } 171 skipAndCancelKeyFetchJob( final JobParameters params, int skipReason, boolean doRecord)172 private boolean skipAndCancelKeyFetchJob( 173 final JobParameters params, int skipReason, boolean doRecord) { 174 this.getSystemService(JobScheduler.class) 175 .cancel(FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID); 176 if (doRecord) { 177 AdServicesJobServiceLogger.getInstance() 178 .recordJobSkipped(FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID, skipReason); 179 } 180 jobFinished(params, false); 181 return false; 182 } 183 184 @Override onStopJob(JobParameters params)185 public boolean onStopJob(JobParameters params) { 186 LoggerFactory.getFledgeLogger().d("DebugReportSenderJobService.onStopJob"); 187 DebugReportSenderWorker.getInstance(this).stopWork(); 188 189 boolean shouldRetry = true; 190 191 AdServicesJobServiceLogger.getInstance() 192 .recordOnStopJob( 193 params, FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID, shouldRetry); 194 return shouldRetry; 195 } 196 197 /** 198 * Attempts to schedule the FLEDGE Ad Selection debug report sender as a singleton periodic job 199 * if it is not already scheduled. 200 * 201 * <p>The debug report sender job primarily sends debug reports generated for ad selections. It 202 * also prunes the ad selection debug report database of any expired data. 203 */ scheduleIfNeeded(Context context, boolean forceSchedule)204 public static void scheduleIfNeeded(Context context, boolean forceSchedule) { 205 Flags flags = FlagsFactory.getFlags(); 206 if (!flags.getFledgeEventLevelDebugReportingEnabled()) { 207 LoggerFactory.getFledgeLogger() 208 .v("FLEDGE Ad selection Debug reporting is disabled; skipping schedule"); 209 return; 210 } 211 212 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 213 214 // Scheduling a job can be expensive, and forcing a schedule could interrupt a job that is 215 // already in progress 216 // TODO(b/221837833): Intelligently decide when to overwrite a scheduled job 217 if ((jobScheduler.getPendingJob(FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID) == null) 218 || forceSchedule) { 219 schedule(context, flags); 220 LoggerFactory.getFledgeLogger() 221 .d("Scheduled FLEDGE ad selection Debug report sender job"); 222 } else { 223 LoggerFactory.getFledgeLogger() 224 .v( 225 "FLEDGE ad selection Debug report sender job already scheduled," 226 + " skipping reschedule"); 227 } 228 } 229 230 /** 231 * Actually schedules the FLEDGE Ad Selection Debug Report sender job as a singleton periodic 232 * job. 233 * 234 * <p>Split out from {@link #scheduleIfNeeded(Context, boolean)} for mockable testing without 235 * pesky permissions. 236 */ 237 @VisibleForTesting schedule(Context context, Flags flags)238 protected static void schedule(Context context, Flags flags) { 239 if (!flags.getFledgeEventLevelDebugReportingEnabled()) { 240 LoggerFactory.getFledgeLogger() 241 .v("FLEDGE Ad selection Debug reporting is disabled; skipping schedule"); 242 return; 243 } 244 245 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 246 final JobInfo job = 247 new JobInfo.Builder( 248 FLEDGE_AD_SELECTION_DEBUG_REPORT_SENDER_JOB_ID, 249 new ComponentName(context, DebugReportSenderJobService.class)) 250 .setRequiresBatteryNotLow(true) 251 .setRequiresDeviceIdle(true) 252 .setPeriodic( 253 flags.getFledgeDebugReportSenderJobPeriodMs(), 254 flags.getFledgeDebugReportSenderJobFlexMs()) 255 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 256 .setPersisted(true) 257 .build(); 258 jobScheduler.schedule(job); 259 } 260 } 261