1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.adservices.service.measurement.attribution; 18 19 import static com.android.adservices.service.measurement.util.JobLockHolder.Type.ATTRIBUTION_PROCESSING; 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.spe.AdServicesJobInfo.MEASUREMENT_ATTRIBUTION_FALLBACK_JOB; 22 23 import android.app.job.JobInfo; 24 import android.app.job.JobParameters; 25 import android.app.job.JobScheduler; 26 import android.app.job.JobService; 27 import android.content.ComponentName; 28 import android.content.Context; 29 30 import com.android.adservices.LogUtil; 31 import com.android.adservices.LoggerFactory; 32 import com.android.adservices.concurrency.AdServicesExecutors; 33 import com.android.adservices.data.measurement.DatastoreManagerFactory; 34 import com.android.adservices.service.Flags; 35 import com.android.adservices.service.FlagsFactory; 36 import com.android.adservices.service.common.compat.ServiceCompatUtils; 37 import com.android.adservices.service.measurement.Trigger; 38 import com.android.adservices.service.measurement.reporting.DebugReportApi; 39 import com.android.adservices.service.measurement.reporting.DebugReportingJobService; 40 import com.android.adservices.service.measurement.reporting.ImmediateAggregateReportingJobService; 41 import com.android.adservices.service.measurement.reporting.ReportingJobService; 42 import com.android.adservices.service.measurement.util.JobLockHolder; 43 import com.android.adservices.spe.AdServicesJobServiceLogger; 44 import com.android.internal.annotations.VisibleForTesting; 45 46 import com.google.common.util.concurrent.ListeningExecutorService; 47 48 import java.util.concurrent.Future; 49 50 /** 51 * Fallback attribution job. The actual job execution logic is part of {@link 52 * AttributionJobHandler}. 53 */ 54 public class AttributionFallbackJobService extends JobService { 55 private static final int MEASUREMENT_ATTRIBUTION_FALLBACK_JOB_ID = 56 MEASUREMENT_ATTRIBUTION_FALLBACK_JOB.getJobId(); 57 private static final ListeningExecutorService sBackgroundExecutor = 58 AdServicesExecutors.getBackgroundExecutor(); 59 private Future mExecutorFuture; 60 61 @Override onCreate()62 public void onCreate() { 63 LogUtil.d("AttributionFallbackJobService.onCreate"); 64 super.onCreate(); 65 } 66 67 @Override onStartJob(JobParameters params)68 public boolean onStartJob(JobParameters params) { 69 // Always ensure that the first thing this job does is check if it should be running, and 70 // cancel itself if it's not supposed to be. 71 if (ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(this)) { 72 LogUtil.d( 73 "Disabling AttributionFallbackJobService job because it's running in" 74 + " ExtServices on T+"); 75 return skipAndCancelBackgroundJob(params, /* skipReason=*/ 0, /* doRecord=*/ false); 76 } 77 78 AdServicesJobServiceLogger.getInstance() 79 .recordOnStartJob(MEASUREMENT_ATTRIBUTION_FALLBACK_JOB_ID); 80 81 if (!FlagsFactory.getFlags().getMeasurementAttributionFallbackJobEnabled()) { 82 LoggerFactory.getMeasurementLogger().e("AttributionFallbackJobService is disabled"); 83 return skipAndCancelBackgroundJob( 84 params, 85 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON, 86 /* doRecord=*/ true); 87 } 88 89 LoggerFactory.getMeasurementLogger().d("AttributionFallbackJobService.onStartJob"); 90 mExecutorFuture = 91 sBackgroundExecutor.submit( 92 () -> { 93 processPendingAttributions(); 94 95 DebugReportingJobService.scheduleIfNeeded( 96 getApplicationContext(), /* forceSchedule */ false); 97 98 // TODO(b/342687685): fold this service into ReportingJobService 99 ImmediateAggregateReportingJobService.scheduleIfNeeded( 100 getApplicationContext(), /* forceSchedule */ false); 101 102 ReportingJobService.scheduleIfNeeded( 103 getApplicationContext(), /* forceSchedule */ false); 104 105 AdServicesJobServiceLogger.getInstance() 106 .recordJobFinished( 107 MEASUREMENT_ATTRIBUTION_FALLBACK_JOB_ID, 108 /* isSuccessful */ true, 109 /* shouldRetry */ false); 110 111 jobFinished(params, /* wantsReschedule= */ false); 112 }); 113 return true; 114 } 115 116 @VisibleForTesting processPendingAttributions()117 void processPendingAttributions() { 118 final JobLockHolder lock = JobLockHolder.getInstance(ATTRIBUTION_PROCESSING); 119 if (lock.tryLock()) { 120 try { 121 new AttributionJobHandler( 122 DatastoreManagerFactory.getDatastoreManager( 123 getApplicationContext()), 124 new DebugReportApi( 125 getApplicationContext(), FlagsFactory.getFlags())) 126 .performPendingAttributions(); 127 return; 128 } finally { 129 lock.unlock(); 130 } 131 } 132 LoggerFactory.getMeasurementLogger() 133 .d("AttributionFallbackJobService did not acquire the lock"); 134 } 135 136 @Override onStopJob(JobParameters params)137 public boolean onStopJob(JobParameters params) { 138 LoggerFactory.getMeasurementLogger().d("AttributionFallbackJobService.onStopJob"); 139 boolean shouldRetry = true; 140 if (mExecutorFuture != null) { 141 shouldRetry = mExecutorFuture.cancel(/* mayInterruptIfRunning */ true); 142 } 143 AdServicesJobServiceLogger.getInstance() 144 .recordOnStopJob(params, MEASUREMENT_ATTRIBUTION_FALLBACK_JOB_ID, shouldRetry); 145 return shouldRetry; 146 } 147 148 /** 149 * Schedules {@link AttributionFallbackJobService} to observer {@link Trigger} content URI 150 * change. 151 */ 152 @VisibleForTesting schedule(JobScheduler jobScheduler, JobInfo job)153 static void schedule(JobScheduler jobScheduler, JobInfo job) { 154 jobScheduler.schedule(job); 155 } 156 buildJobInfo(Context context, Flags flags)157 private static JobInfo buildJobInfo(Context context, Flags flags) { 158 return new JobInfo.Builder( 159 MEASUREMENT_ATTRIBUTION_FALLBACK_JOB_ID, 160 new ComponentName(context, AttributionFallbackJobService.class)) 161 .setPeriodic(flags.getMeasurementAttributionFallbackJobPeriodMs()) 162 .setPersisted(flags.getMeasurementAttributionFallbackJobPersisted()) 163 .build(); 164 } 165 166 /** 167 * Schedule Attribution Fallback Job if it is not already scheduled 168 * 169 * @param context the context 170 * @param forceSchedule flag to indicate whether to force rescheduling the job. 171 */ scheduleIfNeeded(Context context, boolean forceSchedule)172 public static void scheduleIfNeeded(Context context, boolean forceSchedule) { 173 Flags flags = FlagsFactory.getFlags(); 174 if (!flags.getMeasurementAttributionFallbackJobEnabled()) { 175 LoggerFactory.getMeasurementLogger() 176 .e("AttributionFallbackJobService is disabled, skip scheduling"); 177 return; 178 } 179 180 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 181 if (jobScheduler == null) { 182 LoggerFactory.getMeasurementLogger().e("JobScheduler not found"); 183 return; 184 } 185 186 final JobInfo scheduledJob = 187 jobScheduler.getPendingJob(MEASUREMENT_ATTRIBUTION_FALLBACK_JOB_ID); 188 // Schedule if it hasn't been scheduled already or force rescheduling 189 JobInfo jobInfo = buildJobInfo(context, flags); 190 if (forceSchedule || !jobInfo.equals(scheduledJob)) { 191 schedule(jobScheduler, jobInfo); 192 LoggerFactory.getMeasurementLogger().d("Scheduled AttributionFallbackJobService"); 193 } else { 194 LoggerFactory.getMeasurementLogger() 195 .d("AttributionFallbackJobService already scheduled, skipping reschedule"); 196 } 197 } 198 skipAndCancelBackgroundJob( final JobParameters params, int skipReason, boolean doRecord)199 private boolean skipAndCancelBackgroundJob( 200 final JobParameters params, int skipReason, boolean doRecord) { 201 final JobScheduler jobScheduler = this.getSystemService(JobScheduler.class); 202 if (jobScheduler != null) { 203 jobScheduler.cancel(MEASUREMENT_ATTRIBUTION_FALLBACK_JOB_ID); 204 } 205 206 if (doRecord) { 207 AdServicesJobServiceLogger.getInstance() 208 .recordJobSkipped(MEASUREMENT_ATTRIBUTION_FALLBACK_JOB_ID, skipReason); 209 } 210 211 // Tell the JobScheduler that the job has completed and does not need to be rescheduled. 212 jobFinished(params, false); 213 214 // Returning false means that this job has completed its work. 215 return false; 216 } 217 218 @VisibleForTesting getFutureForTesting()219 Future getFutureForTesting() { 220 return mExecutorFuture; 221 } 222 } 223