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.service.common.AdPackageDenyResolver; 43 import com.android.adservices.shared.proto.JobPolicy; 44 import com.android.adservices.shared.proto.JobPolicy.NetworkType; 45 import com.android.adservices.shared.spe.JobServiceConstants.JobSchedulingResultCode; 46 import com.android.adservices.shared.spe.framework.ExecutionResult; 47 import com.android.adservices.shared.spe.framework.ExecutionRuntimeParameters; 48 import com.android.adservices.shared.spe.framework.JobWorker; 49 import com.android.adservices.shared.spe.logging.JobSchedulingLogger; 50 import com.android.adservices.shared.spe.scheduling.JobSpec; 51 import com.android.adservices.spe.AdServicesJobScheduler; 52 import com.android.adservices.spe.AdServicesJobServiceFactory; 53 import com.android.internal.annotations.VisibleForTesting; 54 55 import com.google.android.libraries.mobiledatadownload.Flags; 56 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures; 57 import com.google.common.util.concurrent.FluentFuture; 58 import com.google.common.util.concurrent.ListenableFuture; 59 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 if (FlagsFactory.getFlags().getEnablePackageDenyJobOnMddDownload()) { 152 ListenableFuture<AdPackageDenyResolver.PackageDenyMddProcessStatus> 153 unusedFuturePackageDenyMddProcessStatus = 154 AdPackageDenyResolver.getInstance() 155 .loadDenyDataFromMdd(); 156 } 157 return SUCCESS; 158 }, 159 AdServicesExecutors.getBlockingExecutor()); 160 } 161 162 /** Schedules all MDD background jobs. */ scheduleAllMddJobs()163 public static void scheduleAllMddJobs() { 164 if (!FlagsFactory.getFlags().getSpeOnPilotJobsEnabled()) { 165 int resultCode = MddJobService.scheduleIfNeeded(/* forceSchedule= */ false); 166 167 logJobSchedulingLegacy(resultCode); 168 return; 169 } 170 171 Flags mddFlags = new Flags() {}; 172 AdServicesJobScheduler scheduler = AdServicesJobScheduler.getInstance(); 173 174 // The jobs will still be rescheduled even if they were scheduled by MddJobService with same 175 // constraints, because the component/service is different anyway (AdServicesJobService vs. 176 // MddJobService). 177 scheduleMaintenanceJob(scheduler, mddFlags); 178 scheduleChargingJob(scheduler, mddFlags); 179 scheduleCellularChargingJob(scheduler, mddFlags); 180 scheduleWifiChargingJob(scheduler, mddFlags); 181 } 182 183 /** 184 * Unscheduled all MDD background jobs. 185 * 186 * @param jobScheduler Job scheduler to cancel the jobs 187 */ unscheduleAllJobs(JobScheduler jobScheduler)188 public static void unscheduleAllJobs(JobScheduler jobScheduler) { 189 LogUtil.d("Cancelling all MDD jobs scheduled by SPE....."); 190 191 jobScheduler.cancel(MDD_MAINTENANCE_PERIODIC_TASK_JOB.getJobId()); 192 jobScheduler.cancel(MDD_CHARGING_PERIODIC_TASK_JOB.getJobId()); 193 jobScheduler.cancel(MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB.getJobId()); 194 jobScheduler.cancel(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB.getJobId()); 195 196 LogUtil.d("All MDD jobs scheduled by SPE are cancelled."); 197 } 198 199 @VisibleForTesting createJobSpec( String mddTag, long periodicalIntervalMs, NetworkState networkState)200 static JobSpec createJobSpec( 201 String mddTag, long periodicalIntervalMs, NetworkState networkState) { 202 // We use Extra to pass the MDD Task Tag. 203 PersistableBundle extras = new PersistableBundle(); 204 extras.putString(KEY_MDD_TASK_TAG, mddTag); 205 206 JobPolicy jobPolicy = 207 JobPolicy.newBuilder() 208 .setJobId(getMddTaskJobId(mddTag)) 209 .setPeriodicJobParams( 210 JobPolicy.PeriodicJobParams.newBuilder() 211 .setPeriodicIntervalMs(periodicalIntervalMs) 212 .build()) 213 .setNetworkType(getNetworkConstraints(networkState)) 214 .setBatteryType(BATTERY_TYPE_REQUIRE_CHARGING) 215 .setIsPersisted(true) 216 .build(); 217 218 return new JobSpec.Builder(jobPolicy).setExtras(extras).build(); 219 } 220 221 @VisibleForTesting logJobSchedulingLegacy(@obSchedulingResultCode int resultCode)222 static void logJobSchedulingLegacy(@JobSchedulingResultCode int resultCode) { 223 JobSchedulingLogger logger = 224 AdServicesJobServiceFactory.getInstance().getJobSchedulingLogger(); 225 226 logger.recordOnSchedulingLegacy(MDD_MAINTENANCE_PERIODIC_TASK_JOB.getJobId(), resultCode); 227 logger.recordOnSchedulingLegacy(MDD_CHARGING_PERIODIC_TASK_JOB.getJobId(), resultCode); 228 logger.recordOnSchedulingLegacy( 229 MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB.getJobId(), resultCode); 230 logger.recordOnSchedulingLegacy(MDD_WIFI_CHARGING_PERIODIC_TASK_JOB.getJobId(), resultCode); 231 } 232 scheduleMaintenanceJob(AdServicesJobScheduler scheduler, Flags flags)233 private static void scheduleMaintenanceJob(AdServicesJobScheduler scheduler, Flags flags) { 234 scheduler.schedule( 235 createJobSpec( 236 MAINTENANCE_PERIODIC_TASK, 237 flags.maintenanceGcmTaskPeriod() * MILLISECONDS_PER_SECOND, 238 NETWORK_STATE_ANY)); 239 } 240 scheduleChargingJob(AdServicesJobScheduler scheduler, Flags flags)241 private static void scheduleChargingJob(AdServicesJobScheduler scheduler, Flags flags) { 242 scheduler.schedule( 243 createJobSpec( 244 CHARGING_PERIODIC_TASK, 245 flags.chargingGcmTaskPeriod() * MILLISECONDS_PER_SECOND, 246 NETWORK_STATE_ANY)); 247 } 248 scheduleCellularChargingJob(AdServicesJobScheduler scheduler, Flags flags)249 private static void scheduleCellularChargingJob(AdServicesJobScheduler scheduler, Flags flags) { 250 scheduler.schedule( 251 createJobSpec( 252 CELLULAR_CHARGING_PERIODIC_TASK, 253 flags.cellularChargingGcmTaskPeriod() * MILLISECONDS_PER_SECOND, 254 NETWORK_STATE_CONNECTED)); 255 } 256 scheduleWifiChargingJob(AdServicesJobScheduler scheduler, Flags flags)257 private static void scheduleWifiChargingJob(AdServicesJobScheduler scheduler, Flags flags) { 258 scheduler.schedule( 259 createJobSpec( 260 WIFI_CHARGING_PERIODIC_TASK, 261 flags.wifiChargingGcmTaskPeriod() * MILLISECONDS_PER_SECOND, 262 NETWORK_STATE_UNMETERED)); 263 } 264 265 // Maps from the MDD-supplied NetworkState to the JobInfo equivalent int code. getNetworkConstraints(NetworkState networkState)266 private static NetworkType getNetworkConstraints(NetworkState networkState) { 267 switch (networkState) { 268 case NETWORK_STATE_ANY: 269 // Network not required. 270 return NetworkType.NETWORK_TYPE_NONE; 271 case NETWORK_STATE_CONNECTED: 272 // Metered or unmetered network available. 273 return NetworkType.NETWORK_TYPE_ANY; 274 case NETWORK_STATE_UNMETERED: 275 default: 276 return NetworkType.NETWORK_TYPE_UNMETERED; 277 } 278 } 279 280 // Convert from MDD Task Tag to the corresponding JobService ID. getMddTaskJobId(String mddTag)281 private static int getMddTaskJobId(String mddTag) { 282 switch (mddTag) { 283 case MAINTENANCE_PERIODIC_TASK: 284 return MDD_MAINTENANCE_PERIODIC_TASK_JOB.getJobId(); 285 case CHARGING_PERIODIC_TASK: 286 return MDD_CHARGING_PERIODIC_TASK_JOB.getJobId(); 287 case CELLULAR_CHARGING_PERIODIC_TASK: 288 return MDD_CELLULAR_CHARGING_PERIODIC_TASK_JOB.getJobId(); 289 default: 290 return MDD_WIFI_CHARGING_PERIODIC_TASK_JOB.getJobId(); 291 } 292 } 293 getMddTag(ExecutionRuntimeParameters params)294 private String getMddTag(ExecutionRuntimeParameters params) { 295 PersistableBundle extras = params.getExtras(); 296 if (null == extras) { 297 // TODO(b/279231865): Log CEL with SPE_FAIL_TO_FIND_MDD_TASKS_TAG. 298 throw new IllegalArgumentException("Can't find MDD Tasks Tag!"); 299 } 300 return extras.getString(KEY_MDD_TASK_TAG); 301 } 302 } 303