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.ondevicepersonalization.services.maintenance; 18 19 import static android.content.pm.PackageManager.GET_META_DATA; 20 21 import static com.android.adservices.shared.proto.JobPolicy.BatteryType.BATTERY_TYPE_REQUIRE_NOT_LOW; 22 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON; 23 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_ENABLED; 24 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.MAINTENANCE_TASK_JOB_ID; 25 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.pm.PackageInfo; 29 import android.content.pm.PackageManager; 30 31 import com.android.adservices.shared.proto.JobPolicy; 32 import com.android.adservices.shared.spe.framework.ExecutionResult; 33 import com.android.adservices.shared.spe.framework.ExecutionRuntimeParameters; 34 import com.android.adservices.shared.spe.framework.JobWorker; 35 import com.android.adservices.shared.spe.scheduling.BackoffPolicy; 36 import com.android.adservices.shared.spe.scheduling.JobSpec; 37 import com.android.adservices.shared.util.Clock; 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 40 import com.android.ondevicepersonalization.services.FlagsFactory; 41 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 42 import com.android.ondevicepersonalization.services.data.events.EventsDao; 43 import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao; 44 import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker; 45 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper; 46 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobScheduler; 47 import com.android.ondevicepersonalization.services.sharedlibrary.spe.OdpJobServiceFactory; 48 49 import com.google.common.util.concurrent.Futures; 50 import com.google.common.util.concurrent.ListenableFuture; 51 52 import java.util.ArrayList; 53 54 /** The background job to handle the OnDevicePersonalization maintenance. */ 55 public final class OnDevicePersonalizationMaintenanceJob implements JobWorker { 56 @VisibleForTesting static final long PERIOD_MILLIS = 86400 * 1_000; // 24 hours 57 58 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 59 private static final String TAG = OnDevicePersonalizationMaintenanceJob.class.getSimpleName(); 60 61 // The maximum deletion timeframe is 63 days. 62 // Set parameter to 60 days to account for job scheduler delays. 63 private static final long MAXIMUM_DELETION_TIMEFRAME_MILLIS = 5_184_000_000L; 64 65 @Override getExecutionFuture( Context context, ExecutionRuntimeParameters executionRuntimeParameters)66 public ListenableFuture<ExecutionResult> getExecutionFuture( 67 Context context, ExecutionRuntimeParameters executionRuntimeParameters) { 68 return Futures.submit( 69 () -> { 70 cleanupVendorData(context); 71 72 return ExecutionResult.SUCCESS; 73 }, 74 OnDevicePersonalizationExecutors.getBackgroundExecutor()); 75 } 76 77 @Override getJobEnablementStatus()78 public int getJobEnablementStatus() { 79 if (FlagsFactory.getFlags().getGlobalKillSwitch()) { 80 sLogger.d( 81 TAG 82 + ": GlobalKillSwitch enabled, skip execution of" 83 + " OnDevicePersonalizationMaintenanceJob."); 84 return JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON; 85 } 86 87 return JOB_ENABLED_STATUS_ENABLED; 88 } 89 90 /** Schedules a unique instance of {@link OnDevicePersonalizationMaintenanceJob}. */ schedule(Context context)91 public static void schedule(Context context) { 92 // If SPE is not enabled, force to schedule the job with the old JobService. 93 if (!FlagsFactory.getFlags().getSpePilotJobEnabled()) { 94 sLogger.d( 95 "SPE is not enabled. Schedule the job with" 96 + " OnDevicePersonalizationMaintenanceJobService."); 97 98 int resultCode = 99 OnDevicePersonalizationMaintenanceJobService.schedule( 100 context, /* forceSchedule= */ false); 101 OdpJobServiceFactory.getInstance(context) 102 .getJobSchedulingLogger() 103 .recordOnSchedulingLegacy(MAINTENANCE_TASK_JOB_ID, resultCode); 104 105 return; 106 } 107 108 OdpJobScheduler.getInstance(context).schedule(context, createDefaultJobSpec()); 109 } 110 111 @VisibleForTesting createDefaultJobSpec()112 static JobSpec createDefaultJobSpec() { 113 JobPolicy jobPolicy = 114 JobPolicy.newBuilder() 115 .setJobId(MAINTENANCE_TASK_JOB_ID) 116 .setBatteryType(BATTERY_TYPE_REQUIRE_NOT_LOW) 117 .setRequireStorageNotLow(true) 118 .setPeriodicJobParams( 119 JobPolicy.PeriodicJobParams.newBuilder() 120 .setPeriodicIntervalMs(PERIOD_MILLIS) 121 .build()) 122 .setIsPersisted(true) 123 .build(); 124 125 BackoffPolicy backoffPolicy = 126 new BackoffPolicy.Builder().setShouldRetryOnExecutionStop(true).build(); 127 128 return new JobSpec.Builder(jobPolicy).setBackoffPolicy(backoffPolicy).build(); 129 } 130 131 @VisibleForTesting deleteEventsAndQueries(Context context)132 void deleteEventsAndQueries(Context context) { 133 EventsDao eventsDao = EventsDao.getInstance(context); 134 // Cleanup event and queries table. 135 eventsDao.deleteEventsAndQueries( 136 Clock.getInstance().currentTimeMillis() - MAXIMUM_DELETION_TIMEFRAME_MILLIS); 137 } 138 139 @VisibleForTesting cleanupVendorData(Context context)140 void cleanupVendorData(Context context) throws Exception { 141 ArrayList<ComponentName> services = new ArrayList<>(); 142 143 for (PackageInfo packageInfo : 144 context.getPackageManager() 145 .getInstalledPackages(PackageManager.PackageInfoFlags.of(GET_META_DATA))) { 146 String packageName = packageInfo.packageName; 147 148 if (AppManifestConfigHelper.manifestContainsOdpSettings(context, packageName)) { 149 if (!PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) { 150 sLogger.d(TAG + ": service %s has ODP manifest, but not enrolled", packageName); 151 continue; 152 } 153 154 sLogger.d(TAG + ": service %s has ODP manifest and is enrolled", packageName); 155 156 String serviceClass = 157 AppManifestConfigHelper.getServiceNameFromOdpSettings(context, packageName); 158 ComponentName service = ComponentName.createRelative(packageName, serviceClass); 159 services.add(service); 160 } 161 } 162 163 OnDevicePersonalizationVendorDataDao.deleteVendorTables(context, services); 164 deleteEventsAndQueries(context); 165 } 166 } 167