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