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.aidl.IFederatedComputeCallback; 20 import android.adservices.ondevicepersonalization.aidl.IFederatedComputeService; 21 import android.annotation.NonNull; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.pm.PackageManager; 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 import android.os.SystemProperties; 32 import android.provider.DeviceConfig; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.odp.module.common.PackageUtils; 36 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 37 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 38 import com.android.ondevicepersonalization.services.data.events.EventState; 39 import com.android.ondevicepersonalization.services.data.events.EventsDao; 40 import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus; 41 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper; 42 43 import com.google.common.util.concurrent.ListeningExecutorService; 44 45 import java.io.IOException; 46 import java.util.Objects; 47 48 /** 49 * A class that exports methods that plugin code in the isolated process can use to schedule 50 * federatedCompute jobs. 51 */ 52 public class FederatedComputeServiceImpl extends IFederatedComputeService.Stub { 53 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 54 private static final String TAG = "FederatedComputeServiceImpl"; 55 56 private static final String OVERRIDE_FC_SERVER_URL_PACKAGE = 57 "debug.ondevicepersonalization.override_fc_server_url_package"; 58 private static final String OVERRIDE_FC_SERVER_URL = 59 "debug.ondevicepersonalization.override_fc_server_url"; 60 61 @NonNull private final Context mApplicationContext; 62 @NonNull private ComponentName mCallingService; 63 @NonNull private final Injector mInjector; 64 65 @NonNull private final FederatedComputeManager mFederatedComputeManager; 66 67 @VisibleForTesting FederatedComputeServiceImpl( @onNull ComponentName service, @NonNull Context applicationContext, @NonNull Injector injector)68 public 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 FederatedComputeServiceImpl( @onNull ComponentName service, @NonNull Context applicationContext)79 public FederatedComputeServiceImpl( 80 @NonNull ComponentName service, @NonNull Context applicationContext) { 81 this(service, applicationContext, new Injector()); 82 } 83 84 @Override schedule(TrainingOptions trainingOptions, IFederatedComputeCallback callback)85 public void schedule(TrainingOptions trainingOptions, IFederatedComputeCallback callback) { 86 mInjector.getExecutor().execute(() -> handleSchedule(trainingOptions, callback)); 87 } 88 handleSchedule( TrainingOptions trainingOptions, IFederatedComputeCallback callback)89 private void handleSchedule( 90 TrainingOptions trainingOptions, IFederatedComputeCallback callback) { 91 try { 92 if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()) { 93 sLogger.d(TAG + ": measurement control is revoked."); 94 sendError(callback); 95 return; 96 } 97 98 String url = 99 AppManifestConfigHelper.getFcRemoteServerUrlFromOdpSettings( 100 mApplicationContext, mCallingService.getPackageName()); 101 102 // Check for override manifest url property, if package is debuggable 103 if (PackageUtils.isPackageDebuggable( 104 mApplicationContext, mCallingService.getPackageName())) { 105 if (SystemProperties.get(OVERRIDE_FC_SERVER_URL_PACKAGE, "") 106 .equals(mCallingService.getPackageName())) { 107 String overrideManifestUrl = SystemProperties.get(OVERRIDE_FC_SERVER_URL, ""); 108 if (!overrideManifestUrl.isEmpty()) { 109 sLogger.d( 110 TAG 111 + ": Overriding fc server URL for package " 112 + mCallingService.getPackageName() 113 + " to " 114 + overrideManifestUrl); 115 url = overrideManifestUrl; 116 } 117 String deviceConfigOverrideUrl = 118 DeviceConfig.getString( 119 /* namespace= */ "on_device_personalization", 120 /* name= */ OVERRIDE_FC_SERVER_URL, 121 /* defaultValue= */ ""); 122 if (!deviceConfigOverrideUrl.isEmpty()) { 123 sLogger.d( 124 TAG 125 + ": Overriding fc server URL for package " 126 + mCallingService.getPackageName() 127 + " to " 128 + deviceConfigOverrideUrl); 129 url = deviceConfigOverrideUrl; 130 } 131 } 132 } 133 134 if (url == null) { 135 sLogger.e( 136 TAG 137 + ": Missing remote server URL for package: " 138 + mCallingService.getPackageName()); 139 sendError(callback); 140 return; 141 } 142 143 ContextData contextData = 144 new ContextData( 145 mCallingService.getPackageName(), mCallingService.getClassName()); 146 TrainingOptions trainingOptionsWithContext = 147 new TrainingOptions.Builder() 148 .setContextData(ContextData.toByteArray(contextData)) 149 .setTrainingInterval(trainingOptions.getTrainingInterval()) 150 .setPopulationName(trainingOptions.getPopulationName()) 151 .setServerAddress(url) 152 .setOwnerComponentName(mCallingService) 153 .build(); 154 ScheduleFederatedComputeRequest request = 155 new ScheduleFederatedComputeRequest.Builder() 156 .setTrainingOptions(trainingOptionsWithContext) 157 .build(); 158 mFederatedComputeManager.schedule( 159 request, 160 mInjector.getExecutor(), 161 new OutcomeReceiver<>() { 162 @Override 163 public void onResult(Object result) { 164 mInjector 165 .getEventsDao(mApplicationContext) 166 .updateOrInsertEventState( 167 new EventState.Builder() 168 .setService(mCallingService) 169 .setTaskIdentifier( 170 trainingOptions.getPopulationName()) 171 .setToken(new byte[] {}) 172 .build()); 173 sendSuccess(callback); 174 } 175 176 @Override 177 public void onError(Exception e) { 178 sLogger.e(TAG + ": Error while scheduling federatedCompute", e); 179 sendError(callback); 180 } 181 }); 182 } catch (IOException | PackageManager.NameNotFoundException e) { 183 sLogger.e(TAG + ": Error while scheduling federatedCompute", e); 184 sendError(callback); 185 } 186 } 187 188 @Override cancel(String populationName, IFederatedComputeCallback callback)189 public void cancel(String populationName, IFederatedComputeCallback callback) { 190 EventState eventState = 191 mInjector 192 .getEventsDao(mApplicationContext) 193 .getEventState(populationName, mCallingService); 194 if (eventState == null) { 195 sLogger.d( 196 TAG 197 + ": No population registered for package: " 198 + mCallingService.getPackageName()); 199 sendSuccess(callback); 200 return; 201 } 202 mFederatedComputeManager.cancel( 203 mCallingService, 204 populationName, 205 mInjector.getExecutor(), 206 new OutcomeReceiver<>() { 207 @Override 208 public void onResult(Object result) { 209 sendSuccess(callback); 210 } 211 212 @Override 213 public void onError(Exception e) { 214 sLogger.e(TAG + ": Error while cancelling federatedCompute", e); 215 sendError(callback); 216 } 217 }); 218 } 219 sendSuccess(@onNull IFederatedComputeCallback callback)220 private void sendSuccess(@NonNull IFederatedComputeCallback callback) { 221 try { 222 callback.onSuccess(); 223 } catch (RemoteException e) { 224 sLogger.e(TAG + ": Callback error", e); 225 } 226 } 227 sendError(@onNull IFederatedComputeCallback callback)228 private void sendError(@NonNull IFederatedComputeCallback callback) { 229 try { 230 callback.onFailure(ClientConstants.STATUS_INTERNAL_ERROR); 231 } catch (RemoteException e) { 232 sLogger.e(TAG + ": Callback error", e); 233 } 234 } 235 236 @VisibleForTesting 237 static class Injector { getExecutor()238 ListeningExecutorService getExecutor() { 239 return OnDevicePersonalizationExecutors.getBackgroundExecutor(); 240 } 241 getFederatedComputeManager(Context context)242 FederatedComputeManager getFederatedComputeManager(Context context) { 243 return context.getSystemService(FederatedComputeManager.class); 244 } 245 getEventsDao(Context context)246 EventsDao getEventsDao(Context context) { 247 return EventsDao.getInstance(context); 248 } 249 } 250 } 251