• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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