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