• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 package com.android.adservices.service.common;
17 
18 import static com.android.adservices.spe.AdServicesJobInfo.AD_PACKAGE_DENY_PRE_PROCESS_JOB;
19 
20 import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
21 
22 import android.app.job.JobInfo;
23 import android.app.job.JobParameters;
24 import android.app.job.JobScheduler;
25 import android.app.job.JobService;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.os.Build;
29 
30 import androidx.annotation.RequiresApi;
31 import androidx.annotation.VisibleForTesting;
32 
33 import com.android.adservices.LogUtil;
34 import com.android.adservices.concurrency.AdServicesExecutors;
35 import com.android.adservices.service.FlagsFactory;
36 import com.android.adservices.service.common.compat.ServiceCompatUtils;
37 import com.android.adservices.shared.common.ApplicationContextSingleton;
38 import com.android.adservices.spe.AdServicesJobServiceLogger;
39 
40 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures;
41 import com.google.common.util.concurrent.FutureCallback;
42 import com.google.common.util.concurrent.Futures;
43 import com.google.common.util.concurrent.ListenableFuture;
44 
45 /**
46  * Class schedules a periodic job to load package deny data from mdd into cache on Android S where
47  * adding a new package does not trigger an event for adservices.
48  */
49 @RequiresApi(Build.VERSION_CODES.S)
50 public final class AdPackageDenyPreProcessJobService extends JobService {
51 
52     @Override
onStartJob(JobParameters params)53     public boolean onStartJob(JobParameters params) {
54         // Always ensure that the first thing this job does is check if it should be running, and
55         // cancel itself if it's not supposed to be.
56         if (ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(this)) {
57             LogUtil.d(
58                     "Disabling AdPackageDenyPreProcessJobService because it's running in"
59                             + " ExtServices on T+");
60             // Do not log via the AdservicesJobServiceLogger because the it might cause
61             // ClassNotFound exception on earlier beta versions.
62             return skipAndCancelBackgroundJob(params);
63         }
64 
65         // Record the invocation of onStartJob() for logging purpose.
66         LogUtil.d("AdPackageDenyPreProcessJobService.onStartJob");
67         AdServicesJobServiceLogger.getInstance()
68                 .recordOnStartJob(AD_PACKAGE_DENY_PRE_PROCESS_JOB.getJobId());
69         if (!FlagsFactory.getFlags().getEnablePackageDenyBgJob()) {
70             return skipAndCancelBackgroundJob(params);
71         }
72         ListenableFuture<AdPackageDenyResolver.PackageDenyMddProcessStatus> future =
73                 PropagatedFutures.submitAsync(
74                         () -> {
75                             LogUtil.d("AdPackageDenyPreProcessJobService.onStart Job.");
76                             return AdPackageDenyResolver.getInstance().loadDenyDataFromMdd();
77                         },
78                         AdServicesExecutors.getBlockingExecutor());
79 
80         // Background job logging in onSuccess and OnFailure have to happen before jobFinished() is
81         // called. Due to JobScheduler infra, the JobService instance will end its lifecycle (call
82         // onDestroy()) once jobFinished() is invoked.
83         Futures.addCallback(
84                 future,
85                 new FutureCallback<AdPackageDenyResolver.PackageDenyMddProcessStatus>() {
86                     @Override
87                     public void onSuccess(
88                             AdPackageDenyResolver.PackageDenyMddProcessStatus result) {
89                         LogUtil.d("AdPackageDenyPreProcessJobService job succeeded.");
90 
91                         // Tell the JobScheduler that the job has completed and does not
92                         // need to be rescheduled.
93                         boolean shouldRetry = false;
94                         AdServicesJobServiceLogger.getInstance()
95                                 .recordJobFinished(
96                                         AD_PACKAGE_DENY_PRE_PROCESS_JOB.getJobId(),
97                                         /* isSuccessful= */ true,
98                                         shouldRetry);
99                         jobFinished(params, shouldRetry);
100                     }
101 
102                     @Override
103                     public void onFailure(Throwable t) {
104                         LogUtil.e(t, "Failed to handle AdPackageDenyPreProcessJobService job");
105 
106                         // When failure, also tell the JobScheduler that the job has completed and
107                         // does not need to be rescheduled.
108                         boolean shouldRetry = false;
109                         AdServicesJobServiceLogger.getInstance()
110                                 .recordJobFinished(
111                                         AD_PACKAGE_DENY_PRE_PROCESS_JOB.getJobId(),
112                                         /* isSuccessful= */ false,
113                                         shouldRetry);
114                         jobFinished(params, shouldRetry);
115                     }
116                 },
117                 directExecutor());
118         return true;
119     }
120 
121     @Override
onStopJob(JobParameters params)122     public boolean onStopJob(JobParameters params) {
123         LogUtil.d("AdPackageDenyPreProcessJobService.onStopJob");
124         // Tell JobScheduler not to reschedule the job because it's unknown at this stage if the
125         // execution is completed or not to avoid executing the task twice.
126         boolean shouldRetry = false;
127 
128         AdServicesJobServiceLogger.getInstance()
129                 .recordOnStopJob(params, AD_PACKAGE_DENY_PRE_PROCESS_JOB.getJobId(), shouldRetry);
130         return shouldRetry;
131     }
132 
133     /**
134      * Schedules AdPackageDenyPreProcessJobService if needed: there is no scheduled job with name
135      * job parameters.
136      *
137      * @return a {@code boolean} to indicate if the service job is actually scheduled.
138      */
scheduleIfNeeded()139     public static boolean scheduleIfNeeded() {
140         if (!FlagsFactory.getFlags().getEnablePackageDenyBgJob()) {
141             LogUtil.d("AdPackageDenyPreProcessJobService is disabled");
142             return false;
143         }
144         Context context = ApplicationContextSingleton.get();
145         JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
146         if (jobScheduler == null) {
147             LogUtil.e("Cannot fetch job scheduler.");
148             return false;
149         }
150         JobInfo scheduledJobJobInfo =
151                 jobScheduler.getPendingJob(AD_PACKAGE_DENY_PRE_PROCESS_JOB.getJobId());
152         final JobInfo job = buildJobInfo(context);
153         if (job.equals(scheduledJobJobInfo)) {
154             LogUtil.i(
155                     "AdPackageDenyPreProcessJobService has been scheduled with same "
156                             + "parameters, skip "
157                             + "rescheduling.");
158         } else {
159             schedule(job, jobScheduler);
160         }
161         return true;
162     }
163 
164     @VisibleForTesting
schedule(JobInfo job, JobScheduler jobScheduler)165     static void schedule(JobInfo job, JobScheduler jobScheduler) {
166         jobScheduler.schedule(job);
167         LogUtil.d("Scheduling AdDenyAppPreProcessorJobService job ...");
168     }
169 
170     // TODO(b/311183933): Remove passed in Context from static method.
171     @SuppressWarnings("AvoidStaticContext")
buildJobInfo(Context context)172     private static JobInfo buildJobInfo(Context context) {
173         // TODO(b/374326249) move periodic flex time config to flags
174         long flexIntervalMillis = 60 * 60 * 1000; // 1 hr
175         return new JobInfo.Builder(
176                         AD_PACKAGE_DENY_PRE_PROCESS_JOB.getJobId(),
177                         new ComponentName(context, AdPackageDenyPreProcessJobService.class))
178                 .setPeriodic(
179                         FlagsFactory.getFlags().getPackageDenyBackgroundJobPeriodMillis(),
180                         flexIntervalMillis)
181                 .setPersisted(true)
182                 .build();
183     }
184 
skipAndCancelBackgroundJob(JobParameters params)185     private boolean skipAndCancelBackgroundJob(JobParameters params) {
186         LogUtil.d("Cancelling the AdPackageDenyPreProcessJobService job");
187         JobScheduler jobScheduler = this.getSystemService(JobScheduler.class);
188         if (jobScheduler != null) {
189             jobScheduler.cancel(AD_PACKAGE_DENY_PRE_PROCESS_JOB.getJobId());
190         }
191         AdServicesJobServiceLogger.getInstance()
192                 .recordJobSkipped(AD_PACKAGE_DENY_PRE_PROCESS_JOB.getJobId(), 0);
193         jobFinished(params, false);
194         return false;
195     }
196 }
197