• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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.ondevicepersonalization.services.download;
18 
19 import static com.android.adservices.shared.proto.JobPolicy.BatteryType.BATTERY_TYPE_REQUIRE_NOT_LOW;
20 import static com.android.adservices.shared.proto.JobPolicy.NetworkType.NETWORK_TYPE_NONE;
21 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
22 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_ENABLED;
23 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.DOWNLOAD_PROCESSING_TASK_JOB_ID;
24 
25 import android.content.Context;
26 
27 import com.android.adservices.shared.proto.JobPolicy;
28 import com.android.adservices.shared.spe.framework.ExecutionResult;
29 import com.android.adservices.shared.spe.framework.ExecutionRuntimeParameters;
30 import com.android.adservices.shared.spe.framework.JobWorker;
31 import com.android.adservices.shared.spe.scheduling.BackoffPolicy;
32 import com.android.adservices.shared.spe.scheduling.JobSpec;
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
35 import com.android.ondevicepersonalization.services.FlagsFactory;
36 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
37 import com.android.ondevicepersonalization.services.download.mdd.MobileDataDownloadFactory;
38 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
39 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobScheduler;
40 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobServiceFactory;
41 
42 import com.google.android.libraries.mobiledatadownload.MobileDataDownload;
43 import com.google.common.util.concurrent.Futures;
44 import com.google.common.util.concurrent.ListenableFuture;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 
49 /**
50  * JobService to handle the processing of the downloaded vendor data
51  */
52 public final class OnDevicePersonalizationDownloadProcessingJob implements JobWorker {
53     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
54     private static final String TAG = "OnDevicePersonalizationDownloadProcessingJob";
55 
56     @Override
getExecutionFuture(Context context, ExecutionRuntimeParameters executionRuntimeParameters)57     public ListenableFuture<ExecutionResult> getExecutionFuture(Context context,
58             ExecutionRuntimeParameters executionRuntimeParameters) {
59         ListenableFuture<List<ListenableFuture<Void>>> outerFeature =
60                 Futures.submit(
61                         () -> {
62                             List<ListenableFuture<Void>> innerFeatures = new ArrayList<>();
63                             // Processing installed packages
64                             for (String packageName : AppManifestConfigHelper.getOdpPackages(
65                                     context, /* enrolledOnly= */ true)) {
66                                 innerFeatures.add(Futures.submitAsync(
67                                         new OnDevicePersonalizationDataProcessingAsyncCallable(
68                                                 packageName, context),
69                                         OnDevicePersonalizationExecutors.getBackgroundExecutor()));
70                             }
71                             return innerFeatures;
72                         },
73                         OnDevicePersonalizationExecutors.getBackgroundExecutor());
74 
75         // Handling task completion asynchronously
76         return Futures.transformAsync(
77                 outerFeature,
78                 innerFutures -> Futures.whenAllComplete(innerFutures).call(() -> {
79                     boolean allSuccess = true;
80                     int successTaskCount = 0;
81                     int failureTaskCount = 0;
82                     for (ListenableFuture<Void> future : innerFutures) {
83                         try {
84                             future.get();
85                             successTaskCount++;
86                         } catch (Exception e) {
87                             sLogger.e(e, TAG + ": Error processing future");
88                             failureTaskCount++;
89                             allSuccess = false;
90                         }
91                     }
92                     sLogger.d(TAG + ": all download processing tasks finished, %d succeeded,"
93                             + " %d failed", successTaskCount, failureTaskCount);
94                     // Manually trigger MDD garbage collection after finishing processing all
95                     // downloads.
96                     MobileDataDownload mdd = MobileDataDownloadFactory.getMdd(context);
97                     var unused = mdd.collectGarbage();
98 
99                     return allSuccess ? ExecutionResult.SUCCESS
100                             : ExecutionResult.FAILURE_WITHOUT_RETRY;
101                 }, OnDevicePersonalizationExecutors.getLightweightExecutor()),
102                 OnDevicePersonalizationExecutors.getLightweightExecutor()
103         );
104     }
105 
106     @Override
getJobEnablementStatus()107     public int getJobEnablementStatus() {
108         if (FlagsFactory.getFlags().getGlobalKillSwitch()) {
109             sLogger.d(TAG + ": GlobalKillSwitch enabled, skip execution of "
110                     + "OnDevicePersonalizationDownloadProcessingJob.");
111             return JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
112         }
113         if (!FlagsFactory.getFlags().getSpeOnOdpDownloadProcessingJobEnabled()) {
114             sLogger.d(TAG + ": download processing is disabled; skipping and cancelling job");
115             return JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON;
116         }
117         return JOB_ENABLED_STATUS_ENABLED;
118     }
119 
120     @Override
getBackoffPolicy()121     public BackoffPolicy getBackoffPolicy() {
122         return new BackoffPolicy.Builder().setShouldRetryOnExecutionStop(true).build();
123     }
124 
125     /** Schedules a unique instance of {@link OnDevicePersonalizationDownloadProcessingJob}. */
schedule(Context context)126     public static void schedule(Context context) {
127         // If SPE is not enabled, force to schedule the job with the old JobService.
128         if (!FlagsFactory.getFlags().getSpeOnOdpDownloadProcessingJobEnabled()) {
129             sLogger.d("SPE is not enabled. Schedule the job with "
130                     + "OnDevicePersonalizationDownloadProcessingJobService.");
131 
132             int resultCode = OnDevicePersonalizationDownloadProcessingJobService.schedule(
133                     context, /* forceSchedule */ false);
134             OdpJobServiceFactory.getInstance(context)
135                     .getJobSchedulingLogger()
136                     .recordOnSchedulingLegacy(DOWNLOAD_PROCESSING_TASK_JOB_ID, resultCode);
137 
138             return;
139         }
140 
141         OdpJobScheduler.getInstance(context).schedule(context, createDefaultJobSpec());
142     }
143 
144     @VisibleForTesting
createDefaultJobSpec()145     static JobSpec createDefaultJobSpec() {
146         JobPolicy jobPolicy =
147                 JobPolicy.newBuilder()
148                         .setJobId(DOWNLOAD_PROCESSING_TASK_JOB_ID)
149                         .setRequireDeviceIdle(true)
150                         .setBatteryType(BATTERY_TYPE_REQUIRE_NOT_LOW)
151                         .setRequireStorageNotLow(true)
152                         .setNetworkType(NETWORK_TYPE_NONE)
153                         .setIsPersisted(true)
154                         .build();
155 
156         return new JobSpec.Builder(jobPolicy).build();
157     }
158 }
159