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 JobLockHolder.getInstance(ASYNC_REGISTRATION_PROCESSING) 126 .runWithLock( 127 "AsyncRegistrationFallbackQueueJobService", 128 () -> 129 AsyncRegistrationQueueRunner.getInstance() 130 .runAsyncRegistrationQueueWorker()); 131 } 132 133 @Override onStopJob(JobParameters params)134 public boolean onStopJob(JobParameters params) { 135 LoggerFactory.getMeasurementLogger().d("AsyncRegistrationFallbackJobService.onStopJob"); 136 boolean shouldRetry = true; 137 if (mExecutorFuture != null) { 138 shouldRetry = mExecutorFuture.cancel(/* mayInterruptIfRunning */ true); 139 } 140 AdServicesJobServiceLogger.getInstance() 141 .recordOnStopJob( 142 params, MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID, shouldRetry); 143 return shouldRetry; 144 } 145 146 @VisibleForTesting schedule(JobScheduler jobScheduler, JobInfo jobInfo)147 protected static void schedule(JobScheduler jobScheduler, JobInfo jobInfo) { 148 jobScheduler.schedule(jobInfo); 149 } 150 151 /** 152 * Schedule Fallback Async Registration Job Service if it is not already scheduled 153 * 154 * @param forceSchedule flag to indicate whether to force rescheduling the job. 155 */ 156 @JobSchedulingResultCode scheduleIfNeeded(boolean forceSchedule)157 public static int scheduleIfNeeded(boolean forceSchedule) { 158 Context context = ApplicationContextSingleton.get(); 159 Flags flags = FlagsFactory.getFlags(); 160 if (flags.getAsyncRegistrationFallbackJobKillSwitch()) { 161 LoggerFactory.getMeasurementLogger() 162 .e("AsyncRegistrationFallbackJobService is disabled, skip scheduling"); 163 return SCHEDULING_RESULT_CODE_SKIPPED; 164 } 165 166 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 167 if (jobScheduler == null) { 168 LoggerFactory.getMeasurementLogger().e("JobScheduler not found"); 169 return SCHEDULING_RESULT_CODE_FAILED; 170 } 171 172 final JobInfo scheduledJob = 173 jobScheduler.getPendingJob(MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID); 174 // Schedule if it hasn't been scheduled already or force rescheduling 175 final JobInfo jobInfo = buildJobInfo(context, flags); 176 if (forceSchedule || !jobInfo.equals(scheduledJob)) { 177 schedule(jobScheduler, jobInfo); 178 LoggerFactory.getMeasurementLogger().d("Scheduled AsyncRegistrationFallbackJobService"); 179 return SCHEDULING_RESULT_CODE_SUCCESSFUL; 180 } else { 181 LoggerFactory.getMeasurementLogger() 182 .d( 183 "AsyncRegistrationFallbackJobService already scheduled, skipping" 184 + " reschedule"); 185 return SCHEDULING_RESULT_CODE_SKIPPED; 186 } 187 } 188 189 // TODO(b/311183933): Remove passed in Context from static method. 190 @SuppressWarnings("AvoidStaticContext") buildJobInfo(Context context, Flags flags)191 private static JobInfo buildJobInfo(Context context, Flags flags) { 192 return new JobInfo.Builder( 193 MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID, 194 new ComponentName(context, AsyncRegistrationFallbackJobService.class)) 195 .setRequiresBatteryNotLow( 196 flags.getMeasurementAsyncRegistrationFallbackJobRequiredBatteryNotLow()) 197 .setPeriodic(flags.getAsyncRegistrationJobQueueIntervalMs()) 198 .setRequiredNetworkType( 199 flags.getMeasurementAsyncRegistrationFallbackJobRequiredNetworkType()) 200 .setPersisted(flags.getMeasurementAsyncRegistrationFallbackJobPersisted()) 201 .build(); 202 } 203 skipAndCancelBackgroundJob( final JobParameters params, int skipReason, boolean doRecord)204 private boolean skipAndCancelBackgroundJob( 205 final JobParameters params, int skipReason, boolean doRecord) { 206 final JobScheduler jobScheduler = this.getSystemService(JobScheduler.class); 207 if (jobScheduler != null) { 208 jobScheduler.cancel(MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID); 209 } 210 211 if (doRecord) { 212 AdServicesJobServiceLogger.getInstance() 213 .recordJobSkipped(MEASUREMENT_ASYNC_REGISTRATION_FALLBACK_JOB_ID, skipReason); 214 } 215 216 // Tell the JobScheduler that the job is done and does not need to be rescheduled 217 jobFinished(params, false); 218 219 // Returning false to reschedule this job. 220 return false; 221 } 222 223 @VisibleForTesting getFutureForTesting()224 Future getFutureForTesting() { 225 return mExecutorFuture; 226 } 227 } 228