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 android.adservices.ondevicepersonalization; 18 19 import android.adservices.ondevicepersonalization.aidl.IDataAccessService; 20 import android.adservices.ondevicepersonalization.aidl.IFederatedComputeCallback; 21 import android.adservices.ondevicepersonalization.aidl.IFederatedComputeService; 22 import android.annotation.FlaggedApi; 23 import android.annotation.NonNull; 24 import android.annotation.WorkerThread; 25 import android.federatedcompute.common.TrainingOptions; 26 import android.os.RemoteException; 27 28 import com.android.adservices.ondevicepersonalization.flags.Flags; 29 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 30 31 import java.util.concurrent.CountDownLatch; 32 33 /** 34 * Handles scheduling federated compute jobs. See {@link 35 * IsolatedService#getFederatedComputeScheduler}. 36 */ 37 @FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED) 38 public class FederatedComputeScheduler { 39 private static final String TAG = FederatedComputeScheduler.class.getSimpleName(); 40 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 41 42 private final IFederatedComputeService mFcService; 43 private final IDataAccessService mDataAccessService; 44 45 /** @hide */ FederatedComputeScheduler( IFederatedComputeService binder, IDataAccessService dataService)46 public FederatedComputeScheduler( 47 IFederatedComputeService binder, IDataAccessService dataService) { 48 mFcService = binder; 49 mDataAccessService = dataService; 50 } 51 52 // TODO(b/300461799): add federated compute server document. 53 // TODO(b/269665435): add sample code snippet. 54 /** 55 * Schedules a federated compute job. In {@link IsolatedService#onRequest}, the app can call 56 * {@link IsolatedService#getFederatedComputeScheduler} to pass scheduler when construct {@link 57 * IsolatedWorker}. 58 * 59 * @param params parameters related to job scheduling. 60 * @param input the configuration of the federated compute. It should be consistent with the 61 * federated compute server setup. 62 */ 63 @WorkerThread schedule(@onNull Params params, @NonNull FederatedComputeInput input)64 public void schedule(@NonNull Params params, @NonNull FederatedComputeInput input) { 65 final long startTimeMillis = System.currentTimeMillis(); 66 int responseCode = Constants.STATUS_INTERNAL_ERROR; 67 if (mFcService == null) { 68 throw new IllegalStateException( 69 "FederatedComputeScheduler not available for this instance."); 70 } 71 72 android.federatedcompute.common.TrainingInterval trainingInterval = 73 convertTrainingInterval(params.getTrainingInterval()); 74 TrainingOptions trainingOptions = 75 new TrainingOptions.Builder() 76 .setPopulationName(input.getPopulationName()) 77 .setTrainingInterval(trainingInterval) 78 .build(); 79 CountDownLatch latch = new CountDownLatch(1); 80 final int[] err = {0}; 81 try { 82 mFcService.schedule( 83 trainingOptions, 84 new IFederatedComputeCallback.Stub() { 85 @Override 86 public void onSuccess() { 87 latch.countDown(); 88 } 89 90 @Override 91 public void onFailure(int i) { 92 err[0] = i; 93 latch.countDown(); 94 } 95 }); 96 latch.await(); 97 if (err[0] != 0) { 98 // Fail silently for now. TODO(b/346827691): update schedule/cancel API to return 99 // error status to caller. 100 sLogger.e("Internal failure occurred while scheduling job, error code %d", err[0]); 101 responseCode = Constants.STATUS_INTERNAL_ERROR; 102 return; 103 } 104 responseCode = Constants.STATUS_SUCCESS; 105 } catch (RemoteException | InterruptedException e) { 106 sLogger.e(TAG + ": Failed to schedule federated compute job", e); 107 throw new IllegalStateException(e); 108 } finally { 109 logApiCallStats( 110 Constants.API_NAME_FEDERATED_COMPUTE_SCHEDULE, 111 System.currentTimeMillis() - startTimeMillis, 112 responseCode); 113 } 114 } 115 116 /** 117 * Cancels a federated compute job with input training params. In {@link 118 * IsolatedService#onRequest}, the app can call {@link 119 * IsolatedService#getFederatedComputeScheduler} to pass scheduler when construct {@link 120 * IsolatedWorker}. 121 * 122 * @param input the configuration of the federated compute. It should be consistent with the 123 * federated compute server setup. 124 */ 125 @WorkerThread cancel(@onNull FederatedComputeInput input)126 public void cancel(@NonNull FederatedComputeInput input) { 127 final long startTimeMillis = System.currentTimeMillis(); 128 int responseCode = Constants.STATUS_INTERNAL_ERROR; 129 if (mFcService == null) { 130 throw new IllegalStateException( 131 "FederatedComputeScheduler not available for this instance."); 132 } 133 CountDownLatch latch = new CountDownLatch(1); 134 final int[] err = {0}; 135 try { 136 mFcService.cancel( 137 input.getPopulationName(), 138 new IFederatedComputeCallback.Stub() { 139 @Override 140 public void onSuccess() { 141 latch.countDown(); 142 } 143 144 @Override 145 public void onFailure(int i) { 146 err[0] = i; 147 latch.countDown(); 148 } 149 }); 150 latch.await(); 151 if (err[0] != 0) { 152 sLogger.e("Internal failure occurred while cancelling job, error code %d", err[0]); 153 responseCode = Constants.STATUS_INTERNAL_ERROR; 154 // Fail silently for now. TODO(b/346827691): update schedule/cancel API to return 155 // error status to caller. 156 return; 157 } 158 responseCode = Constants.STATUS_SUCCESS; 159 } catch (RemoteException | InterruptedException e) { 160 sLogger.e(TAG + ": Failed to cancel federated compute job", e); 161 throw new IllegalStateException(e); 162 } finally { 163 logApiCallStats( 164 Constants.API_NAME_FEDERATED_COMPUTE_CANCEL, 165 System.currentTimeMillis() - startTimeMillis, 166 responseCode); 167 } 168 } 169 convertTrainingInterval( TrainingInterval interval)170 private android.federatedcompute.common.TrainingInterval convertTrainingInterval( 171 TrainingInterval interval) { 172 return new android.federatedcompute.common.TrainingInterval.Builder() 173 .setMinimumIntervalMillis(interval.getMinimumInterval().toMillis()) 174 .setSchedulingMode(convertSchedulingMode(interval)) 175 .build(); 176 } 177 178 private @android.federatedcompute.common.TrainingInterval.SchedulingMode int convertSchedulingMode(TrainingInterval interval)179 convertSchedulingMode(TrainingInterval interval) { 180 switch (interval.getSchedulingMode()) { 181 case TrainingInterval.SCHEDULING_MODE_ONE_TIME: 182 return android.federatedcompute.common.TrainingInterval.SCHEDULING_MODE_ONE_TIME; 183 case TrainingInterval.SCHEDULING_MODE_RECURRENT: 184 return android.federatedcompute.common.TrainingInterval.SCHEDULING_MODE_RECURRENT; 185 default: 186 throw new IllegalStateException( 187 "Unsupported scheduling mode " + interval.getSchedulingMode()); 188 } 189 } 190 logApiCallStats(int apiName, long duration, int responseCode)191 private void logApiCallStats(int apiName, long duration, int responseCode) { 192 try { 193 mDataAccessService.logApiCallStats(apiName, duration, responseCode); 194 } catch (Exception e) { 195 sLogger.d(e, TAG + ": failed to log metrics"); 196 } 197 } 198 199 /** The parameters related to job scheduling. */ 200 public static class Params { 201 /** 202 * If training interval is scheduled for recurrent tasks, the earliest time this task could 203 * start is after the minimum training interval expires. E.g. If the task is set to run 204 * maximum once per day, the first run of this task will be one day after this task is 205 * scheduled. When a one time job is scheduled, the earliest next runtime is calculated 206 * based on federated compute default interval. 207 */ 208 @NonNull private final TrainingInterval mTrainingInterval; 209 Params(@onNull TrainingInterval trainingInterval)210 public Params(@NonNull TrainingInterval trainingInterval) { 211 mTrainingInterval = trainingInterval; 212 } 213 214 @NonNull getTrainingInterval()215 public TrainingInterval getTrainingInterval() { 216 return mTrainingInterval; 217 } 218 } 219 } 220