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.reset; 18 19 import static android.app.job.JobScheduler.RESULT_SUCCESS; 20 21 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_FAILED; 22 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_SKIPPED; 23 import static com.android.adservices.shared.spe.JobServiceConstants.SCHEDULING_RESULT_CODE_SUCCESSFUL; 24 import static com.android.ondevicepersonalization.services.OnDevicePersonalizationConfig.RESET_DATA_JOB_ID; 25 26 import android.app.job.JobInfo; 27 import android.app.job.JobParameters; 28 import android.app.job.JobScheduler; 29 import android.app.job.JobService; 30 import android.content.ComponentName; 31 import android.content.Context; 32 33 import com.android.adservices.shared.spe.JobServiceConstants; 34 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 35 import com.android.ondevicepersonalization.services.Flags; 36 import com.android.ondevicepersonalization.services.FlagsFactory; 37 import com.android.ondevicepersonalization.services.OnDevicePersonalizationApplication; 38 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 39 import com.android.ondevicepersonalization.services.statsd.joblogging.OdpJobServiceLogger; 40 41 import com.google.common.util.concurrent.FutureCallback; 42 import com.google.common.util.concurrent.Futures; 43 import com.google.common.util.concurrent.ListenableFuture; 44 45 /** 46 * JobService to handle the OnDevicePersonalization maintenance 47 */ 48 public class ResetDataJobService extends JobService { 49 private static final String TAG = ResetDataJobService.class.getSimpleName(); 50 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 51 private static final long MILLIS = 1000; 52 private ListenableFuture<Void> mFuture; 53 54 /** Schedule the Reset job. */ 55 @JobServiceConstants.JobSchedulingResultCode schedule(boolean forceSchedule)56 public static int schedule(boolean forceSchedule) { 57 Flags flags = FlagsFactory.getFlags(); 58 Context context = OnDevicePersonalizationApplication.getAppContext(); 59 JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); 60 if (jobScheduler == null) { 61 sLogger.e(TAG, "Failed to get job scheduler from system service."); 62 return SCHEDULING_RESULT_CODE_FAILED; 63 } 64 65 if (!forceSchedule && jobScheduler.getPendingJob(RESET_DATA_JOB_ID) != null) { 66 sLogger.d(TAG + ": Job is already scheduled. Doing nothing,"); 67 return SCHEDULING_RESULT_CODE_SKIPPED; 68 } 69 70 ComponentName service = new ComponentName(context, ResetDataJobService.class); 71 JobInfo jobInfo = new JobInfo.Builder(RESET_DATA_JOB_ID, service) 72 .setMinimumLatency(flags.getResetDataDelaySeconds() * MILLIS) 73 .setOverrideDeadline(flags.getResetDataDeadlineSeconds() * MILLIS) 74 .setRequiresBatteryNotLow(true) 75 .setPersisted(true) 76 .build(); 77 78 int schedulingResult = jobScheduler.schedule(jobInfo); 79 return RESULT_SUCCESS == schedulingResult ? SCHEDULING_RESULT_CODE_SUCCESSFUL 80 : SCHEDULING_RESULT_CODE_FAILED; 81 } 82 83 @Override onStartJob(JobParameters params)84 public boolean onStartJob(JobParameters params) { 85 sLogger.d(TAG + ": onStartJob()"); 86 OdpJobServiceLogger.getInstance(this).recordOnStartJob( 87 RESET_DATA_JOB_ID); 88 89 // Reschedule jobs with SPE if it's enabled. Note scheduled jobs by this 90 // ResetDataJobService will be cancelled for the same job ID. 91 if (FlagsFactory.getFlags().getSpeOnResetDataJobEnabled()) { 92 sLogger.d( 93 "SPE is enabled. Reschedule ResetDataJobService with ResetDataJob."); 94 ResetDataJob.schedule(/* context */ this); 95 return false; 96 } 97 98 mFuture = Futures.submit(new Runnable() { 99 @Override 100 public void run() { 101 sLogger.d(TAG + ": Running reset job"); 102 try { 103 ResetDataTask.deleteMeasurementData(); 104 } catch (Exception e) { 105 sLogger.e(TAG + ": Failed to delete data", e); 106 } 107 } 108 }, OnDevicePersonalizationExecutors.getBackgroundExecutor()); 109 110 Futures.addCallback( 111 mFuture, 112 new FutureCallback<Void>() { 113 @Override 114 public void onSuccess(Void result) { 115 sLogger.d(TAG + ": Reset job completed."); 116 boolean wantsReschedule = false; 117 OdpJobServiceLogger.getInstance( 118 ResetDataJobService.this) 119 .recordJobFinished( 120 RESET_DATA_JOB_ID, 121 /* isSuccessful= */ true, 122 wantsReschedule); 123 // Tell the JobScheduler that the job has completed and does not needs to be 124 // rescheduled. 125 jobFinished(params, wantsReschedule); 126 } 127 128 @Override 129 public void onFailure(Throwable t) { 130 sLogger.e(TAG + ": Failed to handle JobService: " + params.getJobId(), t); 131 boolean wantsReschedule = false; 132 OdpJobServiceLogger.getInstance( 133 ResetDataJobService.this) 134 .recordJobFinished( 135 RESET_DATA_JOB_ID, 136 /* isSuccessful= */ false, 137 wantsReschedule); 138 // When failure, also tell the JobScheduler that the job has completed and 139 // does not need to be rescheduled. 140 jobFinished(params, wantsReschedule); 141 } 142 }, 143 OnDevicePersonalizationExecutors.getBackgroundExecutor()); 144 145 return true; 146 } 147 148 @Override onStopJob(JobParameters params)149 public boolean onStopJob(JobParameters params) { 150 if (mFuture != null) { 151 mFuture.cancel(true); 152 } 153 // Reschedule the job since it ended before finishing 154 boolean wantsReschedule = true; 155 OdpJobServiceLogger.getInstance(this) 156 .recordOnStopJob( 157 params, 158 RESET_DATA_JOB_ID, 159 wantsReschedule); 160 return wantsReschedule; 161 } 162 } 163