1 /* 2 * Copyright (C) 2022 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.reporting; 18 19 import static com.android.adservices.spe.AdservicesJobInfo.MEASUREMENT_DEBUG_REPORT_API_JOB; 20 import static com.android.adservices.spe.AdservicesJobInfo.MEASUREMENT_DEBUG_REPORT_JOB; 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.PersistableBundle; 29 30 import com.android.adservices.LogUtil; 31 import com.android.adservices.concurrency.AdServicesExecutors; 32 import com.android.adservices.data.enrollment.EnrollmentDao; 33 import com.android.adservices.data.measurement.DatastoreManager; 34 import com.android.adservices.data.measurement.DatastoreManagerFactory; 35 import com.android.adservices.service.FlagsFactory; 36 import com.android.adservices.service.common.compat.ServiceCompatUtils; 37 import com.android.internal.annotations.VisibleForTesting; 38 39 import java.util.concurrent.Executor; 40 41 /** 42 * Main service for scheduling debug reporting jobs. The actual job execution logic is part of 43 * {@link DebugReportingJobHandler }, {@link EventReportingJobHandler } and {@link 44 * AggregateReportingJobHandler} 45 */ 46 public final class DebugReportingJobService extends JobService { 47 48 public static final String EXTRA_BUNDLE_IS_DEBUG_REPORT_API = 49 "EXTRA_BUNDLE_IS_DEBUG_REPORT_API"; 50 private static final long DEBUG_REPORT_API_JOB_DELAY_MS = 3600 * 1000L; 51 private static final Executor sBlockingExecutor = AdServicesExecutors.getBlockingExecutor(); 52 53 @Override onCreate()54 public void onCreate() { 55 super.onCreate(); 56 } 57 58 @Override onStartJob(JobParameters params)59 public boolean onStartJob(JobParameters params) { 60 // Always ensure that the first thing this job does is check if it should be running, and 61 // cancel itself if it's not supposed to be. 62 if (ServiceCompatUtils.shouldDisableExtServicesJobOnTPlus(this)) { 63 LogUtil.d( 64 "Disabling DebugReportingJobService job because it's running in ExtServices on" 65 + " T+"); 66 return skipAndCancelBackgroundJob(params); 67 } 68 69 if (FlagsFactory.getFlags().getMeasurementJobDebugReportingKillSwitch()) { 70 LogUtil.e("DebugReportingJobService is disabled"); 71 return skipAndCancelBackgroundJob(params); 72 } 73 boolean isDebugReportApi = params.getExtras().getBoolean(EXTRA_BUNDLE_IS_DEBUG_REPORT_API); 74 75 LogUtil.d("DebugReportingJobService.onStartJob: isDebugReportApi " + isDebugReportApi); 76 sBlockingExecutor.execute( 77 () -> { 78 sendReports(isDebugReportApi); 79 jobFinished(params, false); 80 }); 81 return true; 82 } 83 84 @Override onStopJob(JobParameters params)85 public boolean onStopJob(JobParameters params) { 86 LogUtil.d("DebugReportingJobService.onStopJob"); 87 return true; 88 } 89 90 /** Schedules {@link DebugReportingJobService} */ 91 @VisibleForTesting schedule(Context context, JobScheduler jobScheduler, boolean isDebugReportApi)92 static void schedule(Context context, JobScheduler jobScheduler, boolean isDebugReportApi) { 93 final JobInfo job = 94 new JobInfo.Builder( 95 getJobId(isDebugReportApi), 96 new ComponentName(context, DebugReportingJobService.class)) 97 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) 98 .setOverrideDeadline(getJobDelay(isDebugReportApi)) 99 .setExtras(getBundle(isDebugReportApi)) 100 .build(); 101 jobScheduler.schedule(job); 102 } 103 104 /** 105 * Schedule Debug Reporting Job if it is not already scheduled 106 * 107 * @param context the context 108 * @param forceSchedule flag to indicate whether to force rescheduling the job. 109 * @param isDebugReportApi flag to indicate whether caller is DebugReportAPI. 110 */ scheduleIfNeeded( Context context, boolean forceSchedule, boolean isDebugReportApi)111 public static void scheduleIfNeeded( 112 Context context, boolean forceSchedule, boolean isDebugReportApi) { 113 if (FlagsFactory.getFlags().getMeasurementJobDebugReportingKillSwitch()) { 114 LogUtil.d("DebugReportingJobService is disabled, skip scheduling"); 115 return; 116 } 117 118 final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 119 if (jobScheduler == null) { 120 LogUtil.e("JobScheduler not found"); 121 return; 122 } 123 124 final JobInfo job = jobScheduler.getPendingJob(getJobId(isDebugReportApi)); 125 // Schedule if it hasn't been scheduled already or force rescheduling 126 if (job == null || forceSchedule) { 127 schedule(context, jobScheduler, isDebugReportApi); 128 LogUtil.d("Scheduled DebugReportingJobService: isDebugReportApi " + isDebugReportApi); 129 } else { 130 LogUtil.d("DebugReportingJobService already scheduled, skipping reschedule"); 131 } 132 } 133 skipAndCancelBackgroundJob(final JobParameters params)134 private boolean skipAndCancelBackgroundJob(final JobParameters params) { 135 final JobScheduler jobScheduler = this.getSystemService(JobScheduler.class); 136 if (jobScheduler != null) { 137 jobScheduler.cancel( 138 getJobId(params.getExtras().getBoolean(EXTRA_BUNDLE_IS_DEBUG_REPORT_API))); 139 } 140 141 // Tell the JobScheduler that the job has completed and does not need to be rescheduled. 142 jobFinished(params, false); 143 144 // Returning false means that this job has completed its work. 145 return false; 146 } 147 sendReports(boolean isDebugReportApi)148 private void sendReports(boolean isDebugReportApi) { 149 EnrollmentDao enrollmentDao = EnrollmentDao.getInstance(getApplicationContext()); 150 DatastoreManager datastoreManager = 151 DatastoreManagerFactory.getDatastoreManager(getApplicationContext()); 152 if (isDebugReportApi) { 153 new DebugReportingJobHandler(enrollmentDao, datastoreManager) 154 .performScheduledPendingReports(); 155 } else { 156 new EventReportingJobHandler( 157 enrollmentDao, datastoreManager, ReportingStatus.UploadMethod.UNKNOWN) 158 .setIsDebugInstance(true) 159 .performScheduledPendingReportsInWindow(0, 0); 160 new AggregateReportingJobHandler( 161 enrollmentDao, datastoreManager, ReportingStatus.UploadMethod.UNKNOWN) 162 .setIsDebugInstance(true) 163 .performScheduledPendingReportsInWindow(0, 0); 164 } 165 } 166 getJobId(boolean isDebugReportApi)167 private static int getJobId(boolean isDebugReportApi) { 168 if (isDebugReportApi) { 169 return MEASUREMENT_DEBUG_REPORT_API_JOB.getJobId(); 170 } else { 171 return MEASUREMENT_DEBUG_REPORT_JOB.getJobId(); 172 } 173 } 174 getJobDelay(boolean isDebugReportApi)175 private static long getJobDelay(boolean isDebugReportApi) { 176 if (isDebugReportApi) { 177 return DEBUG_REPORT_API_JOB_DELAY_MS; 178 } else { 179 return 1L; 180 } 181 } 182 getBundle(boolean isDebugReportApi)183 private static PersistableBundle getBundle(boolean isDebugReportApi) { 184 PersistableBundle bundle = new PersistableBundle(); 185 bundle.putBoolean(EXTRA_BUNDLE_IS_DEBUG_REPORT_API, isDebugReportApi); 186 return bundle; 187 } 188 } 189