/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ondevicepersonalization.services.federatedcompute; import android.adservices.ondevicepersonalization.aidl.IFederatedComputeCallback; import android.adservices.ondevicepersonalization.aidl.IFederatedComputeService; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.federatedcompute.FederatedComputeManager; import android.federatedcompute.common.ClientConstants; import android.federatedcompute.common.ScheduleFederatedComputeRequest; import android.federatedcompute.common.TrainingOptions; import android.os.OutcomeReceiver; import android.os.RemoteException; import android.os.SystemProperties; import android.provider.DeviceConfig; import com.android.internal.annotations.VisibleForTesting; import com.android.odp.module.common.PackageUtils; import com.android.ondevicepersonalization.internal.util.LoggerFactory; import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; import com.android.ondevicepersonalization.services.data.events.EventState; import com.android.ondevicepersonalization.services.data.events.EventsDao; import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus; import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper; import com.google.common.util.concurrent.ListeningExecutorService; import java.io.IOException; import java.util.Objects; /** * A class that exports methods that plugin code in the isolated process can use to schedule * federatedCompute jobs. */ public class FederatedComputeServiceImpl extends IFederatedComputeService.Stub { private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); private static final String TAG = "FederatedComputeServiceImpl"; private static final String OVERRIDE_FC_SERVER_URL_PACKAGE = "debug.ondevicepersonalization.override_fc_server_url_package"; private static final String OVERRIDE_FC_SERVER_URL = "debug.ondevicepersonalization.override_fc_server_url"; @NonNull private final Context mApplicationContext; @NonNull private ComponentName mCallingService; @NonNull private final Injector mInjector; @NonNull private final FederatedComputeManager mFederatedComputeManager; @VisibleForTesting public FederatedComputeServiceImpl( @NonNull ComponentName service, @NonNull Context applicationContext, @NonNull Injector injector) { this.mApplicationContext = Objects.requireNonNull(applicationContext); this.mCallingService = Objects.requireNonNull(service); this.mInjector = Objects.requireNonNull(injector); this.mFederatedComputeManager = Objects.requireNonNull(injector.getFederatedComputeManager(mApplicationContext)); } public FederatedComputeServiceImpl( @NonNull ComponentName service, @NonNull Context applicationContext) { this(service, applicationContext, new Injector()); } @Override public void schedule(TrainingOptions trainingOptions, IFederatedComputeCallback callback) { mInjector.getExecutor().execute(() -> handleSchedule(trainingOptions, callback)); } private void handleSchedule( TrainingOptions trainingOptions, IFederatedComputeCallback callback) { try { if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()) { sLogger.d(TAG + ": measurement control is revoked."); sendError(callback); return; } String url = AppManifestConfigHelper.getFcRemoteServerUrlFromOdpSettings( mApplicationContext, mCallingService.getPackageName()); // Check for override manifest url property, if package is debuggable if (PackageUtils.isPackageDebuggable( mApplicationContext, mCallingService.getPackageName())) { if (SystemProperties.get(OVERRIDE_FC_SERVER_URL_PACKAGE, "") .equals(mCallingService.getPackageName())) { String overrideManifestUrl = SystemProperties.get(OVERRIDE_FC_SERVER_URL, ""); if (!overrideManifestUrl.isEmpty()) { sLogger.d( TAG + ": Overriding fc server URL for package " + mCallingService.getPackageName() + " to " + overrideManifestUrl); url = overrideManifestUrl; } String deviceConfigOverrideUrl = DeviceConfig.getString( /* namespace= */ "on_device_personalization", /* name= */ OVERRIDE_FC_SERVER_URL, /* defaultValue= */ ""); if (!deviceConfigOverrideUrl.isEmpty()) { sLogger.d( TAG + ": Overriding fc server URL for package " + mCallingService.getPackageName() + " to " + deviceConfigOverrideUrl); url = deviceConfigOverrideUrl; } } } if (url == null) { sLogger.e( TAG + ": Missing remote server URL for package: " + mCallingService.getPackageName()); sendError(callback); return; } ContextData contextData = new ContextData( mCallingService.getPackageName(), mCallingService.getClassName()); TrainingOptions trainingOptionsWithContext = new TrainingOptions.Builder() .setContextData(ContextData.toByteArray(contextData)) .setTrainingInterval(trainingOptions.getTrainingInterval()) .setPopulationName(trainingOptions.getPopulationName()) .setServerAddress(url) .setOwnerComponentName(mCallingService) .build(); ScheduleFederatedComputeRequest request = new ScheduleFederatedComputeRequest.Builder() .setTrainingOptions(trainingOptionsWithContext) .build(); mFederatedComputeManager.schedule( request, mInjector.getExecutor(), new OutcomeReceiver<>() { @Override public void onResult(Object result) { mInjector .getEventsDao(mApplicationContext) .updateOrInsertEventState( new EventState.Builder() .setService(mCallingService) .setTaskIdentifier( trainingOptions.getPopulationName()) .setToken(new byte[] {}) .build()); sendSuccess(callback); } @Override public void onError(Exception e) { sLogger.e(TAG + ": Error while scheduling federatedCompute", e); sendError(callback); } }); } catch (IOException | PackageManager.NameNotFoundException e) { sLogger.e(TAG + ": Error while scheduling federatedCompute", e); sendError(callback); } } @Override public void cancel(String populationName, IFederatedComputeCallback callback) { EventState eventState = mInjector .getEventsDao(mApplicationContext) .getEventState(populationName, mCallingService); if (eventState == null) { sLogger.d( TAG + ": No population registered for package: " + mCallingService.getPackageName()); sendSuccess(callback); return; } mFederatedComputeManager.cancel( mCallingService, populationName, mInjector.getExecutor(), new OutcomeReceiver<>() { @Override public void onResult(Object result) { sendSuccess(callback); } @Override public void onError(Exception e) { sLogger.e(TAG + ": Error while cancelling federatedCompute", e); sendError(callback); } }); } private void sendSuccess(@NonNull IFederatedComputeCallback callback) { try { callback.onSuccess(); } catch (RemoteException e) { sLogger.e(TAG + ": Callback error", e); } } private void sendError(@NonNull IFederatedComputeCallback callback) { try { callback.onFailure(ClientConstants.STATUS_INTERNAL_ERROR); } catch (RemoteException e) { sLogger.e(TAG + ": Callback error", e); } } @VisibleForTesting static class Injector { ListeningExecutorService getExecutor() { return OnDevicePersonalizationExecutors.getBackgroundExecutor(); } FederatedComputeManager getFederatedComputeManager(Context context) { return context.getSystemService(FederatedComputeManager.class); } EventsDao getEventsDao(Context context) { return EventsDao.getInstance(context); } } }