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