• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.ondevicepersonalization.services.request;
18 
19 import android.adservices.ondevicepersonalization.CalleeMetadata;
20 import android.adservices.ondevicepersonalization.Constants;
21 import android.adservices.ondevicepersonalization.ExecuteInIsolatedServiceRequest;
22 import android.adservices.ondevicepersonalization.ExecuteInputParcel;
23 import android.adservices.ondevicepersonalization.ExecuteOptionsParcel;
24 import android.adservices.ondevicepersonalization.ExecuteOutputParcel;
25 import android.adservices.ondevicepersonalization.RenderingConfig;
26 import android.adservices.ondevicepersonalization.UserData;
27 import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
28 import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService;
29 import android.annotation.NonNull;
30 import android.content.ComponentName;
31 import android.content.Context;
32 import android.os.Bundle;
33 import android.os.RemoteException;
34 import android.os.SystemClock;
35 import android.provider.DeviceConfig;
36 
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.odp.module.common.Clock;
39 import com.android.odp.module.common.MonotonicClock;
40 import com.android.odp.module.common.PackageUtils;
41 import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
42 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
43 import com.android.ondevicepersonalization.services.Flags;
44 import com.android.ondevicepersonalization.services.FlagsFactory;
45 import com.android.ondevicepersonalization.services.OdpServiceException;
46 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors;
47 import com.android.ondevicepersonalization.services.data.DataAccessPermission;
48 import com.android.ondevicepersonalization.services.data.DataAccessServiceImpl;
49 import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus;
50 import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao;
51 import com.android.ondevicepersonalization.services.federatedcompute.FederatedComputeServiceImpl;
52 import com.android.ondevicepersonalization.services.inference.IsolatedModelServiceProvider;
53 import com.android.ondevicepersonalization.services.manifest.AppManifestConfig;
54 import com.android.ondevicepersonalization.services.manifest.AppManifestConfigHelper;
55 import com.android.ondevicepersonalization.services.policyengine.UserDataAccessor;
56 import com.android.ondevicepersonalization.services.serviceflow.ServiceFlow;
57 import com.android.ondevicepersonalization.services.util.AllowListUtils;
58 import com.android.ondevicepersonalization.services.util.CryptUtils;
59 import com.android.ondevicepersonalization.services.util.DebugUtils;
60 import com.android.ondevicepersonalization.services.util.LogUtils;
61 import com.android.ondevicepersonalization.services.util.NoiseUtil;
62 import com.android.ondevicepersonalization.services.util.StatsUtils;
63 
64 import com.google.common.util.concurrent.FluentFuture;
65 import com.google.common.util.concurrent.FutureCallback;
66 import com.google.common.util.concurrent.Futures;
67 import com.google.common.util.concurrent.ListenableFuture;
68 import com.google.common.util.concurrent.ListeningExecutorService;
69 import com.google.common.util.concurrent.ListeningScheduledExecutorService;
70 
71 import java.util.Objects;
72 import java.util.Set;
73 import java.util.concurrent.ThreadLocalRandom;
74 import java.util.concurrent.TimeUnit;
75 import java.util.concurrent.TimeoutException;
76 import java.util.concurrent.atomic.AtomicLong;
77 import java.util.concurrent.atomic.AtomicReference;
78 
79 /**
80  * Handles a surface package request from an app or SDK.
81  */
82 public class AppRequestFlow implements ServiceFlow<Bundle> {
83     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
84     private static final String TAG = AppRequestFlow.class.getSimpleName();
85     @NonNull
86     private final String mCallingPackageName;
87     @NonNull
88     private final ComponentName mService;
89     @NonNull private final Bundle mWrappedParams;
90     @NonNull
91     private final IExecuteCallback mCallback;
92     @NonNull
93     private final Context mContext;
94     private final long mStartTimeMillis;
95     private final long mServiceEntryTimeMillis;
96     private final ExecuteOptionsParcel mOptions;
97 
98     @NonNull
99     private AtomicReference<IsolatedModelServiceProvider> mModelServiceProvider =
100             new AtomicReference<>(null);
101 
102     private AtomicLong mStartServiceTimeMillis = new AtomicLong();
103     private byte[] mSerializedAppParams;
104 
105     @VisibleForTesting
106     public static class Injector {
getExecutor()107         ListeningExecutorService getExecutor() {
108             return OnDevicePersonalizationExecutors.getBackgroundExecutor();
109         }
110 
getClock()111         Clock getClock() {
112             return MonotonicClock.getInstance();
113         }
114 
getFlags()115         public Flags getFlags() {
116             return FlagsFactory.getFlags();
117         }
118 
getScheduledExecutor()119         ListeningScheduledExecutorService getScheduledExecutor() {
120             return OnDevicePersonalizationExecutors.getScheduledExecutor();
121         }
122 
123         /** Returns whether should validate rendering configuration keys. */
shouldValidateExecuteOutput()124         public boolean shouldValidateExecuteOutput() {
125             return DeviceConfig.getBoolean(
126                     /* namespace= */ "on_device_personalization",
127                     /* name= */ "debug.validate_rendering_config_keys",
128                     /* defaultValue= */ true);
129         }
130 
getNoiseUtil()131         public NoiseUtil getNoiseUtil() {
132             return new NoiseUtil();
133         }
134     }
135 
136     @NonNull
137     private final Injector mInjector;
138 
AppRequestFlow( @onNull String callingPackageName, @NonNull ComponentName service, @NonNull Bundle wrappedParams, @NonNull IExecuteCallback callback, @NonNull Context context, long startTimeMillis, long serviceEntryTimeMillis, ExecuteOptionsParcel options)139     public AppRequestFlow(
140             @NonNull String callingPackageName,
141             @NonNull ComponentName service,
142             @NonNull Bundle wrappedParams,
143             @NonNull IExecuteCallback callback,
144             @NonNull Context context,
145             long startTimeMillis,
146             long serviceEntryTimeMillis,
147             ExecuteOptionsParcel options) {
148         this(
149                 callingPackageName,
150                 service,
151                 wrappedParams,
152                 callback,
153                 context,
154                 startTimeMillis,
155                 serviceEntryTimeMillis,
156                 options,
157                 new Injector());
158     }
159 
160     @VisibleForTesting
AppRequestFlow( @onNull String callingPackageName, @NonNull ComponentName service, @NonNull Bundle wrappedParams, @NonNull IExecuteCallback callback, @NonNull Context context, long startTimeMillis, long serviceEntryTimeMillis, ExecuteOptionsParcel options, @NonNull Injector injector)161     public AppRequestFlow(
162             @NonNull String callingPackageName,
163             @NonNull ComponentName service,
164             @NonNull Bundle wrappedParams,
165             @NonNull IExecuteCallback callback,
166             @NonNull Context context,
167             long startTimeMillis,
168             long serviceEntryTimeMillis,
169             ExecuteOptionsParcel options,
170             @NonNull Injector injector) {
171         sLogger.d(TAG + ": AppRequestFlow created.");
172         mCallingPackageName = Objects.requireNonNull(callingPackageName);
173         mService = Objects.requireNonNull(service);
174         mWrappedParams = Objects.requireNonNull(wrappedParams);
175         mCallback = Objects.requireNonNull(callback);
176         mContext = Objects.requireNonNull(context);
177         mStartTimeMillis = startTimeMillis;
178         mServiceEntryTimeMillis =  serviceEntryTimeMillis;
179         mOptions = options;
180         mInjector = Objects.requireNonNull(injector);
181     }
182 
183     @Override
isServiceFlowReady()184     public boolean isServiceFlowReady() {
185         mStartServiceTimeMillis.set(mInjector.getClock().elapsedRealtime());
186 
187         try {
188             ByteArrayParceledSlice paramsBuffer =
189                     Objects.requireNonNull(
190                             mWrappedParams.getParcelable(
191                                     Constants.EXTRA_APP_PARAMS_SERIALIZED,
192                                     ByteArrayParceledSlice.class));
193             mSerializedAppParams = Objects.requireNonNull(paramsBuffer.getByteArray());
194         } catch (Exception e) {
195             sLogger.d(TAG + ": Failed to extract app params.", e);
196             sendErrorResult(Constants.STATUS_INTERNAL_ERROR, 0, e);
197             return false;
198         }
199 
200         AppManifestConfig config = null;
201         try {
202             config = Objects.requireNonNull(
203                     AppManifestConfigHelper.getAppManifestConfig(
204                             mContext, mService.getPackageName()));
205         } catch (Exception e) {
206             sLogger.d(TAG + ": Failed to read manifest.", e);
207             sendErrorResult(
208                     Constants.STATUS_MANIFEST_PARSING_FAILED, /* isolatedServiceErrorCode= */ 0, e);
209             return false;
210         }
211 
212         if (!mService.getClassName().equals(config.getServiceName())) {
213             sLogger.d(TAG + ": service class not found");
214             sendErrorResult(
215                     Constants.STATUS_MANIFEST_MISCONFIGURED,
216                     /* isolatedServiceErrorCode= */ 0,
217                     new ClassNotFoundException(
218                             "Expected: "
219                                     + mService.getClassName()
220                                     + " Found: "
221                                     + config.getServiceName()));
222             return false;
223         }
224 
225         return true;
226     }
227 
228     @Override
getService()229     public ComponentName getService() {
230         return mService;
231     }
232 
233     @Override
getServiceParams()234     public Bundle getServiceParams() {
235         sLogger.d(TAG + ": getting service params.");
236 
237         DataAccessPermission localDataPermission = DataAccessPermission.READ_WRITE;
238         if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()) {
239             localDataPermission = DataAccessPermission.READ_ONLY;
240         }
241         Bundle serviceParams = new Bundle();
242         serviceParams.putParcelable(
243                 Constants.EXTRA_INPUT,
244                 new ExecuteInputParcel.Builder()
245                         .setAppPackageName(mCallingPackageName)
246                         .setSerializedAppParams(new ByteArrayParceledSlice(mSerializedAppParams))
247                         .build());
248         serviceParams.putBinder(
249                 Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER,
250                 new DataAccessServiceImpl(
251                         mService,
252                         mContext,
253                         /* localDataPermission */ localDataPermission,
254                         /* eventDataPermission */ DataAccessPermission.READ_ONLY));
255         serviceParams.putBinder(
256                 Constants.EXTRA_FEDERATED_COMPUTE_SERVICE_BINDER,
257                 new FederatedComputeServiceImpl(mService, mContext));
258         UserData userData =
259                 isPlatformDataProvided()
260                         ? new UserDataAccessor().getUserDataWithAppInstall()
261                         : new UserDataAccessor().getUserData();
262         serviceParams.putParcelable(Constants.EXTRA_USER_DATA, userData);
263         mModelServiceProvider.set(new IsolatedModelServiceProvider());
264         IIsolatedModelService modelService = mModelServiceProvider.get().getModelService(mContext);
265         serviceParams.putBinder(Constants.EXTRA_MODEL_SERVICE_BINDER, modelService.asBinder());
266 
267         return serviceParams;
268     }
269 
270     @Override
uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture)271     public void uploadServiceFlowMetrics(ListenableFuture<Bundle> runServiceFuture) {
272         sLogger.d(TAG + ": uploading service flow metrics.");
273         var unused =
274                 FluentFuture.from(runServiceFuture)
275                         .transform(
276                                 val -> {
277                                     StatsUtils.writeServiceRequestMetrics(
278                                             Constants.API_NAME_SERVICE_ON_EXECUTE,
279                                             mService.getPackageName(),
280                                             val,
281                                             mInjector.getClock(),
282                                             Constants.STATUS_SUCCESS,
283                                             mStartServiceTimeMillis.get());
284                                     return val;
285                                 },
286                                 mInjector.getExecutor())
287                         .catchingAsync(
288                                 Exception.class,
289                                 e -> {
290                                     StatsUtils.writeServiceRequestMetrics(
291                                             Constants.API_NAME_SERVICE_ON_EXECUTE,
292                                             mService.getPackageName(),
293 
294                                             /* result= */ null,
295                                             mInjector.getClock(),
296                                             Constants.STATUS_INTERNAL_ERROR,
297                                             mStartServiceTimeMillis.get());
298                                     return Futures.immediateFailedFuture(e);
299                                 },
300                                 mInjector.getExecutor());
301     }
302 
303     @Override
getServiceFlowResultFuture( ListenableFuture<Bundle> runServiceFuture)304     public ListenableFuture<Bundle> getServiceFlowResultFuture(
305             ListenableFuture<Bundle> runServiceFuture) {
306         ListenableFuture<ExecuteOutputParcel> executeResultFuture =
307                 FluentFuture.from(runServiceFuture)
308                         .transform(
309                                 result -> result.getParcelable(
310                                         Constants.EXTRA_RESULT, ExecuteOutputParcel.class),
311                                 mInjector.getExecutor()
312                         );
313 
314         ListenableFuture<Long> queryIdFuture = FluentFuture.from(executeResultFuture)
315                 .transformAsync(this::validateExecuteOutput, mInjector.getExecutor())
316                 .transformAsync(this::logQuery, mInjector.getExecutor());
317 
318         return FluentFuture.from(
319                                 Futures.whenAllSucceed(executeResultFuture, queryIdFuture)
320                                         .callAsync(
321                                                 () -> createResultBundle(
322                                                         executeResultFuture, queryIdFuture),
323                                                 mInjector.getExecutor()))
324                         .withTimeout(
325                                 mInjector.getFlags().getIsolatedServiceDeadlineSeconds(),
326                                 TimeUnit.SECONDS,
327                                 mInjector.getScheduledExecutor()
328                         );
329     }
330 
331     @Override
returnResultThroughCallback(ListenableFuture<Bundle> serviceFlowResultFuture)332     public void returnResultThroughCallback(ListenableFuture<Bundle> serviceFlowResultFuture) {
333         Futures.addCallback(
334                 serviceFlowResultFuture,
335                 new FutureCallback<Bundle>() {
336                     @Override
337                     public void onSuccess(Bundle bundle) {
338                         sendSuccessResult(bundle);
339                     }
340 
341                     @Override
342                     public void onFailure(Throwable t) {
343                         sLogger.w(TAG + ": Request failed.", t);
344                         if (t instanceof OdpServiceException) {
345                             OdpServiceException e = (OdpServiceException) t;
346 
347                             sendErrorResult(
348                                     e.getErrorCode(),
349                                     DebugUtils.getIsolatedServiceExceptionCode(
350                                             mContext, mService, e),
351                                     t);
352                         } else {
353                             int errorCode =
354                                     t instanceof TimeoutException
355                                             ? Constants.STATUS_ISOLATED_SERVICE_TIMEOUT
356                                             : Constants.STATUS_INTERNAL_ERROR;
357                             sLogger.w(TAG + ": Failing with error code: " + errorCode);
358                             sendErrorResult(errorCode, /* isolatedServiceErrorCode= */ 0, t);
359                         }
360                     }
361                 },
362                 mInjector.getExecutor());
363     }
364 
365     @Override
cleanUpServiceParams()366     public void cleanUpServiceParams() {
367         mModelServiceProvider.get().unBindFromModelService();
368     }
369 
validateExecuteOutput( ExecuteOutputParcel result)370     private ListenableFuture<ExecuteOutputParcel> validateExecuteOutput(
371             ExecuteOutputParcel result) {
372         sLogger.d(TAG + ": validateExecuteOutput() started.");
373         if (!mInjector.shouldValidateExecuteOutput()) {
374             sLogger.d(TAG + ": validateExecuteOutput() skipped.");
375             return Futures.immediateFuture(result);
376         }
377         try {
378                 OnDevicePersonalizationVendorDataDao vendorDataDao =
379                         OnDevicePersonalizationVendorDataDao.getInstance(mContext,
380                                 mService,
381                                 PackageUtils.getCertDigest(mContext, mService.getPackageName()));
382                 if (result.getRenderingConfig() != null) {
383                     Set<String> keyset = vendorDataDao.readAllVendorDataKeys();
384                     if (!keyset.containsAll(result.getRenderingConfig().getKeys())) {
385                     return Futures.immediateFailedFuture(
386                             new OdpServiceException(Constants.STATUS_SERVICE_FAILED));
387                     }
388                 }
389             } catch (Exception e) {
390             return Futures.immediateFailedFuture(
391                     new OdpServiceException(Constants.STATUS_SERVICE_FAILED));
392             }
393         sLogger.d(TAG + ": validateExecuteOutput() succeeded.");
394         return Futures.immediateFuture(result);
395     }
396 
logQuery(ExecuteOutputParcel result)397     private ListenableFuture<Long> logQuery(ExecuteOutputParcel result) {
398         sLogger.d(TAG + ": logQuery() started.");
399         if (!UserPrivacyStatus.getInstance().isMeasurementEnabled()) {
400             sLogger.d(TAG + ": User control is not given for measurement,"
401                             + "dropping request and event entries.");
402             return Futures.immediateFuture(-1L);
403         }
404         return LogUtils.writeLogRecords(
405                 Constants.TASK_TYPE_EXECUTE,
406                 mContext,
407                 mCallingPackageName,
408                 mService,
409                 result.getRequestLogRecord(),
410                 result.getEventLogRecords());
411     }
412 
createResultBundle( ListenableFuture<ExecuteOutputParcel> resultFuture, ListenableFuture<Long> queryIdFuture)413     private ListenableFuture<Bundle> createResultBundle(
414             ListenableFuture<ExecuteOutputParcel> resultFuture,
415             ListenableFuture<Long> queryIdFuture) {
416         try {
417             sLogger.d(TAG + ": createResultBundle() started.");
418 
419             if (!UserPrivacyStatus.getInstance().isProtectedAudienceEnabled()) {
420                 sLogger.d(TAG + ": user control is not given for targeting.");
421                 return Futures.immediateFuture(Bundle.EMPTY);
422             }
423 
424             ExecuteOutputParcel result = Futures.getDone(resultFuture);
425             long queryId = Futures.getDone(queryIdFuture);
426             RenderingConfig renderingConfig = result.getRenderingConfig();
427 
428             String token;
429             if (renderingConfig == null) {
430                 token = null;
431             } else {
432                 SlotWrapper wrapper = new SlotWrapper(
433                         result.getRequestLogRecord(), renderingConfig,
434                         mService.getPackageName(), queryId);
435                 token = CryptUtils.encrypt(wrapper);
436             }
437             Bundle bundle = new Bundle();
438             bundle.putString(Constants.EXTRA_SURFACE_PACKAGE_TOKEN_STRING, token);
439             // bundle.getInt(key) returns 0 if the key is not found. It can be confused with the
440             // real best value 0, so set it to -1 explicitly to indicate this field is unset.
441             bundle.putInt(
442                     Constants.EXTRA_OUTPUT_BEST_VALUE, processBestValue(result.getBestValue()));
443 
444             return Futures.immediateFuture(bundle);
445         } catch (Exception e) {
446             return Futures.immediateFailedFuture(e);
447         }
448     }
449 
processBestValue(int actualResult)450     private int processBestValue(int actualResult) {
451         int bestValue = -1;
452         if (!isOutputDataAllowed()
453                 || mOptions.getOutputType()
454                         != ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE) {
455             return bestValue;
456         }
457         // Don't apply noise if partner only uses their own data.
458         if (!isPlatformDataProvided()) {
459             return actualResult;
460         }
461         return mInjector
462                 .getNoiseUtil()
463                 .applyNoiseToBestValue(
464                         actualResult, mOptions.getMaxIntValue(), ThreadLocalRandom.current());
465     }
466 
isOutputDataAllowed()467     private boolean isOutputDataAllowed() {
468         try {
469             return AllowListUtils.isPairAllowListed(
470                     mCallingPackageName,
471                     PackageUtils.getCertDigest(mContext, mCallingPackageName),
472                     mService.getPackageName(),
473                     PackageUtils.getCertDigest(mContext, mService.getPackageName()),
474                     mInjector.getFlags().getOutputDataAllowList());
475         } catch (Exception e) {
476             sLogger.d(TAG + ": allow list error", e);
477             return false;
478         }
479     }
480 
isPlatformDataProvided()481     private boolean isPlatformDataProvided() {
482         try {
483             return AllowListUtils.isAllowListed(
484                     mService.getPackageName(),
485                     PackageUtils.getCertDigest(mContext, mService.getPackageName()),
486                     mInjector.getFlags().getDefaultPlatformDataForExecuteAllowlist());
487         } catch (Exception e) {
488             sLogger.d(TAG + ": allow list error", e);
489             return false;
490         }
491     }
492 
sendSuccessResult(Bundle result)493     private void sendSuccessResult(Bundle result) {
494         try {
495             mCallback.onSuccess(
496                     result,
497                     new CalleeMetadata.Builder()
498                             .setServiceEntryTimeMillis(mServiceEntryTimeMillis)
499                             .setCallbackInvokeTimeMillis(
500                             SystemClock.elapsedRealtime()).build());
501         } catch (RemoteException e) {
502             sLogger.w(TAG + ": Callback error", e);
503         }
504     }
505 
sendErrorResult(int errorCode, int isolatedServiceErrorCode, Throwable t)506     private void sendErrorResult(int errorCode, int isolatedServiceErrorCode, Throwable t) {
507         try {
508             mCallback.onError(
509                     errorCode,
510                     isolatedServiceErrorCode,
511                     DebugUtils.serializeExceptionInfo(mService, t),
512                     new CalleeMetadata.Builder()
513                             .setServiceEntryTimeMillis(mServiceEntryTimeMillis)
514                             .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime()).build());
515         } catch (RemoteException e) {
516             sLogger.w(TAG + ": Callback error", e);
517         }
518     }
519 }
520