1 /* 2 * Copyright (C) 2022 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.app.job.JobScheduler.RESULT_SUCCESS; 20 import static android.content.pm.PackageManager.GET_META_DATA; 21 22 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON; 23 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_FAILED; 24 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_SKIPPED; 25 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_SUCCESSFUL; 26 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.MAINTENANCE_TASK_JOB_ID; 27 28 import android.app.job.JobInfo; 29 import android.app.job.JobParameters; 30 import android.app.job.JobScheduler; 31 import android.app.job.JobService; 32 import android.content.ComponentName; 33 import android.content.Context; 34 import android.content.pm.PackageInfo; 35 import android.content.pm.PackageManager; 36 37 import com.android.adservices.shared.spe.JobServiceConstants.JobSchedulingResultCode; 38 import com.android.internal.annotations.VisibleForTesting; 39 import com.android.odp.module.common.PackageUtils; 40 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 41 import com.android.ondevicepersonalization.services.FlagsFactory; 42 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 43 import com.android.ondevicepersonalization.services.data.events.EventsDao; 44 import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao; 45 import com.android.ondevicepersonalization.services.enrollment.PartnerEnrollmentChecker; 46 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper; 47 import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger; 48 49 import com.google.common.util.concurrent.FutureCallback; 50 import com.google.common.util.concurrent.Futures; 51 import com.google.common.util.concurrent.ListenableFuture; 52 53 import java.util.ArrayList; 54 55 /** JobService to handle the OnDevicePersonalization maintenance */ 56 public class OnDevicePersonalizationMaintenanceJobService extends JobService { 57 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 58 private static final String TAG = "OnDevicePersonalizationMaintenanceJobService"; 59 60 // Every 24hrs. 61 private static final long PERIOD_SECONDS = 86400; 62 63 // The maximum deletion timeframe is 63 days. 64 // Set parameter to 60 days to account for job scheduler delays. 65 private static final long MAXIMUM_DELETION_TIMEFRAME_MILLIS = 5184000000L; 66 private ListenableFuture<Void> mFuture; 67 68 /** Schedules a unique instance of OnDevicePersonalizationMaintenanceJobService to be run. */ 69 @JobSchedulingResultCode schedule(Context context, boolean forceSchedule)70 public static int schedule(Context context, boolean forceSchedule) { 71 JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 72 if (!forceSchedule && jobScheduler.getPendingJob(MAINTENANCE_TASK_JOB_ID) != null) { 73 sLogger.d(TAG + ": Job is already scheduled. Doing nothing,"); 74 return SCHEDULING_RESULT_CODE_SKIPPED; 75 } 76 ComponentName serviceComponent = 77 new ComponentName(context, OnDevicePersonalizationMaintenanceJobService.class); 78 JobInfo.Builder builder = new JobInfo.Builder(MAINTENANCE_TASK_JOB_ID, serviceComponent); 79 80 // Constraints. 81 builder.setRequiresDeviceIdle(true); 82 builder.setRequiresBatteryNotLow(true); 83 builder.setRequiresStorageNotLow(true); 84 builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE); 85 builder.setPeriodic(1000 * PERIOD_SECONDS); // JobScheduler uses Milliseconds. 86 // persist this job across boots 87 builder.setPersisted(true); 88 89 int schedulingResult = 90 jobScheduler.schedule(builder.build()) == RESULT_SUCCESS 91 ? SCHEDULING_RESULT_CODE_SUCCESSFUL 92 : SCHEDULING_RESULT_CODE_FAILED; 93 sLogger.d( 94 TAG + ": OnDevicePersonalizationMaintenanceJobService scheduling result is %s.", 95 schedulingResult == SCHEDULING_RESULT_CODE_SUCCESSFUL 96 ? "SCHEDULING_RESULT_CODE_SUCCESSFUL" 97 : "SCHEDULING_RESULT_CODE_FAILED"); 98 return schedulingResult; 99 } 100 101 @VisibleForTesting deleteEventsAndQueries( Context context)102 static void deleteEventsAndQueries( 103 Context context) throws Exception { 104 EventsDao eventsDao = EventsDao.getInstance(context); 105 // Cleanup event and queries table. 106 eventsDao.deleteEventsAndQueries( 107 System.currentTimeMillis() - MAXIMUM_DELETION_TIMEFRAME_MILLIS); 108 } 109 110 @VisibleForTesting cleanupVendorData(Context context)111 static void cleanupVendorData(Context context) throws Exception { 112 ArrayList<ComponentName> services = new ArrayList<>(); 113 114 for (PackageInfo packageInfo : context.getPackageManager().getInstalledPackages( 115 PackageManager.PackageInfoFlags.of(GET_META_DATA))) { 116 String packageName = packageInfo.packageName; 117 if (AppManifestConfigHelper.manifestContainsOdpSettings( 118 context, packageName)) { 119 if (!PartnerEnrollmentChecker.isIsolatedServiceEnrolled(packageName)) { 120 sLogger.d(TAG + ": service %s has ODP manifest, but not enrolled", 121 packageName); 122 continue; 123 } 124 sLogger.d(TAG + ": service %s has ODP manifest and is enrolled", packageName); 125 String certDigest = PackageUtils.getCertDigest(context, packageName); 126 String serviceClass = AppManifestConfigHelper.getServiceNameFromOdpSettings( 127 context, packageName); 128 ComponentName service = ComponentName.createRelative(packageName, serviceClass); 129 services.add(service); 130 } 131 } 132 133 OnDevicePersonalizationVendorDataDao.deleteVendorTables(context, services); 134 deleteEventsAndQueries(context); 135 } 136 137 @Override onStartJob(JobParameters params)138 public boolean onStartJob(JobParameters params) { 139 sLogger.d(TAG + ": onStartJob()"); 140 OdpJobServiceLogger.getInstance(this).recordOnStartJob(MAINTENANCE_TASK_JOB_ID); 141 if (FlagsFactory.getFlags().getGlobalKillSwitch()) { 142 sLogger.d(TAG + ": GlobalKillSwitch enabled, finishing job."); 143 return cancelAndFinishJob( 144 params, 145 AD_SERVICES_BACKGROUND_JOBS_EXECUTION_REPORTED__EXECUTION_RESULT_CODE__SKIP_FOR_KILL_SWITCH_ON); 146 } 147 148 Context context = this; 149 150 // Reschedule jobs with SPE if it's enabled. Note scheduled jobs by this 151 // OnDevicePersonalizationMaintenanceJobService will be cancelled for the same job ID. 152 // 153 // Note the job without a flex period will execute immediately after rescheduling with the 154 // same ID. Therefore, ending the execution here and let it run in the new SPE job. 155 if (FlagsFactory.getFlags().getSpePilotJobEnabled()) { 156 sLogger.d( 157 "SPE is enabled. Reschedule OnDevicePersonalizationMaintenanceJobService with" 158 + " OnDevicePersonalizationMaintenanceJob."); 159 OnDevicePersonalizationMaintenanceJob.schedule(context); 160 return false; 161 } 162 163 mFuture = 164 Futures.submit( 165 new Runnable() { 166 @Override 167 public void run() { 168 sLogger.d(TAG + ": Running maintenance job"); 169 try { 170 cleanupVendorData(context); 171 } catch (Exception e) { 172 sLogger.e(TAG + ": Failed to cleanup vendorData", e); 173 } 174 } 175 }, 176 OnDevicePersonalizationExecutors.getBackgroundExecutor()); 177 178 Futures.addCallback( 179 mFuture, 180 new FutureCallback<Void>() { 181 @Override 182 public void onSuccess(Void result) { 183 sLogger.d(TAG + ": Maintenance job completed."); 184 boolean wantsReschedule = false; 185 OdpJobServiceLogger.getInstance( 186 OnDevicePersonalizationMaintenanceJobService.this) 187 .recordJobFinished( 188 MAINTENANCE_TASK_JOB_ID, 189 /* isSuccessful= */ true, 190 wantsReschedule); 191 // Tell the JobScheduler that the job has completed and does not needs to be 192 // rescheduled. 193 jobFinished(params, wantsReschedule); 194 } 195 196 @Override 197 public void onFailure(Throwable t) { 198 sLogger.e(TAG + ": Failed to handle JobService: " + params.getJobId(), t); 199 boolean wantsReschedule = false; 200 OdpJobServiceLogger.getInstance( 201 OnDevicePersonalizationMaintenanceJobService.this) 202 .recordJobFinished( 203 MAINTENANCE_TASK_JOB_ID, 204 /* isSuccessful= */ false, 205 wantsReschedule); 206 // When failure, also tell the JobScheduler that the job has completed and 207 // does not need to be rescheduled. 208 jobFinished(params, wantsReschedule); 209 } 210 }, 211 OnDevicePersonalizationExecutors.getBackgroundExecutor()); 212 213 return true; 214 } 215 216 @Override onStopJob(JobParameters params)217 public boolean onStopJob(JobParameters params) { 218 if (mFuture != null) { 219 mFuture.cancel(true); 220 } 221 // Reschedule the job since it ended before finishing 222 boolean wantsReschedule = true; 223 OdpJobServiceLogger.getInstance(this) 224 .recordOnStopJob( 225 params, 226 MAINTENANCE_TASK_JOB_ID, 227 wantsReschedule); 228 return wantsReschedule; 229 } 230 cancelAndFinishJob(final JobParameters params, int skipReason)231 private boolean cancelAndFinishJob(final JobParameters params, int skipReason) { 232 JobScheduler jobScheduler = this.getSystemService(JobScheduler.class); 233 if (jobScheduler != null) { 234 jobScheduler.cancel(MAINTENANCE_TASK_JOB_ID); 235 } 236 OdpJobServiceLogger.getInstance(this).recordJobSkipped( 237 MAINTENANCE_TASK_JOB_ID, 238 skipReason); 239 jobFinished(params, /* wantsReschedule = */ false); 240 return true; 241 } 242 } 243