• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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