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