1 /* 2 * Copyright (C) 2023 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.federatedcompute; 18 19 import android.adservices.ondevicepersonalization.Constants; 20 import android.adservices.ondevicepersonalization.aidl.IFederatedComputeCallback; 21 import android.adservices.ondevicepersonalization.aidl.IFederatedComputeService; 22 import android.annotation.NonNull; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.federatedcompute.FederatedComputeManager; 26 import android.federatedcompute.common.ClientConstants; 27 import android.federatedcompute.common.ScheduleFederatedComputeRequest; 28 import android.federatedcompute.common.TrainingOptions; 29 import android.os.OutcomeReceiver; 30 import android.os.RemoteException; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 34 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 35 import com.android.ondevicepersonalization.services.data.events.EventState; 36 import com.android.ondevicepersonalization.services.data.events.EventsDao; 37 import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus; 38 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper; 39 import com.android.ondevicepersonalization.services.util.DebugUtils; 40 41 import com.google.common.util.concurrent.ListeningExecutorService; 42 43 import java.io.IOException; 44 import java.util.Objects; 45 46 /** 47 * A class that exports methods that adopter code in the isolated process can use to schedule/cancel 48 * federatedCompute jobs. 49 * 50 * <p>See {@link android.adservices.ondevicepersonalization.FederatedComputeScheduler#schedule} 51 */ 52 public class FederatedComputeServiceImpl extends IFederatedComputeService.Stub { 53 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 54 private static final String TAG = FederatedComputeServiceImpl.class.getSimpleName(); 55 56 @NonNull private final Context mApplicationContext; 57 @NonNull private final ComponentName mCallingService; 58 @NonNull private final Injector mInjector; 59 60 @NonNull private final FederatedComputeManager mFederatedComputeManager; 61 FederatedComputeServiceImpl( @onNull ComponentName service, @NonNull Context applicationContext)62 public FederatedComputeServiceImpl( 63 @NonNull ComponentName service, @NonNull Context applicationContext) { 64 this(service, applicationContext, new Injector()); 65 } 66 67 @VisibleForTesting FederatedComputeServiceImpl( @onNull ComponentName service, @NonNull Context applicationContext, @NonNull Injector injector)68 FederatedComputeServiceImpl( 69 @NonNull ComponentName service, 70 @NonNull Context applicationContext, 71 @NonNull Injector injector) { 72 this.mApplicationContext = Objects.requireNonNull(applicationContext); 73 this.mCallingService = Objects.requireNonNull(service); 74 this.mInjector = Objects.requireNonNull(injector); 75 this.mFederatedComputeManager = 76 Objects.requireNonNull(injector.getFederatedComputeManager(mApplicationContext)); 77 } 78 79 @Override schedule(TrainingOptions trainingOptions, IFederatedComputeCallback callback)80 public void schedule(TrainingOptions trainingOptions, IFederatedComputeCallback callback) { 81 mInjector.getExecutor().execute(() -> handleSchedule(trainingOptions, callback)); 82 } 83 handleSchedule( TrainingOptions trainingOptions, IFederatedComputeCallback callback)84 private void handleSchedule( 85 TrainingOptions trainingOptions, IFederatedComputeCallback callback) { 86 try { 87 if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()) { 88 sLogger.d(TAG + ": measurement control is revoked."); 89 sendError(callback, Constants.STATUS_PERSONALIZATION_DISABLED); 90 return; 91 } 92 93 String url = 94 AppManifestConfigHelper.getFcRemoteServerUrlFromOdpSettings( 95 mApplicationContext, mCallingService.getPackageName()); 96 String overrideUrl = 97 DebugUtils.getFcServerOverrideUrl( 98 mApplicationContext, mCallingService.getPackageName()); 99 if (!overrideUrl.isEmpty()) { 100 url = overrideUrl; 101 } 102 103 if (url == null) { 104 sLogger.e( 105 TAG 106 + ": Missing remote server URL for package: " 107 + mCallingService.getPackageName()); 108 sendError(callback, Constants.STATUS_FCP_MANIFEST_INVALID); 109 return; 110 } 111 112 ContextData contextData = 113 new ContextData( 114 mCallingService.getPackageName(), mCallingService.getClassName()); 115 TrainingOptions trainingOptionsWithContext = 116 new TrainingOptions.Builder() 117 .setContextData(ContextData.toByteArray(contextData)) 118 .setTrainingInterval(trainingOptions.getTrainingInterval()) 119 .setPopulationName(trainingOptions.getPopulationName()) 120 .setServerAddress(url) 121 .setOwnerComponentName(mCallingService) 122 .build(); 123 ScheduleFederatedComputeRequest request = 124 new ScheduleFederatedComputeRequest.Builder() 125 .setTrainingOptions(trainingOptionsWithContext) 126 .build(); 127 mFederatedComputeManager.schedule( 128 request, 129 mInjector.getExecutor(), 130 new OutcomeReceiver<>() { 131 @Override 132 public void onResult(Object result) { 133 mInjector 134 .getEventsDao(mApplicationContext) 135 .updateOrInsertEventState( 136 new EventState.Builder() 137 .setService(mCallingService) 138 .setTaskIdentifier( 139 trainingOptions.getPopulationName()) 140 .setToken(new byte[] {}) 141 .build()); 142 sendSuccess(callback); 143 } 144 145 @Override 146 public void onError(Exception e) { 147 sLogger.e(TAG + ": Error while scheduling federatedCompute", e); 148 sendError(callback); 149 } 150 }); 151 } catch (IOException | IllegalArgumentException e) { 152 // The AppManifestConfigHelper methods throw IllegalArgumentExceptions when 153 // parsings fails or the fc settings URL is missing. 154 sLogger.e(TAG + ": Error while scheduling federatedCompute", e); 155 sendError(callback, Constants.STATUS_FCP_MANIFEST_INVALID); 156 } 157 } 158 159 @Override cancel(String populationName, IFederatedComputeCallback callback)160 public void cancel(String populationName, IFederatedComputeCallback callback) { 161 EventState eventState = 162 mInjector 163 .getEventsDao(mApplicationContext) 164 .getEventState(populationName, mCallingService); 165 if (eventState == null) { 166 sLogger.d( 167 TAG 168 + ": No population registered for package: " 169 + mCallingService.getPackageName()); 170 sendSuccess(callback); 171 return; 172 } 173 mFederatedComputeManager.cancel( 174 mCallingService, 175 populationName, 176 mInjector.getExecutor(), 177 new OutcomeReceiver<>() { 178 @Override 179 public void onResult(Object result) { 180 sendSuccess(callback); 181 } 182 183 @Override 184 public void onError(Exception e) { 185 sLogger.e(TAG + ": Error while cancelling federatedCompute", e); 186 sendError(callback); 187 } 188 }); 189 } 190 sendSuccess(@onNull IFederatedComputeCallback callback)191 private static void sendSuccess(@NonNull IFederatedComputeCallback callback) { 192 try { 193 callback.onSuccess(); 194 } catch (RemoteException e) { 195 sLogger.e(TAG + ": Callback error", e); 196 } 197 } 198 sendError(@onNull IFederatedComputeCallback callback)199 private static void sendError(@NonNull IFederatedComputeCallback callback) { 200 sendError(callback, ClientConstants.STATUS_INTERNAL_ERROR); 201 } 202 sendError(@onNull IFederatedComputeCallback callback, int errorCode)203 private static void sendError(@NonNull IFederatedComputeCallback callback, int errorCode) { 204 try { 205 callback.onFailure(errorCode); 206 } catch (RemoteException e) { 207 sLogger.e(TAG + ": Callback error", e); 208 } 209 } 210 211 @VisibleForTesting 212 static class Injector { getExecutor()213 ListeningExecutorService getExecutor() { 214 return OnDevicePersonalizationExecutors.getBackgroundExecutor(); 215 } 216 getFederatedComputeManager(Context context)217 FederatedComputeManager getFederatedComputeManager(Context context) { 218 return context.getSystemService(FederatedComputeManager.class); 219 } 220 getEventsDao(Context context)221 EventsDao getEventsDao(Context context) { 222 return EventsDao.getInstance(context); 223 } 224 } 225 } 226