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