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.federatedcompute.services.scheduling; 18 19 import static com.android.adservices.shared.proto.JobPolicy.BatteryType.BATTERY_TYPE_REQUIRE_NOT_LOW; 20 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON; 21 import static com.android.adservices.shared.spe.JobServiceConstants.JOB_ENABLED_STATUS_ENABLED; 22 import static com.android.federatedcompute.services.common.FederatedComputeJobInfo.DELETE_EXPIRED_JOB_ID; 23 import static com.android.federatedcompute.services.common.Flags.ODP_AUTHORIZATION_TOKEN_DELETION_PERIOD_SECONDs; 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.JobSpec; 32 import com.android.federatedcompute.internal.util.LogUtil; 33 import com.android.federatedcompute.services.common.FederatedComputeExecutors; 34 import com.android.federatedcompute.services.common.Flags; 35 import com.android.federatedcompute.services.common.FlagsFactory; 36 import com.android.federatedcompute.services.data.FederatedTrainingTaskDao; 37 import com.android.federatedcompute.services.data.ODPAuthorizationTokenDao; 38 import com.android.federatedcompute.services.sharedlibrary.spe.FederatedComputeJobScheduler; 39 import com.android.federatedcompute.services.sharedlibrary.spe.FederatedComputeJobServiceFactory; 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.odp.module.common.Clock; 42 import com.android.odp.module.common.MonotonicClock; 43 44 import com.google.common.util.concurrent.FluentFuture; 45 import com.google.common.util.concurrent.Futures; 46 import com.google.common.util.concurrent.ListenableFuture; 47 import com.google.common.util.concurrent.ListeningExecutorService; 48 49 /** A background job that deletes expired assets, such as authorization tokens and task history. */ 50 public final class DeleteExpiredJob implements JobWorker { 51 private static final String TAG = DeleteExpiredJobService.class.getSimpleName(); 52 53 private final Injector mInjector; 54 55 /** Default constructor with initializing the injector. */ DeleteExpiredJob()56 public DeleteExpiredJob() { 57 mInjector = new Injector(); 58 } 59 60 /** Constructor that allows passing in an injector. */ 61 @VisibleForTesting DeleteExpiredJob(Injector injector)62 public DeleteExpiredJob(Injector injector) { 63 mInjector = injector; 64 } 65 66 static class Injector { getExecutor()67 ListeningExecutorService getExecutor() { 68 return FederatedComputeExecutors.getBackgroundExecutor(); 69 } 70 getODPAuthorizationTokenDao(Context context)71 ODPAuthorizationTokenDao getODPAuthorizationTokenDao(Context context) { 72 return ODPAuthorizationTokenDao.getInstance(context); 73 } 74 getTrainingTaskDao(Context context)75 FederatedTrainingTaskDao getTrainingTaskDao(Context context) { 76 return FederatedTrainingTaskDao.getInstance(context); 77 } 78 getClock()79 Clock getClock() { 80 return MonotonicClock.getInstance(); 81 } 82 getFlags()83 Flags getFlags() { 84 return FlagsFactory.getFlags(); 85 } 86 } 87 88 @Override getExecutionFuture( Context context, ExecutionRuntimeParameters executionRuntimeParameters)89 public ListenableFuture<ExecutionResult> getExecutionFuture( 90 Context context, ExecutionRuntimeParameters executionRuntimeParameters) { 91 ListenableFuture<Integer> deleteExpiredAuthTokenFuture = 92 Futures.submit( 93 () -> 94 mInjector 95 .getODPAuthorizationTokenDao(context) 96 .deleteExpiredAuthorizationTokens(), 97 mInjector.getExecutor()); 98 99 return FluentFuture.from(deleteExpiredAuthTokenFuture) 100 .transform( 101 numberOfDeletedTokens -> { 102 LogUtil.d( 103 TAG, 104 "Deleted %d expired authorization tokens", 105 numberOfDeletedTokens); 106 107 long deleteTime = 108 mInjector.getClock().currentTimeMillis() 109 - mInjector.getFlags().getTaskHistoryTtl(); 110 return mInjector 111 .getTrainingTaskDao(context) 112 .deleteExpiredTaskHistory(deleteTime); 113 }, 114 mInjector.getExecutor()) 115 .transform( 116 numberOfDeletedTaskHistory -> { 117 LogUtil.d( 118 TAG, 119 "Deleted %d expired task history", 120 numberOfDeletedTaskHistory); 121 return ExecutionResult.SUCCESS; 122 }, 123 mInjector.getExecutor()); 124 } 125 126 @Override getJobEnablementStatus()127 public int getJobEnablementStatus() { 128 if (FlagsFactory.getFlags().getGlobalKillSwitch()) { 129 LogUtil.d(TAG, "GlobalKillSwitch is enabled, finishing job."); 130 return JOB_ENABLED_STATUS_DISABLED_FOR_KILL_SWITCH_ON; 131 } 132 133 return JOB_ENABLED_STATUS_ENABLED; 134 } 135 136 /** Schedule the periodic {@link DeleteExpiredJob}. */ schedule(Context context, Flags flags)137 public static void schedule(Context context, Flags flags) { 138 // If SPE is not enabled, force to schedule the job with the old JobService. 139 if (!FlagsFactory.getFlags().getSpePilotJobEnabled()) { 140 LogUtil.d( 141 TAG, "SPE is not enabled. Schedule the job with" + " DeleteExpiredJobService."); 142 143 int resultCode = 144 DeleteExpiredJobService.scheduleJobIfNeeded( 145 context, flags, /* forceSchedule= */ false); 146 FederatedComputeJobServiceFactory.getInstance(context) 147 .getJobSchedulingLogger() 148 .recordOnSchedulingLegacy(DELETE_EXPIRED_JOB_ID, resultCode); 149 150 return; 151 } 152 153 FederatedComputeJobScheduler.getInstance(context).schedule(context, createDefaultJobSpec()); 154 } 155 156 @VisibleForTesting createDefaultJobSpec()157 static JobSpec createDefaultJobSpec() { 158 JobPolicy jobPolicy = 159 JobPolicy.newBuilder() 160 .setJobId(DELETE_EXPIRED_JOB_ID) 161 .setPeriodicJobParams( 162 JobPolicy.PeriodicJobParams.newBuilder() 163 .setPeriodicIntervalMs( 164 ODP_AUTHORIZATION_TOKEN_DELETION_PERIOD_SECONDs 165 * 1000) 166 .build()) 167 .setBatteryType(BATTERY_TYPE_REQUIRE_NOT_LOW) 168 .setRequireDeviceIdle(true) 169 .setIsPersisted(true) 170 .build(); 171 172 return new JobSpec.Builder(jobPolicy).build(); 173 } 174 } 175