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