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 import static com.android.adservices.service.measurement.util.JobLockHolder.Type.ASYNC_REGISTRATION_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.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_FAILED; 22 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_SKIPPED; 23 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_SUCCESSFUL; 24 import static com.android.adservices.spe.AdServicesJobInfo.MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB; 25 26 import android.annotation.RequiresApi; 27 import android.app.job.JobInfo; 28 import android.app.job.JobParameters; 29 import android.app.job.JobScheduler; 30 import android.app.job.JobService; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.os.Build; 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.measurement.util.JobLockHolder; 42 import com.android.adservices.shared.common.ApplicationContextSingleton; 43 import com.android.adservices.shared.spe.JobServiceConstants.JobSchedulingResultCode; 44 import com.android.adservices.spe.AdServicesJobServiceLogger; 45 import com.android.internal.annotations.VisibleForTesting; 46 47 import java.time.Clock; 48 import java.time.Instant; 49 import java.util.concurrent.Future; 50 51 /** Fallback Job Service for servicing queued registration requests */ 52 // TODO(b/328287543): Since Rb has released to R so functionally this class should support R. Due to 53 // Legacy issue, class such as BackgroundJobsManager and MddJobService which have to support R also 54 // have this annotation. It won't have production impact but is needed to bypass the build error. 55 @RequiresApi(Build.VERSION_CODES.S) 56 public class AsyncRegistrationFallbackJobService extends JobService { 57 private static final int MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID = 58 MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB.getJobId(); 59 60 private Future mExecutorFuture; 61 62 @Override onStartJob(JobParameters params)63 public boolean onStartJob(JobParameters params) { 64 // Always ensure that the first thing this job does is check if it should be running, and 65 // cancel itself if it's not supposed to be. 66 if (ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(this)) { 67 LogUtil.d( 68 "Disabling AsyncRegistrationFallbackJobService job because it's running in" 69 + " ExtServices on T+"); 70 return skipAndCancelBackgroundJob(params, /* skipReason= */ 0, /* doRecord= */ false); 71 } 72 73 // Reschedule jobs with SPE if it's enabled. Note scheduled jobs by this 74 // AsyncRegistrationFallbackJobService will be cancelled for the same job ID. 75 // 76 // Note the job without a flex period will execute immediately after rescheduling with the 77 // same ID. Therefore, ending the execution here and let it run in the new SPE job. 78 if (FlagsFactory.getFlags().getSpeOnAsyncRegistrationFallbackJobEnabled()) { 79 LoggerFactory.getMeasurementLogger() 80 .d( 81 "SPE is enabled. Reschedule AsyncRegistrationFallbackJobService with" 82 + " AsyncRegistrationFallbackJob."); 83 AsyncRegistrationFallbackJob.schedule(); 84 return false; 85 } 86 87 AdServicesJobServiceLogger.getInstance() 88 .recordOnStartJob(MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID); 89 90 if (FlagsFactory.getFlags().getAsyncRegistrationFallbackJobKillSwitch()) { 91 LoggerFactory.getMeasurementLogger() 92 .e("AsyncRegistrationFallbackJobService is disabled"); 93 return skipAndCancelBackgroundJob( 94 params, 95 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON, 96 /* doRecord= */ true); 97 } 98 99 Instant jobStartTime = Clock.systemUTC().instant(); 100 LoggerFactory.getMeasurementLogger() 101 .d( 102 "AsyncRegistrationFallbackJobService.onStartJob " + "at %s", 103 jobStartTime.toString()); 104 105 mExecutorFuture = 106 AdServicesExecutors.getBlockingExecutor() 107 .submit( 108 () -> { 109 processAsyncRecords(); 110 111 boolean shouldRetry = false; 112 AdServicesJobServiceLogger.getInstance() 113 .recordJobFinished( 114 MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID, 115 /* isSuccessful */ true, 116 shouldRetry); 117 118 jobFinished(params, false); 119 }); 120 return true; 121 } 122 123 @VisibleForTesting processAsyncRecords()124 void processAsyncRecords() { 125 final JobLockHolder lock = JobLockHolder.getInstance(ASYNC_REGISTRATION_PROCESSING); 126 if (lock.tryLock()) { 127 try { 128 AsyncRegistrationQueueRunner.getInstance(getApplicationContext()) 129 .runAsyncRegistrationQueueWorker(); 130 return; 131 } finally { 132 lock.unlock(); 133 } 134 } 135 LoggerFactory.getMeasurementLogger() 136 .d("AsyncRegistrationFallbackQueueJobService did not acquire the lock"); 137 } 138 139 @Override onStopJob(JobParameters params)140 public boolean onStopJob(JobParameters params) { 141 LoggerFactory.getMeasurementLogger().d("AsyncRegistrationFallbackJobService.onStopJob"); 142 boolean shouldRetry = true; 143 if (mExecutorFuture != null) { 144 shouldRetry = mExecutorFuture.cancel(/* mayInterruptIfRunning */ true); 145 } 146 AdServicesJobServiceLogger.getInstance() 147 .recordOnStopJob( 148 params, MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID, shouldRetry); 149 return shouldRetry; 150 } 151 152 @VisibleForTesting schedule(JobScheduler jobScheduler, JobInfo jobInfo)153 protected static void schedule(JobScheduler jobScheduler, JobInfo jobInfo) { 154 jobScheduler.schedule(jobInfo); 155 } 156 157 /** 158 * Schedule Fallback Async Registration Job Service if it is not already scheduled 159 * 160 * @param forceSchedule flag to indicate whether to force rescheduling the job. 161 */ 162 @JobSchedulingResultCode scheduleIfNeeded(boolean forceSchedule)163 public static int scheduleIfNeeded(boolean forceSchedule) { 164 Context context = ApplicationContextSingleton.get(); 165 Flags flags = FlagsFactory.getFlags(); 166 if (flags.getAsyncRegistrationFallbackJobKillSwitch()) { 167 LoggerFactory.getMeasurementLogger() 168 .e("AsyncRegistrationFallbackJobService is disabled, skip scheduling"); 169 return SCHEDULING_RESULT_CODE_SKIPPED; 170 } 171 172 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 173 if (jobScheduler == null) { 174 LoggerFactory.getMeasurementLogger().e("JobScheduler not found"); 175 return SCHEDULING_RESULT_CODE_FAILED; 176 } 177 178 final JobInfo scheduledJob = 179 jobScheduler.getPendingJob(MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID); 180 // Schedule if it hasn't been scheduled already or force rescheduling 181 final JobInfo jobInfo = buildJobInfo(context, flags); 182 if (forceSchedule || !jobInfo.equals(scheduledJob)) { 183 schedule(jobScheduler, jobInfo); 184 LoggerFactory.getMeasurementLogger().d("Scheduled AsyncRegistrationFallbackJobService"); 185 return SCHEDULING_RESULT_CODE_SUCCESSFUL; 186 } else { 187 LoggerFactory.getMeasurementLogger() 188 .d( 189 "AsyncRegistrationFallbackJobService already scheduled, skipping" 190 + " reschedule"); 191 return SCHEDULING_RESULT_CODE_SKIPPED; 192 } 193 } 194 buildJobInfo(Context context, Flags flags)195 private static JobInfo buildJobInfo(Context context, Flags flags) { 196 return new JobInfo.Builder( 197 MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID, 198 new ComponentName(context, AsyncRegistrationFallbackJobService.class)) 199 .setRequiresBatteryNotLow( 200 flags.getMeasurementAsyncRegistrationFallbackJobRequiredBatteryNotLow()) 201 .setPeriodic(flags.getAsyncRegistrationJobQueueIntervalMs()) 202 .setRequiredNetworkType( 203 flags.getMeasurementAsyncRegistrationFallbackJobRequiredNetworkType()) 204 .setPersisted(flags.getMeasurementAsyncRegistrationFallbackJobPersisted()) 205 .build(); 206 } 207 skipAndCancelBackgroundJob( final JobParameters params, int skipReason, boolean doRecord)208 private boolean skipAndCancelBackgroundJob( 209 final JobParameters params, int skipReason, boolean doRecord) { 210 final JobScheduler jobScheduler = this.getSystemService(JobScheduler.class); 211 if (jobScheduler != null) { 212 jobScheduler.cancel(MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID); 213 } 214 215 if (doRecord) { 216 AdServicesJobServiceLogger.getInstance() 217 .recordJobSkipped(MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID, skipReason); 218 } 219 220 // Tell the JobScheduler that the job is done and does not need to be rescheduled 221 jobFinished(params, false); 222 223 // Returning false to reschedule this job. 224 return false; 225 } 226 227 @VisibleForTesting getFutureForTesting()228 Future getFutureForTesting() { 229 return mExecutorFuture; 230 } 231 } 232