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 
17 package com.android.adservices.service.kanon;
18 
19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON;
20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_USER_CONSENT_REVOKED;
21 import static com.android.adservices.spe.AdServicesJobInfo.FLEDGE_KANON_SIGN_JOIN_BACKGROUND_JOB;
22 
23 import android.annotation.RequiresApi;
24 import android.annotation.SuppressLint;
25 import android.app.job.JobInfo;
26 import android.app.job.JobParameters;
27 import android.app.job.JobScheduler;
28 import android.app.job.JobService;
29 import android.content.ComponentName;
30 import android.content.Context;
31 import android.os.Build;
32 
33 import androidx.annotation.NonNull;
34 
35 import com.android.adservices.LoggerFactory;
36 import com.android.adservices.concurrency.AdServicesExecutors;
37 import com.android.adservices.service.Flags;
38 import com.android.adservices.service.FlagsFactory;
39 import com.android.adservices.service.common.compat.ServiceCompatUtils;
40 import com.android.adservices.service.consent.AdServicesApiType;
41 import com.android.adservices.service.consent.ConsentManager;
42 import com.android.adservices.spe.AdServicesJobServiceLogger;
43 
44 import com.google.common.util.concurrent.FluentFuture;
45 import com.google.common.util.concurrent.FutureCallback;
46 import com.google.common.util.concurrent.Futures;
47 
48 /** Background job for making sign/join calls. */
49 @SuppressLint("LineLength")
50 @RequiresApi(Build.VERSION_CODES.S)
51 public class KAnonSignJoinBackgroundJobService extends JobService {
52 
53     @Override
onStartJob(JobParameters params)54     public boolean onStartJob(JobParameters params) {
55         Flags flags = FlagsFactory.getFlags();
56 
57         // Always ensure that the first thing this job does is check if it should be running, and
58         // cancel itself if it's not supposed to be.
59         if (ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(this)) {
60             return skipAndCancelBackgroundJob(
61                     params,
62                     AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON,
63                     true);
64         }
65         if (!flags.getFledgeKAnonBackgroundProcessEnabled()) {
66             return skipAndCancelBackgroundJob(
67                     params,
68                     AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON,
69                     true);
70         }
71         // Skip the execution and cancel the job if user consent is revoked.
72         // Use the per-API consent with GA UX.
73         if (!ConsentManager.getInstance().getConsent(AdServicesApiType.FLEDGE).isGiven()) {
74             LoggerFactory.getFledgeLogger()
75                     .d("User Consent is revoked ; skipping and cancelling job");
76             return skipAndCancelBackgroundJob(
77                     params,
78                     AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_USER_CONSENT_REVOKED,
79                     /* doRecord= */ true);
80         }
81 
82         Futures.addCallback(
83                 doSignJoinBackgroundJob(),
84                 new FutureCallback<Void>() {
85                     @Override
86                     public void onSuccess(Void result) {
87                         boolean shouldRetry = false;
88                         AdServicesJobServiceLogger.getInstance()
89                                 .recordJobFinished(
90                                         FLEDGE_KANON_SIGN_JOIN_BACKGROUND_JOB.getJobId(),
91                                         /* isSuccessful= */ true,
92                                         shouldRetry);
93                         jobFinished(params, shouldRetry);
94                     }
95 
96                     @Override
97                     public void onFailure(Throwable t) {
98                         boolean shouldRetry = false;
99                         AdServicesJobServiceLogger.getInstance()
100                                 .recordJobFinished(
101                                         FLEDGE_KANON_SIGN_JOIN_BACKGROUND_JOB.getJobId(),
102                                         /* isSuccessful= */ false,
103                                         shouldRetry);
104                         jobFinished(params, shouldRetry);
105                     }
106                 },
107                 AdServicesExecutors.getBackgroundExecutor());
108         return true;
109     }
110 
111     @Override
onStopJob(JobParameters params)112     public boolean onStopJob(JobParameters params) {
113         AdServicesJobServiceLogger.getInstance()
114                 .recordOnStopJob(params, FLEDGE_KANON_SIGN_JOIN_BACKGROUND_JOB.getJobId(), false);
115         KAnonSignJoinBackgroundJobWorker.getInstance(this).stopWork();
116         return false;
117     }
118 
skipAndCancelBackgroundJob( final JobParameters params, int skipReason, boolean doRecord)119     private boolean skipAndCancelBackgroundJob(
120             final JobParameters params, int skipReason, boolean doRecord) {
121         JobScheduler jobScheduler = this.getSystemService(JobScheduler.class);
122         if (jobScheduler != null) {
123             jobScheduler.cancel(FLEDGE_KANON_SIGN_JOIN_BACKGROUND_JOB.getJobId());
124         }
125 
126         if (doRecord) {
127             AdServicesJobServiceLogger.getInstance()
128                     .recordJobSkipped(FLEDGE_KANON_SIGN_JOIN_BACKGROUND_JOB.getJobId(), skipReason);
129         }
130 
131         // Tell the JobScheduler that the job has completed and does not need to be
132         // rescheduled.
133         jobFinished(params, false);
134 
135         // Returning false means that this job has completed its work.
136         return false;
137     }
138 
139     /**
140      * Attempts to schedule the KAnon Sign join background job as a periodic job if it is not
141      * already scheduled.
142      */
scheduleIfNeeded(@onNull Context context, boolean forceSchedule)143     public static void scheduleIfNeeded(@NonNull Context context, boolean forceSchedule) {
144         final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
145         Flags flags = FlagsFactory.getFlags();
146         if (jobScheduler.getPendingJob(FLEDGE_KANON_SIGN_JOIN_BACKGROUND_JOB.getJobId()) == null
147                 || forceSchedule) {
148             schedule(context, flags);
149         }
150     }
151 
152     /** Schedules the kanon sign join background job as a periodic job. */
schedule(Context context, Flags flags)153     public static void schedule(Context context, Flags flags) {
154         if (!flags.getFledgeKAnonBackgroundProcessEnabled()) {
155             return;
156         }
157         final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
158         final JobInfo job =
159                 new JobInfo.Builder(
160                                 FLEDGE_KANON_SIGN_JOIN_BACKGROUND_JOB.getJobId(),
161                                 new ComponentName(context, KAnonSignJoinBackgroundJobService.class))
162                         .setRequiresBatteryNotLow(
163                                 flags.getFledgeKAnonBackgroundJobRequiresBatteryNotLow())
164                         .setRequiresDeviceIdle(
165                                 flags.getFledgeKAnonBackgroundJobRequiresDeviceIdle())
166                         .setPeriodic(flags.getFledgeKAnonBackgroundProcessTimePeriodInMs())
167                         .setRequiredNetworkType(flags.getFledgeKanonBackgroundJobConnectionType())
168                         .setPersisted(true)
169                         .build();
170         jobScheduler.schedule(job);
171     }
172 
doSignJoinBackgroundJob()173     private FluentFuture<Void> doSignJoinBackgroundJob() {
174         return KAnonSignJoinBackgroundJobWorker.getInstance(this).runSignJoinBackgroundProcess();
175     }
176 }
177