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