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