• 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.adselection.encryption;
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.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_USER_CONSENT_REVOKED;
22 import static com.android.adservices.spe.AdServicesJobInfo.FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB;
23 
24 import android.annotation.RequiresApi;
25 import android.annotation.SuppressLint;
26 import android.app.job.JobInfo;
27 import android.app.job.JobParameters;
28 import android.app.job.JobScheduler;
29 import android.app.job.JobService;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.content.Intent;
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.DebugFlags;
39 import com.android.adservices.service.Flags;
40 import com.android.adservices.service.FlagsFactory;
41 import com.android.adservices.service.common.compat.ServiceCompatUtils;
42 import com.android.adservices.service.consent.AdServicesApiType;
43 import com.android.adservices.service.consent.ConsentManager;
44 import com.android.adservices.shared.common.ApplicationContextSingleton;
45 import com.android.adservices.spe.AdServicesJobServiceLogger;
46 import com.android.internal.annotations.VisibleForTesting;
47 
48 import com.google.common.util.concurrent.FutureCallback;
49 
50 import java.time.Clock;
51 import java.time.Instant;
52 import java.util.concurrent.ExecutionException;
53 import java.util.concurrent.TimeoutException;
54 
55 /**
56  * Background fetch for Fledge encryption key fetch from the Key Management Servers and periodic
57  * deletion of expired keys.
58  */
59 @SuppressLint("LineLength")
60 @RequiresApi(Build.VERSION_CODES.S)
61 public class BackgroundKeyFetchJobService extends JobService {
62     private static final int FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB_ID =
63             FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB.getJobId();
64     private static final String ACTION_BACKGROUND_KEY_FETCH_COMPLETE =
65             "ACTION_BACKGROUND_KEY_FETCH_COMPLETE";
66 
67     @Override
onStartJob(JobParameters params)68     public boolean onStartJob(JobParameters params) {
69         // Always ensure that the first thing this job does is check if it should be running, and
70         // cancel itself if it's not supposed to be.
71         if (ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(this)) {
72             LogUtil.d(
73                     "Disabling BackgroundKeyFetchJobService job because it's running in "
74                             + " ExtServices on T+");
75             return skipAndCancelKeyFetchJob(
76                     params,
77                     AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_EXTSERVICES_JOB_ON_TPLUS);
78         }
79 
80         LoggerFactory.getFledgeLogger().d("BackgroundKeyFetchJobService.onStartJob");
81 
82         AdServicesJobServiceLogger.getInstance()
83                 .recordOnStartJob(FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB_ID);
84 
85         if (FlagsFactory.getFlags().getFledgeAuctionServerKillSwitch()) {
86             LoggerFactory.getFledgeLogger()
87                     .d("FLEDGE Ad Selection Data API is disabled ; skipping and cancelling job");
88             return skipAndCancelKeyFetchJob(
89                     params,
90                     AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
91         }
92 
93         if (!FlagsFactory.getFlags().getFledgeAuctionServerBackgroundKeyFetchJobEnabled()) {
94             LoggerFactory.getFledgeLogger()
95                     .d("FLEDGE background key fetch is disabled; skipping and cancelling job");
96             return skipAndCancelKeyFetchJob(
97                     params,
98                     AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON);
99         }
100 
101         // Skip the execution and cancel the job if user consent is revoked.
102         // Use the per-API consent with GA UX.
103         if (!ConsentManager.getInstance().getConsent(AdServicesApiType.FLEDGE).isGiven()) {
104             LoggerFactory.getFledgeLogger()
105                     .d("User Consent is revoked ; skipping and cancelling job");
106             return skipAndCancelKeyFetchJob(
107                     params,
108                     AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_USER_CONSENT_REVOKED);
109         }
110 
111         // TODO(b/235841960): Consider using com.android.adservices.service.stats.Clock instead of
112         //  Java Clock
113         Instant jobStartTime = Clock.systemUTC().instant();
114         LoggerFactory.getFledgeLogger()
115                 .d("Starting FLEDGE key fetch job at %s", jobStartTime.toString());
116 
117         BackgroundKeyFetchWorker.getInstance()
118                 .runBackgroundKeyFetch()
119                 .addCallback(
120                         new FutureCallback<Void>() {
121                             // Never manually reschedule the background key fetch job, since it is
122                             // already scheduled periodically and should try again as per its
123                             // schedule.
124                             @Override
125                             public void onSuccess(Void result) {
126                                 boolean shouldRetry = false;
127                                 AdServicesJobServiceLogger.getInstance()
128                                         .recordJobFinished(
129                                                 FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB_ID,
130                                                 /* isSuccessful= */ true,
131                                                 shouldRetry);
132 
133                                 jobFinished(params, shouldRetry);
134                                 sendBroadcastIntentIfEnabled();
135                             }
136 
137                             @Override
138                             public void onFailure(Throwable t) {
139                                 if (t instanceof InterruptedException) {
140                                     LoggerFactory.getFledgeLogger()
141                                             .e(
142                                                     t,
143                                                     "FLEDGE key background fetch interrupted while"
144                                                             + " waiting for key fetch payload");
145                                 } else if (t instanceof ExecutionException) {
146                                     LoggerFactory.getFledgeLogger()
147                                             .e(
148                                                     t,
149                                                     "FLEDGE key background fetch failed due to"
150                                                             + " internal error");
151                                 } else if (t instanceof TimeoutException) {
152                                     LoggerFactory.getFledgeLogger()
153                                             .e(t, "FLEDGE background key fetch timeout exceeded");
154                                 } else {
155                                     LoggerFactory.getFledgeLogger()
156                                             .e(
157                                                     t,
158                                                     "FLEDGE background key fetch failed due to"
159                                                             + " unexpected error");
160                                 }
161 
162                                 boolean shouldRetry = false;
163                                 AdServicesJobServiceLogger.getInstance()
164                                         .recordJobFinished(
165                                                 FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB_ID,
166                                                 /* isSuccessful= */ false,
167                                                 shouldRetry);
168 
169                                 jobFinished(params, shouldRetry);
170                                 sendBroadcastIntentIfEnabled();
171                             }
172                         },
173                         AdServicesExecutors.getLightWeightExecutor());
174 
175         return true;
176     }
177 
178     @Override
onStopJob(JobParameters params)179     public boolean onStopJob(JobParameters params) {
180         LoggerFactory.getFledgeLogger().d("BackgroundKeyFetchJobService.onStopJob");
181         BackgroundKeyFetchWorker.getInstance().stopWork();
182 
183         boolean shouldRetry = true;
184 
185         AdServicesJobServiceLogger.getInstance()
186                 .recordOnStopJob(
187                         params, FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB_ID, shouldRetry);
188         return shouldRetry;
189     }
190 
skipAndCancelKeyFetchJob(final JobParameters params, int skipReason)191     private boolean skipAndCancelKeyFetchJob(final JobParameters params, int skipReason) {
192         this.getSystemService(JobScheduler.class)
193                 .cancel(FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB_ID);
194 
195         AdServicesJobServiceLogger.getInstance()
196                 .recordJobSkipped(FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB_ID, skipReason);
197 
198         jobFinished(params, false);
199         return false;
200     }
201 
202     /**
203      * Attempts to schedule the Key Background Fetch as a singleton periodic job if it is not
204      * already scheduled.
205      *
206      * <p>The key fetch background job fetches fresh encryption key, persists them to
207      * EncryptionKeyDb and deletes expired keys.
208      */
209     // TODO(b/311183933): Remove passed in Context from static method.
210     @SuppressWarnings("AvoidStaticContext")
scheduleIfNeeded(Context context, Flags flags, boolean forceSchedule)211     public static void scheduleIfNeeded(Context context, Flags flags, boolean forceSchedule) {
212         if (!flags.getFledgeAuctionServerBackgroundKeyFetchJobEnabled()) {
213             LoggerFactory.getFledgeLogger()
214                     .v("Background key fetch is disabled; skipping schedule");
215             return;
216         }
217 
218         final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
219 
220         // Scheduling a job can be expensive, and forcing a schedule could interrupt a job that is
221         // already in progress
222         // TODO(b/221837833): Intelligently decide when to overwrite a scheduled job
223         if ((jobScheduler.getPendingJob(FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB_ID) == null)
224                 || forceSchedule) {
225             schedule(context, flags);
226             LoggerFactory.getFledgeLogger().d("Scheduled Background Key Fetch job");
227         } else {
228             LoggerFactory.getFledgeLogger()
229                     .v("Background Key Fetch job already scheduled, skipping reschedule");
230         }
231     }
232 
233     /**
234      * Actually schedules the Background Key Fetch as a singleton periodic job.
235      *
236      * <p>Split out from {@link #scheduleIfNeeded(Context, Flags, boolean)} for mockable testing
237      * without pesky permissions.
238      */
239     // TODO(b/311183933): Remove passed in Context from static method.
240     @SuppressWarnings("AvoidStaticContext")
241     @VisibleForTesting
schedule(Context context, Flags flags)242     protected static void schedule(Context context, Flags flags) {
243         if (!flags.getFledgeAuctionServerBackgroundKeyFetchJobEnabled()) {
244             LoggerFactory.getFledgeLogger()
245                     .v("Background key fetch is disabled; skipping schedule");
246             return;
247         }
248 
249         final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
250         final JobInfo job =
251                 new JobInfo.Builder(
252                                 FLEDGE_AD_SELECTION_ENCRYPTION_KEY_FETCH_JOB_ID,
253                                 new ComponentName(context, BackgroundKeyFetchJobService.class))
254                         .setRequiresBatteryNotLow(true)
255                         .setRequiresDeviceIdle(true)
256                         .setPeriodic(
257                                 flags.getFledgeAuctionServerBackgroundKeyFetchJobPeriodMs(),
258                                 flags.getFledgeAuctionServerBackgroundKeyFetchJobFlexMs())
259                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
260                         .setPersisted(true)
261                         .build();
262         jobScheduler.schedule(job);
263     }
264 
sendBroadcastIntentIfEnabled()265     private void sendBroadcastIntentIfEnabled() {
266         if (DebugFlags.getInstance().getFledgeBackgroundKeyFetchCompleteBroadcastEnabled()) {
267             Context context = ApplicationContextSingleton.get();
268             LogUtil.d(
269                     "Sending a broadcast intent with intent action: "
270                             + ACTION_BACKGROUND_KEY_FETCH_COMPLETE);
271             context.sendBroadcast(new Intent(ACTION_BACKGROUND_KEY_FETCH_COMPLETE));
272         }
273     }
274 }
275