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.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.spe.AdservicesJobInfo.MEASUREMENT_ASYNC_REGISTRATION_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.concurrency.AdServicesExecutors; 32 import com.android.adservices.service.FlagsFactory; 33 import com.android.adservices.service.common.compat.ServiceCompatUtils; 34 import com.android.adservices.service.measurement.SystemHealthParams; 35 import com.android.adservices.spe.AdservicesJobServiceLogger; 36 import com.android.internal.annotations.VisibleForTesting; 37 38 import java.time.Clock; 39 import java.time.Instant; 40 41 /** Job Service for servicing queued registration requests */ 42 public class AsyncRegistrationQueueJobService extends JobService { 43 private static final int MEASUREMENT_ASYNC_REGISTRATION_JOB_ID = 44 MEASUREMENT_ASYNC_REGISTRATION_JOB.getJobId(); 45 46 @Override onStartJob(JobParameters params)47 public boolean onStartJob(JobParameters params) { 48 // Always ensure that the first thing this job does is check if it should be running, and 49 // cancel itself if it's not supposed to be. 50 if (ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(this)) { 51 LogUtil.d( 52 "Disabling AsyncRegistrationQueueJobService job because it's running in" 53 + " ExtServices on T+"); 54 return skipAndCancelBackgroundJob( 55 params, 56 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_EXTSERVICES_JOB_ON_TPLUS); 57 } 58 59 AdservicesJobServiceLogger.getInstance(this) 60 .recordOnStartJob(MEASUREMENT_ASYNC_REGISTRATION_JOB_ID); 61 62 if (FlagsFactory.getFlags().getAsyncRegistrationJobQueueKillSwitch()) { 63 LogUtil.e("AsyncRegistrationQueueJobService is disabled"); 64 return skipAndCancelBackgroundJob( 65 params, 66 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON); 67 } 68 69 Instant jobStartTime = Clock.systemUTC().instant(); 70 LogUtil.d( 71 "AsyncRegistrationQueueJobService.onStartJob " + "at %s", jobStartTime.toString()); 72 AsyncRegistrationQueueRunner asyncQueueRunner = 73 AsyncRegistrationQueueRunner.getInstance(getApplicationContext()); 74 75 AdServicesExecutors.getBackgroundExecutor() 76 .execute( 77 () -> { 78 asyncQueueRunner.runAsyncRegistrationQueueWorker(); 79 80 boolean shouldRetry = false; 81 AdservicesJobServiceLogger.getInstance( 82 AsyncRegistrationQueueJobService.this) 83 .recordJobFinished( 84 MEASUREMENT_ASYNC_REGISTRATION_JOB_ID, 85 /* isSuccessful */ true, 86 shouldRetry); 87 88 jobFinished(params, shouldRetry); 89 // jobFinished is asynchronous, so forcing scheduling avoiding 90 // concurrency issue 91 scheduleIfNeeded(this, /* forceSchedule */ true); 92 }); 93 return true; 94 } 95 96 @Override onStopJob(JobParameters params)97 public boolean onStopJob(JobParameters params) { 98 LogUtil.d("AsyncRegistrationQueueJobService.onStopJob"); 99 boolean shouldRetry = false; 100 101 AdservicesJobServiceLogger.getInstance(this) 102 .recordOnStopJob(params, MEASUREMENT_ASYNC_REGISTRATION_JOB_ID, shouldRetry); 103 return shouldRetry; 104 } 105 106 @VisibleForTesting schedule(Context context, JobScheduler jobScheduler)107 protected static void schedule(Context context, JobScheduler jobScheduler) { 108 final JobInfo job = 109 new JobInfo.Builder( 110 MEASUREMENT_ASYNC_REGISTRATION_JOB_ID, 111 new ComponentName(context, AsyncRegistrationQueueJobService.class)) 112 .addTriggerContentUri( 113 new JobInfo.TriggerContentUri( 114 AsyncRegistrationContentProvider.TRIGGER_URI, 115 JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)) 116 .setTriggerContentUpdateDelay( 117 SystemHealthParams.ASYNC_REGISTRATION_JOB_TRIGGERING_DELAY_MS) 118 .setTriggerContentMaxDelay( 119 SystemHealthParams.ASYNC_REGISTRATION_JOB_TRIGGERING_MAX_DELAY_MS) 120 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) 121 .setPersisted(false) // Can't call addTriggerContentUri() on a persisted job 122 .build(); 123 jobScheduler.schedule(job); 124 } 125 /** 126 * Schedule Async Registration Queue Job Service if it is not already scheduled 127 * 128 * @param context the context 129 * @param forceSchedule flag to indicate whether to force rescheduling the job. 130 */ scheduleIfNeeded(Context context, boolean forceSchedule)131 public static void scheduleIfNeeded(Context context, boolean forceSchedule) { 132 if (FlagsFactory.getFlags().getAsyncRegistrationJobQueueKillSwitch()) { 133 LogUtil.e("AsyncRegistrationQueueJobService is disabled, skip scheduling"); 134 return; 135 } 136 137 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 138 if (jobScheduler == null) { 139 LogUtil.e("JobScheduler not found"); 140 return; 141 } 142 143 final JobInfo job = jobScheduler.getPendingJob(MEASUREMENT_ASYNC_REGISTRATION_JOB_ID); 144 // Schedule if it hasn't been scheduled already or force rescheduling 145 if (job == null || forceSchedule) { 146 schedule(context, jobScheduler); 147 LogUtil.d("Scheduled AsyncRegistrationQueueJobService"); 148 } else { 149 LogUtil.d("AsyncRegistrationQueueJobService already scheduled, skipping reschedule"); 150 } 151 } 152 skipAndCancelBackgroundJob(final JobParameters params, int skipReason)153 private boolean skipAndCancelBackgroundJob(final JobParameters params, int skipReason) { 154 final JobScheduler jobScheduler = this.getSystemService(JobScheduler.class); 155 if (jobScheduler != null) { 156 jobScheduler.cancel(MEASUREMENT_ASYNC_REGISTRATION_JOB_ID); 157 } 158 159 AdservicesJobServiceLogger.getInstance(this) 160 .recordJobSkipped(MEASUREMENT_ASYNC_REGISTRATION_JOB_ID, skipReason); 161 162 // Tell the JobScheduler that the job is done and does not need to be rescheduled 163 jobFinished(params, false); 164 165 // Returning false to reschedule this job. 166 return false; 167 } 168 } 169