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 android.adservices.ondevicepersonalization; 18 19 import android.adservices.ondevicepersonalization.aidl.IExecuteCallback; 20 import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService; 21 import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback; 22 import android.annotation.CallbackExecutor; 23 import android.annotation.FlaggedApi; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.pm.PackageManager; 29 import android.os.Binder; 30 import android.os.Bundle; 31 import android.os.IBinder; 32 import android.os.OutcomeReceiver; 33 import android.os.PersistableBundle; 34 import android.os.SystemClock; 35 import android.view.SurfaceControlViewHost; 36 37 import com.android.adservices.ondevicepersonalization.flags.Flags; 38 import com.android.federatedcompute.internal.util.AbstractServiceBinder; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.modules.utils.build.SdkLevel; 41 import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice; 42 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 43 import com.android.ondevicepersonalization.internal.util.PersistableBundleUtils; 44 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.concurrent.Executor; 48 49 // TODO(b/289102463): Add a link to the public ODP developer documentation. 50 /** 51 * OnDevicePersonalizationManager provides APIs for apps to load an 52 * {@link IsolatedService} in an isolated process and interact with it. 53 * 54 * An app can request an {@link IsolatedService} to generate content for display 55 * within an {@link android.view.SurfaceView} within the app's view hierarchy, and also write 56 * persistent results to on-device storage which can be consumed by Federated Analytics for 57 * cross-device statistical analysis or by Federated Learning for model training. The displayed 58 * content and the persistent output are both not directly accessible by the calling app. 59 */ 60 @FlaggedApi(Flags.FLAG_ON_DEVICE_PERSONALIZATION_APIS_ENABLED) 61 public class OnDevicePersonalizationManager { 62 /** @hide */ 63 public static final String ON_DEVICE_PERSONALIZATION_SERVICE = 64 "on_device_personalization_service"; 65 private static final String INTENT_FILTER_ACTION = "android.OnDevicePersonalizationService"; 66 private static final String ODP_MANAGING_SERVICE_PACKAGE_SUFFIX = 67 "com.android.ondevicepersonalization.services"; 68 69 private static final String ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX = 70 "com.google.android.ondevicepersonalization.services"; 71 72 private static final String ODP_INTERNAL_ERROR_MESSAGE = 73 "Internal error in the OnDevicePersonalizationService."; 74 75 private static final String ISOLATED_SERVICE_ERROR_MESSAGE = "Error in the IsolatedService."; 76 77 private static final String ODP_DISABLED_ERROR_MESSAGE = 78 "Personalization disabled by device configuration."; 79 80 private static final String TAG = OnDevicePersonalizationManager.class.getSimpleName(); 81 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 82 private final AbstractServiceBinder<IOnDevicePersonalizationManagingService> mServiceBinder; 83 private final Context mContext; 84 85 /** 86 * The result of a call to {@link OnDevicePersonalizationManager#execute(ComponentName, 87 * PersistableBundle, Executor, OutcomeReceiver)} 88 */ 89 public static class ExecuteResult { 90 @Nullable private final SurfacePackageToken mSurfacePackageToken; 91 @Nullable private final byte[] mOutputData; 92 93 /** @hide */ ExecuteResult( @ullable SurfacePackageToken surfacePackageToken, @Nullable byte[] outputData)94 ExecuteResult( 95 @Nullable SurfacePackageToken surfacePackageToken, 96 @Nullable byte[] outputData) { 97 mSurfacePackageToken = surfacePackageToken; 98 mOutputData = outputData; 99 } 100 101 /** 102 * Returns a {@link SurfacePackageToken}, which is an opaque reference to content that 103 * can be displayed in a {@link android.view.SurfaceView}. This may be null if the 104 * {@link IsolatedService} has not generated any content to be displayed within the 105 * calling app. 106 */ getSurfacePackageToken()107 @Nullable public SurfacePackageToken getSurfacePackageToken() { 108 return mSurfacePackageToken; 109 } 110 111 /** 112 * Returns the output data that was returned by the {@link IsolatedService}. This will be 113 * non-null if the {@link IsolatedService} returns any results to the caller, and the 114 * egress of data from the {@link IsolatedService} to the specific calling app is allowed 115 * by policy as well as an allowlist. 116 */ getOutputData()117 @Nullable public byte[] getOutputData() { 118 return mOutputData; 119 } 120 } 121 122 /** @hide */ OnDevicePersonalizationManager(Context context)123 public OnDevicePersonalizationManager(Context context) { 124 this( 125 context, 126 AbstractServiceBinder.getServiceBinderByIntent( 127 context, 128 INTENT_FILTER_ACTION, 129 List.of( 130 ODP_MANAGING_SERVICE_PACKAGE_SUFFIX, 131 ALT_ODP_MANAGING_SERVICE_PACKAGE_SUFFIX), 132 SdkLevel.isAtLeastU() ? Context.BIND_ALLOW_ACTIVITY_STARTS : 0, 133 IOnDevicePersonalizationManagingService.Stub::asInterface)); 134 } 135 136 /** @hide */ 137 @VisibleForTesting OnDevicePersonalizationManager( Context context, AbstractServiceBinder<IOnDevicePersonalizationManagingService> serviceBinder)138 public OnDevicePersonalizationManager( 139 Context context, 140 AbstractServiceBinder<IOnDevicePersonalizationManagingService> serviceBinder) { 141 mContext = context; 142 mServiceBinder = serviceBinder; 143 } 144 145 /** 146 * Executes an {@link IsolatedService} in the OnDevicePersonalization sandbox. The 147 * platform binds to the specified {@link IsolatedService} in an isolated process 148 * and calls {@link IsolatedWorker#onExecute(ExecuteInput, android.os.OutcomeReceiver)} 149 * with the caller-provided parameters. When the {@link IsolatedService} finishes execution, 150 * the platform returns tokens that refer to the results from the service to the caller. 151 * These tokens can be subsequently used to display results in a 152 * {@link android.view.SurfaceView} within the calling app. 153 * 154 * @param service The {@link ComponentName} of the {@link IsolatedService}. 155 * @param params a {@link PersistableBundle} that is passed from the calling app to the 156 * {@link IsolatedService}. The expected contents of this parameter are defined 157 * by the{@link IsolatedService}. The platform does not interpret this parameter. 158 * @param executor the {@link Executor} on which to invoke the callback. 159 * @param receiver This returns a {@link ExecuteResult} object on success or an 160 * {@link Exception} on failure. If the 161 * {@link IsolatedService} returned a {@link RenderingConfig} to be displayed, 162 * {@link ExecuteResult#getSurfacePackageToken()} will return a non-null 163 * {@link SurfacePackageToken}. 164 * The {@link SurfacePackageToken} object can be used in a subsequent 165 * {@link #requestSurfacePackage(SurfacePackageToken, IBinder, int, int, int, Executor, 166 * OutcomeReceiver)} call to display the result in a view. The returned 167 * {@link SurfacePackageToken} may be null to indicate that no output is expected to be 168 * displayed for this request. If the {@link IsolatedService} has returned any output data 169 * and the calling app is allowlisted to receive data from this service, the 170 * {@link ExecuteResult#getOutputData()} will return a non-null byte array. 171 * 172 * In case of an error, the receiver returns one of the following exceptions: 173 * Returns a {@link android.content.pm.PackageManager.NameNotFoundException} if the handler 174 * package is not installed or does not have a valid ODP manifest. 175 * Returns {@link ClassNotFoundException} if the handler class is not found. 176 * Returns an {@link OnDevicePersonalizationException} if execution of the handler fails. 177 */ execute( @onNull ComponentName service, @NonNull PersistableBundle params, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<ExecuteResult, Exception> receiver )178 public void execute( 179 @NonNull ComponentName service, 180 @NonNull PersistableBundle params, 181 @NonNull @CallbackExecutor Executor executor, 182 @NonNull OutcomeReceiver<ExecuteResult, Exception> receiver 183 ) { 184 Objects.requireNonNull(service); 185 Objects.requireNonNull(params); 186 Objects.requireNonNull(executor); 187 Objects.requireNonNull(receiver); 188 Objects.requireNonNull(service.getPackageName()); 189 Objects.requireNonNull(service.getClassName()); 190 if (service.getPackageName().isEmpty()) { 191 throw new IllegalArgumentException("missing service package name"); 192 } 193 if (service.getClassName().isEmpty()) { 194 throw new IllegalArgumentException("missing service class name"); 195 } 196 long startTimeMillis = SystemClock.elapsedRealtime(); 197 198 try { 199 final IOnDevicePersonalizationManagingService odpService = 200 mServiceBinder.getService(executor); 201 202 try { 203 IExecuteCallback callbackWrapper = 204 new IExecuteCallback.Stub() { 205 @Override 206 public void onSuccess( 207 Bundle callbackResult, CalleeMetadata calleeMetadata) { 208 final long token = Binder.clearCallingIdentity(); 209 try { 210 executor.execute( 211 () -> { 212 try { 213 SurfacePackageToken surfacePackageToken = null; 214 if (callbackResult != null) { 215 String tokenString = 216 callbackResult.getString( 217 Constants 218 .EXTRA_SURFACE_PACKAGE_TOKEN_STRING); 219 if (tokenString != null 220 && !tokenString.isBlank()) { 221 surfacePackageToken = 222 new SurfacePackageToken( 223 tokenString); 224 } 225 } 226 byte[] data = 227 callbackResult.getByteArray( 228 Constants.EXTRA_OUTPUT_DATA); 229 receiver.onResult( 230 new ExecuteResult( 231 surfacePackageToken, data)); 232 } catch (Exception e) { 233 receiver.onError(e); 234 } 235 }); 236 } finally { 237 Binder.restoreCallingIdentity(token); 238 logApiCallStats( 239 odpService, 240 service.getPackageName(), 241 Constants.API_NAME_EXECUTE, 242 SystemClock.elapsedRealtime() - startTimeMillis, 243 calleeMetadata.getServiceEntryTimeMillis() - startTimeMillis, 244 SystemClock.elapsedRealtime() 245 - calleeMetadata.getCallbackInvokeTimeMillis(), 246 Constants.STATUS_SUCCESS); 247 } 248 } 249 250 @Override 251 public void onError(int errorCode, int isolatedServiceErrorCode, 252 String message, CalleeMetadata calleeMetadata) { 253 final long token = Binder.clearCallingIdentity(); 254 try { 255 executor.execute( 256 () -> 257 receiver.onError( 258 createException( 259 errorCode, 260 isolatedServiceErrorCode, 261 message))); 262 } finally { 263 Binder.restoreCallingIdentity(token); 264 logApiCallStats( 265 odpService, 266 service.getPackageName(), 267 Constants.API_NAME_EXECUTE, 268 SystemClock.elapsedRealtime() - startTimeMillis, 269 calleeMetadata.getServiceEntryTimeMillis() - startTimeMillis, 270 SystemClock.elapsedRealtime() 271 - calleeMetadata.getCallbackInvokeTimeMillis(), 272 errorCode); 273 } 274 } 275 }; 276 277 Bundle wrappedParams = new Bundle(); 278 wrappedParams.putParcelable( 279 Constants.EXTRA_APP_PARAMS_SERIALIZED, 280 new ByteArrayParceledSlice(PersistableBundleUtils.toByteArray(params))); 281 odpService.execute( 282 mContext.getPackageName(), 283 service, 284 wrappedParams, 285 new CallerMetadata.Builder().setStartTimeMillis(startTimeMillis).build(), 286 callbackWrapper); 287 } catch (Exception e) { 288 logApiCallStats( 289 odpService, 290 service.getPackageName(), 291 Constants.API_NAME_EXECUTE, 292 SystemClock.elapsedRealtime() - startTimeMillis, 293 0, 294 0, 295 Constants.STATUS_INTERNAL_ERROR); 296 receiver.onError(e); 297 } 298 299 } catch (Exception e) { 300 receiver.onError(e); 301 } 302 } 303 304 /** 305 * Requests a {@link android.view.SurfaceControlViewHost.SurfacePackage} to be inserted into a 306 * {@link android.view.SurfaceView} inside the calling app. The surface package will contain an 307 * {@link android.view.View} with the content from a result of a prior call to 308 * {@code #execute(ComponentName, PersistableBundle, Executor, OutcomeReceiver)} running in 309 * the OnDevicePersonalization sandbox. 310 * 311 * @param surfacePackageToken a reference to a {@link SurfacePackageToken} returned by a prior 312 * call to {@code #execute(ComponentName, PersistableBundle, Executor, OutcomeReceiver)}. 313 * @param surfaceViewHostToken the hostToken of the {@link android.view.SurfaceView}, which is 314 * returned by {@link android.view.SurfaceView#getHostToken()} after the 315 * {@link android.view.SurfaceView} has been added to the view hierarchy. 316 * @param displayId the integer ID of the logical display on which to display the 317 * {@link android.view.SurfaceControlViewHost.SurfacePackage}, returned by 318 * {@code Context.getDisplay().getDisplayId()}. 319 * @param width the width of the {@link android.view.SurfaceControlViewHost.SurfacePackage} 320 * in pixels. 321 * @param height the height of the {@link android.view.SurfaceControlViewHost.SurfacePackage} 322 * in pixels. 323 * @param executor the {@link Executor} on which to invoke the callback 324 * @param receiver This either returns a 325 * {@link android.view.SurfaceControlViewHost.SurfacePackage} on success, or 326 * {@link Exception} on failure. The exception type is 327 * {@link OnDevicePersonalizationException} if execution of the handler fails. 328 */ requestSurfacePackage( @onNull SurfacePackageToken surfacePackageToken, @NonNull IBinder surfaceViewHostToken, int displayId, int width, int height, @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SurfaceControlViewHost.SurfacePackage, Exception> receiver )329 public void requestSurfacePackage( 330 @NonNull SurfacePackageToken surfacePackageToken, 331 @NonNull IBinder surfaceViewHostToken, 332 int displayId, 333 int width, 334 int height, 335 @NonNull @CallbackExecutor Executor executor, 336 @NonNull OutcomeReceiver<SurfaceControlViewHost.SurfacePackage, Exception> receiver 337 ) { 338 Objects.requireNonNull(surfacePackageToken); 339 Objects.requireNonNull(surfaceViewHostToken); 340 Objects.requireNonNull(executor); 341 Objects.requireNonNull(receiver); 342 if (width <= 0) { 343 throw new IllegalArgumentException("width must be > 0"); 344 } 345 346 if (height <= 0) { 347 throw new IllegalArgumentException("height must be > 0"); 348 } 349 350 if (displayId < 0) { 351 throw new IllegalArgumentException("displayId must be >= 0"); 352 } 353 long startTimeMillis = SystemClock.elapsedRealtime(); 354 355 try { 356 final IOnDevicePersonalizationManagingService service = 357 Objects.requireNonNull(mServiceBinder.getService(executor)); 358 long serviceInvokedTimeMillis = SystemClock.elapsedRealtime(); 359 360 try { 361 IRequestSurfacePackageCallback callbackWrapper = 362 new IRequestSurfacePackageCallback.Stub() { 363 @Override 364 public void onSuccess( 365 SurfaceControlViewHost.SurfacePackage surfacePackage, 366 CalleeMetadata calleeMetadata) { 367 final long token = Binder.clearCallingIdentity(); 368 try { 369 executor.execute( 370 () -> { 371 receiver.onResult(surfacePackage); 372 }); 373 } finally { 374 Binder.restoreCallingIdentity(token); 375 logApiCallStats( 376 service, 377 "", 378 Constants.API_NAME_REQUEST_SURFACE_PACKAGE, 379 SystemClock.elapsedRealtime() - startTimeMillis, 380 0, 381 SystemClock.elapsedRealtime() 382 - calleeMetadata.getCallbackInvokeTimeMillis(), 383 Constants.STATUS_SUCCESS); 384 } 385 386 } 387 388 @Override 389 public void onError(int errorCode, int isolatedServiceErrorCode, 390 String message, CalleeMetadata calleeMetadata) { 391 final long token = Binder.clearCallingIdentity(); 392 try { 393 executor.execute( 394 () -> 395 receiver.onError( 396 createException( 397 errorCode, 398 isolatedServiceErrorCode, 399 message))); 400 } finally { 401 Binder.restoreCallingIdentity(token); 402 logApiCallStats( 403 service, "", 404 Constants.API_NAME_REQUEST_SURFACE_PACKAGE, 405 SystemClock.elapsedRealtime() - startTimeMillis, 406 0, 407 SystemClock.elapsedRealtime() 408 - calleeMetadata.getCallbackInvokeTimeMillis(), 409 errorCode); 410 } 411 } 412 }; 413 414 service.requestSurfacePackage( 415 surfacePackageToken.getTokenString(), 416 surfaceViewHostToken, 417 displayId, 418 width, 419 height, 420 new CallerMetadata.Builder().setStartTimeMillis(startTimeMillis).build(), 421 callbackWrapper); 422 logApiCallStats( 423 service, 424 "", 425 Constants.API_NAME_REQUEST_SURFACE_PACKAGE, 426 SystemClock.elapsedRealtime() - startTimeMillis, 427 SystemClock.elapsedRealtime() - serviceInvokedTimeMillis, 428 0, 429 Constants.STATUS_SUCCESS); 430 431 } catch (Exception e) { 432 logApiCallStats( 433 service, 434 "", 435 Constants.API_NAME_REQUEST_SURFACE_PACKAGE, 436 SystemClock.elapsedRealtime() - startTimeMillis, 437 0, 438 0, 439 Constants.STATUS_INTERNAL_ERROR); 440 receiver.onError(e); 441 } 442 443 } catch (Exception e) { 444 receiver.onError(e); 445 } 446 } 447 convertMessage(int errorCode, String message)448 private static String convertMessage(int errorCode, String message) { 449 // Defer to existing message received from service callback if it is non-empty, else 450 // translate the internal error codes into error messages. 451 if (message != null && !message.isBlank()) { 452 return message; 453 } 454 455 switch (errorCode) { 456 case Constants.STATUS_INTERNAL_ERROR: 457 return ODP_INTERNAL_ERROR_MESSAGE; 458 case Constants.STATUS_SERVICE_FAILED: 459 return ISOLATED_SERVICE_ERROR_MESSAGE; 460 case Constants.STATUS_PERSONALIZATION_DISABLED: 461 return ODP_DISABLED_ERROR_MESSAGE; 462 default: 463 sLogger.w(TAG + "Unexpected error code while creating exception: " + errorCode); 464 return ""; 465 } 466 } 467 createException( int errorCode, int isolatedServiceErrorCode, String message)468 private static Exception createException( 469 int errorCode, int isolatedServiceErrorCode, String message) { 470 if (errorCode == Constants.STATUS_NAME_NOT_FOUND) { 471 return new PackageManager.NameNotFoundException(); 472 } else if (errorCode == Constants.STATUS_CLASS_NOT_FOUND) { 473 return new ClassNotFoundException(); 474 } else if (errorCode == Constants.STATUS_SERVICE_FAILED) { 475 if (isolatedServiceErrorCode > 0 && isolatedServiceErrorCode < 128) { 476 return new OnDevicePersonalizationException( 477 OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED, 478 new IsolatedServiceException(isolatedServiceErrorCode)); 479 } else { 480 return new OnDevicePersonalizationException( 481 OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED, 482 convertMessage(errorCode, message)); 483 } 484 } else if (errorCode == Constants.STATUS_PERSONALIZATION_DISABLED) { 485 return new OnDevicePersonalizationException( 486 OnDevicePersonalizationException.ERROR_PERSONALIZATION_DISABLED, 487 convertMessage(errorCode, message)); 488 } else { 489 return new IllegalStateException(convertMessage(errorCode, message)); 490 } 491 } 492 logApiCallStats( IOnDevicePersonalizationManagingService service, String sdkPackageName, int apiName, long latencyMillis, long rpcCallLatencyMillis, long rpcReturnLatencyMillis, int responseCode)493 private void logApiCallStats( 494 IOnDevicePersonalizationManagingService service, 495 String sdkPackageName, 496 int apiName, 497 long latencyMillis, 498 long rpcCallLatencyMillis, 499 long rpcReturnLatencyMillis, 500 int responseCode) { 501 try { 502 if (service != null) { 503 service.logApiCallStats(sdkPackageName, apiName, latencyMillis, 504 rpcCallLatencyMillis, rpcReturnLatencyMillis, responseCode); 505 } 506 } catch (Exception e) { 507 sLogger.e(e, TAG + ": Error logging API call stats"); 508 } 509 } 510 } 511