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.download; 18 19 import static com.android.adservices.download.MddJob.NetworkState.NETWORK_STATE_ANY; 20 import static com.android.adservices.download.MddJob.NetworkState.NETWORK_STATE_CONNECTED; 21 import static com.android.adservices.download.MddJob.NetworkState.NETWORK_STATE_UNMETERED; 22 import static com.android.adservices.shared.proto.JobPolicy.BatteryType.BATTERY_TYPE_REQUIRE_CHARGING; 23 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON; 24 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_ENABLED; 25 import static com.android.adservices.shared.spe.framework.ExecutionResult.SUCCESS; 26 import static com.android.adservices.spe.AdServicesJobInfo.MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB; 27 import static com.android.adservices.spe.AdServicesJobInfo.MDD_CHARGING_PERIODIC_TASK_JOB; 28 import static com.android.adservices.spe.AdServicesJobInfo.MDD_MAINTENANCE_PERIODIC_TASK_JOB; 29 import static com.android.adservices.spe.AdServicesJobInfo.MDD_WIFI_CHARGING_PERIODIC_TASK_JOB; 30 31 import android.app.job.JobScheduler; 32 import android.content.Context; 33 import android.os.Build; 34 import android.os.PersistableBundle; 35 36 import androidx.annotation.RequiresApi; 37 38 import com.android.adservices.LogUtil; 39 import com.android.adservices.concurrency.AdServicesExecutors; 40 import com.android.adservices.download.EnrollmentDataDownloadManager.DownloadStatus; 41 import com.android.adservices.service.FlagsFactory; 42 import com.android.adservices.shared.proto.JobPolicy; 43 import com.android.adservices.shared.proto.JobPolicy.NetworkType; 44 import com.android.adservices.shared.spe.JobServiceConstants.JobSchedulingResultCode; 45 import com.android.adservices.shared.spe.framework.ExecutionResult; 46 import com.android.adservices.shared.spe.framework.ExecutionRuntimeParameters; 47 import com.android.adservices.shared.spe.framework.JobWorker; 48 import com.android.adservices.shared.spe.logging.JobSchedulingLogger; 49 import com.android.adservices.shared.spe.scheduling.JobSpec; 50 import com.android.adservices.spe.AdServicesJobScheduler; 51 import com.android.adservices.spe.AdServicesJobServiceFactory; 52 import com.android.internal.annotations.VisibleForTesting; 53 54 import com.google.android.libraries.mobiledatadownload.Flags; 55 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures; 56 import com.google.common.util.concurrent.FluentFuture; 57 import com.google.common.util.concurrent.ListenableFuture; 58 59 // TODO(b/269798827): Enable for R. 60 // TODO(b/331291972): Refactor this class. 61 @RequiresApi(Build.VERSION_CODES.S) 62 public final class MddJob implements JobWorker { 63 /** 64 * Tag for daily mdd maintenance task, that *should* be run once and only once every 24 hours. 65 * 66 * <p>By default, this task runs on charging. 67 */ 68 @VisibleForTesting 69 static final String MAINTENANCE_PERIODIC_TASK = "MDD.MAINTENANCE.PERIODIC.GCM.TASK"; 70 71 /** 72 * Tag for mdd task that doesn't require any network. This is used to perform some routine 73 * operation that do not require network, in case a device doesn't connect to any network for a 74 * long time. 75 * 76 * <p>By default, this task runs on charging once every 6 hours. 77 */ 78 @VisibleForTesting static final String CHARGING_PERIODIC_TASK = "MDD.CHARGING.PERIODIC.TASK"; 79 80 /** 81 * Tag for mdd task that runs on cellular network. This is used to primarily download file 82 * groups that can be downloaded on cellular network. 83 * 84 * <p>By default, this task runs on charging once every 6 hours. This task can be skipped if 85 * nothing is downloaded on cellular. 86 */ 87 @VisibleForTesting 88 static final String CELLULAR_CHARGING_PERIODIC_TASK = "MDD.CELLULAR.CHARGING.PERIODIC.TASK"; 89 90 /** 91 * Tag for mdd task that runs on Wi-Fi network. This is used to primarily download file groups 92 * that can be downloaded only on Wi-Fi network. 93 * 94 * <p>By default, this task runs on charging once every 6 hours. This task can be skipped if 95 * nothing is restricted to Wi-Fi. 96 */ 97 @VisibleForTesting 98 static final String WIFI_CHARGING_PERIODIC_TASK = "MDD.WIFI.CHARGING.PERIODIC.TASK"; 99 100 @VisibleForTesting static final int MILLISECONDS_PER_SECOND = 1_000; 101 102 @VisibleForTesting static final String KEY_MDD_TASK_TAG = "mdd_task_tag"; 103 104 @VisibleForTesting 105 // Required network state of the device when to run the task. 106 enum NetworkState { 107 // Metered or unmetered network available. 108 NETWORK_STATE_CONNECTED, 109 110 // Unmetered network available. 111 NETWORK_STATE_UNMETERED, 112 113 // Network not required. 114 NETWORK_STATE_ANY, 115 } 116 117 @Override getJobEnablementStatus()118 public int getJobEnablementStatus() { 119 if (FlagsFactory.getFlags().getMddBackgroundTaskKillSwitch()) { 120 LogUtil.d("MDD background task is disabled, skipping and cancelling MddJobService"); 121 return JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON; 122 } 123 return JOB_ENABLED_STATUS_ENABLED; 124 } 125 126 @Override getExecutionFuture( Context context, ExecutionRuntimeParameters params)127 public ListenableFuture<ExecutionResult> getExecutionFuture( 128 Context context, ExecutionRuntimeParameters params) { 129 ListenableFuture<Void> handleTaskFuture = 130 PropagatedFutures.submitAsync( 131 () -> { 132 String mddTag = getMddTag(params); 133 134 return MobileDataDownloadFactory.getMdd(FlagsFactory.getFlags()) 135 .handleTask(mddTag); 136 }, 137 AdServicesExecutors.getBackgroundExecutor()); 138 139 return FluentFuture.from(handleTaskFuture) 140 .transform( 141 ignoredVoid -> { 142 // TODO(b/331285831): Handle unused return value. 143 // To suppress the lint error of future returning value is unused. 144 ListenableFuture<DownloadStatus> unusedFutureEnrollment = 145 EnrollmentDataDownloadManager.getInstance() 146 .readAndInsertEnrollmentDataFromMdd(); 147 ListenableFuture<EncryptionDataDownloadManager.DownloadStatus> 148 unusedFutureEncryption = 149 EncryptionDataDownloadManager.getInstance() 150 .readAndInsertEncryptionDataFromMdd(); 151 return SUCCESS; 152 }, 153 AdServicesExecutors.getBlockingExecutor()); 154 } 155 156 /** Schedules all MDD background jobs. */ scheduleAllMddJobs()157 public static void scheduleAllMddJobs() { 158 if (!FlagsFactory.getFlags().getSpeOnPilotJobsEnabled()) { 159 int resultCode = MddJobService.scheduleIfNeeded(/* forceSchedule= */ false); 160 161 logJobSchedulingLegacy(resultCode); 162 return; 163 } 164 165 Flags mddFlags = new Flags() {}; 166 AdServicesJobScheduler scheduler = AdServicesJobScheduler.getInstance(); 167 168 // The jobs will still be rescheduled even if they were scheduled by MddJobService with same 169 // constraints, because the component/service is different anyway (AdServicesJobService vs. 170 // MddJobService). 171 scheduleMaintenanceJob(scheduler, mddFlags); 172 scheduleChargingJob(scheduler, mddFlags); 173 scheduleCellularChargingJob(scheduler, mddFlags); 174 scheduleWifiChargingJob(scheduler, mddFlags); 175 } 176 177 /** 178 * Unscheduled all MDD background jobs. 179 * 180 * @param jobScheduler Job scheduler to cancel the jobs 181 */ unscheduleAllJobs(JobScheduler jobScheduler)182 public static void unscheduleAllJobs(JobScheduler jobScheduler) { 183 LogUtil.d("Cancelling all MDD jobs scheduled by SPE....."); 184 185 jobScheduler.cancel(MDD_MAINTENANCE_PERIODIC_TASK_JOB.getJobId()); 186 jobScheduler.cancel(MDD_CHARGING_PERIODIC_TASK_JOB.getJobId()); 187 jobScheduler.cancel(MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB.getJobId()); 188 jobScheduler.cancel(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB.getJobId()); 189 190 LogUtil.d("All MDD jobs scheduled by SPE are cancelled."); 191 } 192 193 @VisibleForTesting createJobSpec( String mddTag, long periodicalIntervalMs, NetworkState networkState)194 static JobSpec createJobSpec( 195 String mddTag, long periodicalIntervalMs, NetworkState networkState) { 196 // We use Extra to pass the MDD Task Tag. 197 PersistableBundle extras = new PersistableBundle(); 198 extras.putString(KEY_MDD_TASK_TAG, mddTag); 199 200 JobPolicy jobPolicy = 201 JobPolicy.newBuilder() 202 .setJobId(getMddTaskJobId(mddTag)) 203 .setPeriodicJobParams( 204 JobPolicy.PeriodicJobParams.newBuilder() 205 .setPeriodicIntervalMs(periodicalIntervalMs) 206 .build()) 207 .setNetworkType(getNetworkConstraints(networkState)) 208 .setBatteryType(BATTERY_TYPE_REQUIRE_CHARGING) 209 .setIsPersisted(true) 210 .build(); 211 212 return new JobSpec.Builder(jobPolicy).setExtras(extras).build(); 213 } 214 215 @VisibleForTesting logJobSchedulingLegacy(@obSchedulingResultCode int resultCode)216 static void logJobSchedulingLegacy(@JobSchedulingResultCode int resultCode) { 217 JobSchedulingLogger logger = 218 AdServicesJobServiceFactory.getInstance().getJobSchedulingLogger(); 219 220 logger.recordOnSchedulingLegacy(MDD_MAINTENANCE_PERIODIC_TASK_JOB.getJobId(), resultCode); 221 logger.recordOnSchedulingLegacy(MDD_CHARGING_PERIODIC_TASK_JOB.getJobId(), resultCode); 222 logger.recordOnSchedulingLegacy( 223 MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB.getJobId(), resultCode); 224 logger.recordOnSchedulingLegacy(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB.getJobId(), resultCode); 225 } 226 scheduleMaintenanceJob(AdServicesJobScheduler scheduler, Flags flags)227 private static void scheduleMaintenanceJob(AdServicesJobScheduler scheduler, Flags flags) { 228 scheduler.schedule( 229 createJobSpec( 230 MAINTENANCE_PERIODIC_TASK, 231 flags.maintenanceGcmTaskPeriod() * MILLISECONDS_PER_SECOND, 232 NETWORK_STATE_ANY)); 233 } 234 scheduleChargingJob(AdServicesJobScheduler scheduler, Flags flags)235 private static void scheduleChargingJob(AdServicesJobScheduler scheduler, Flags flags) { 236 scheduler.schedule( 237 createJobSpec( 238 CHARGING_PERIODIC_TASK, 239 flags.chargingGcmTaskPeriod() * MILLISECONDS_PER_SECOND, 240 NETWORK_STATE_ANY)); 241 } 242 scheduleCellularChargingJob(AdServicesJobScheduler scheduler, Flags flags)243 private static void scheduleCellularChargingJob(AdServicesJobScheduler scheduler, Flags flags) { 244 scheduler.schedule( 245 createJobSpec( 246 CELLULAR_CHARGING_PERIODIC_TASK, 247 flags.cellularChargingGcmTaskPeriod() * MILLISECONDS_PER_SECOND, 248 NETWORK_STATE_CONNECTED)); 249 } 250 scheduleWifiChargingJob(AdServicesJobScheduler scheduler, Flags flags)251 private static void scheduleWifiChargingJob(AdServicesJobScheduler scheduler, Flags flags) { 252 scheduler.schedule( 253 createJobSpec( 254 WIFI_CHARGING_PERIODIC_TASK, 255 flags.wifiChargingGcmTaskPeriod() * MILLISECONDS_PER_SECOND, 256 NETWORK_STATE_UNMETERED)); 257 } 258 259 // Maps from the MDD-supplied NetworkState to the JobInfo equivalent int code. getNetworkConstraints(NetworkState networkState)260 private static NetworkType getNetworkConstraints(NetworkState networkState) { 261 switch (networkState) { 262 case NETWORK_STATE_ANY: 263 // Network not required. 264 return NetworkType.NETWORK_TYPE_NONE; 265 case NETWORK_STATE_CONNECTED: 266 // Metered or unmetered network available. 267 return NetworkType.NETWORK_TYPE_ANY; 268 case NETWORK_STATE_UNMETERED: 269 default: 270 return NetworkType.NETWORK_TYPE_UNMETERED; 271 } 272 } 273 274 // Convert from MDD Task Tag to the corresponding JobService ID. getMddTaskJobId(String mddTag)275 private static int getMddTaskJobId(String mddTag) { 276 switch (mddTag) { 277 case MAINTENANCE_PERIODIC_TASK: 278 return MDD_MAINTENANCE_PERIODIC_TASK_JOB.getJobId(); 279 case CHARGING_PERIODIC_TASK: 280 return MDD_CHARGING_PERIODIC_TASK_JOB.getJobId(); 281 case CELLULAR_CHARGING_PERIODIC_TASK: 282 return MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB.getJobId(); 283 default: 284 return MDD_WIFI_CHARGING_PERIODIC_TASK_JOB.getJobId(); 285 } 286 } 287 getMddTag(ExecutionRuntimeParameters params)288 private String getMddTag(ExecutionRuntimeParameters params) { 289 PersistableBundle extras = params.getExtras(); 290 if (null == extras) { 291 // TODO(b/279231865): Log CEL with SPE_FAIL_TO_FIND_MDD_TASKS_TAG. 292 throw new IllegalArgumentException("Can't find MDD Tasks Tag!"); 293 } 294 return extras.getString(KEY_MDD_TASK_TAG); 295 } 296 } 297