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.IDataAccessService; 20 import android.adservices.ondevicepersonalization.aidl.IFederatedComputeService; 21 import android.adservices.ondevicepersonalization.aidl.IIsolatedModelService; 22 import android.adservices.ondevicepersonalization.aidl.IIsolatedService; 23 import android.adservices.ondevicepersonalization.aidl.IIsolatedServiceCallback; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.Service; 27 import android.content.Intent; 28 import android.os.Binder; 29 import android.os.Bundle; 30 import android.os.IBinder; 31 import android.os.OutcomeReceiver; 32 import android.os.Parcelable; 33 import android.os.RemoteException; 34 import android.os.SystemClock; 35 36 import com.android.ondevicepersonalization.internal.util.ExceptionInfo; 37 import com.android.ondevicepersonalization.internal.util.LoggerFactory; 38 import com.android.ondevicepersonalization.internal.util.OdpParceledListSlice; 39 40 import java.util.Objects; 41 import java.util.function.Function; 42 43 // TODO(b/289102463): Add a link to the public ODP developer documentation. 44 /** 45 * Base class for services that are started by ODP on a call to 46 * {@code OnDevicePersonalizationManager#execute(ComponentName, PersistableBundle, 47 * java.util.concurrent.Executor, OutcomeReceiver)} 48 * and run in an <a 49 * href="https://developer.android.com/guide/topics/manifest/service-element#isolated">isolated 50 * process</a>. The service can produce content to be displayed in a 51 * {@link android.view.SurfaceView} in a calling app and write persistent results to on-device 52 * storage, which can be consumed by Federated Analytics for cross-device statistical analysis or 53 * by Federated Learning for model training. 54 * Client apps use {@link OnDevicePersonalizationManager} to interact with an {@link 55 * IsolatedService}. 56 */ 57 public abstract class IsolatedService extends Service { 58 private static final String TAG = IsolatedService.class.getSimpleName(); 59 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 60 private static final int MAX_EXCEPTION_CHAIN_DEPTH = 3; 61 private IBinder mBinder; 62 63 /** Creates a binder for an {@link IsolatedService}. */ 64 @Override onCreate()65 public void onCreate() { 66 mBinder = new ServiceBinder(); 67 } 68 69 /** 70 * Handles binding to the {@link IsolatedService}. 71 * 72 * @param intent The Intent that was used to bind to this service, as given to {@link 73 * android.content.Context#bindService Context.bindService}. Note that any extras that were 74 * included with the Intent at that point will <em>not</em> be seen here. 75 */ 76 @Override 77 @Nullable onBind(@onNull Intent intent)78 public IBinder onBind(@NonNull Intent intent) { 79 return mBinder; 80 } 81 82 /** 83 * Return an instance of an {@link IsolatedWorker} that handles client requests. 84 * 85 * @param requestToken an opaque token that identifies the current request to the service that 86 * must be passed to service methods that depend on per-request state. 87 */ 88 @NonNull onRequest(@onNull RequestToken requestToken)89 public abstract IsolatedWorker onRequest(@NonNull RequestToken requestToken); 90 91 /** 92 * Returns a Data Access Object for the REMOTE_DATA table. The REMOTE_DATA table is a read-only 93 * key-value store that contains data that is periodically downloaded from an endpoint declared 94 * in the <download> tag in the ODP manifest of the service, as shown in the following example. 95 * 96 * <pre>{@code 97 * <!-- Contents of res/xml/OdpSettings.xml --> 98 * <on-device-personalization> 99 * <!-- Name of the service subclass --> 100 * <service "com.example.odpsample.SampleService"> 101 * <!-- If this tag is present, ODP will periodically poll this URL and 102 * download content to populate REMOTE_DATA. Adopters that do not need to 103 * download content from their servers can skip this tag. --> 104 * <download-settings url="https://example.com/get" /> 105 * </service> 106 * </on-device-personalization> 107 * }</pre> 108 * 109 * @param requestToken an opaque token that identifies the current request to the service. 110 * @return A {@link KeyValueStore} object that provides access to the REMOTE_DATA table. The 111 * methods in the returned {@link KeyValueStore} are blocking operations and should be 112 * called from a worker thread and not the main thread or a binder thread. 113 * @see #onRequest(RequestToken) 114 */ 115 @NonNull getRemoteData(@onNull RequestToken requestToken)116 public final KeyValueStore getRemoteData(@NonNull RequestToken requestToken) { 117 return new RemoteDataImpl(requestToken.getDataAccessService()); 118 } 119 120 /** 121 * Returns a Data Access Object for the LOCAL_DATA table. The LOCAL_DATA table is a persistent 122 * key-value store that the service can use to store any data. The contents of this table are 123 * visible only to the service running in an isolated process and cannot be sent outside the 124 * device. 125 * 126 * @param requestToken an opaque token that identifies the current request to the service. 127 * @return A {@link MutableKeyValueStore} object that provides access to the LOCAL_DATA table. 128 * The methods in the returned {@link MutableKeyValueStore} are blocking operations and 129 * should be called from a worker thread and not the main thread or a binder thread. 130 * @see #onRequest(RequestToken) 131 */ 132 @NonNull getLocalData(@onNull RequestToken requestToken)133 public final MutableKeyValueStore getLocalData(@NonNull RequestToken requestToken) { 134 return new LocalDataImpl(requestToken.getDataAccessService()); 135 } 136 137 /** 138 * Returns a DAO for the REQUESTS and EVENTS tables that provides 139 * access to the rows that are readable by the IsolatedService. 140 * 141 * @param requestToken an opaque token that identifies the current request to the service. 142 * @return A {@link LogReader} object that provides access to the REQUESTS and EVENTS table. 143 * The methods in the returned {@link LogReader} are blocking operations and 144 * should be called from a worker thread and not the main thread or a binder thread. 145 * @see #onRequest(RequestToken) 146 */ 147 @NonNull getLogReader(@onNull RequestToken requestToken)148 public final LogReader getLogReader(@NonNull RequestToken requestToken) { 149 return new LogReader(requestToken.getDataAccessService()); 150 } 151 152 /** 153 * Returns an {@link EventUrlProvider} for the current request. The {@link EventUrlProvider} 154 * provides URLs that can be embedded in HTML. When the HTML is rendered in an 155 * {@link android.webkit.WebView}, the platform intercepts requests to these URLs and invokes 156 * {@code IsolatedWorker#onEvent(EventInput, Consumer)}. 157 * 158 * @param requestToken an opaque token that identifies the current request to the service. 159 * @return An {@link EventUrlProvider} that returns event tracking URLs. 160 * @see #onRequest(RequestToken) 161 */ 162 @NonNull getEventUrlProvider(@onNull RequestToken requestToken)163 public final EventUrlProvider getEventUrlProvider(@NonNull RequestToken requestToken) { 164 return new EventUrlProvider(requestToken.getDataAccessService()); 165 } 166 167 /** 168 * Returns the platform-provided {@link UserData} for the current request. 169 * 170 * @param requestToken an opaque token that identifies the current request to the service. 171 * @return A {@link UserData} object. 172 * @see #onRequest(RequestToken) 173 */ 174 @Nullable getUserData(@onNull RequestToken requestToken)175 public final UserData getUserData(@NonNull RequestToken requestToken) { 176 return requestToken.getUserData(); 177 } 178 179 /** 180 * Returns an {@link FederatedComputeScheduler} for the current request. The {@link 181 * FederatedComputeScheduler} can be used to schedule and cancel federated computation jobs. The 182 * federated computation includes federated learning and federated analytics jobs. 183 * 184 * @param requestToken an opaque token that identifies the current request to the service. 185 * @return An {@link FederatedComputeScheduler} that returns a federated computation job 186 * scheduler. 187 * @see #onRequest(RequestToken) 188 */ 189 @NonNull getFederatedComputeScheduler( @onNull RequestToken requestToken)190 public final FederatedComputeScheduler getFederatedComputeScheduler( 191 @NonNull RequestToken requestToken) { 192 return new FederatedComputeScheduler( 193 requestToken.getFederatedComputeService(), 194 requestToken.getDataAccessService()); 195 } 196 197 /** 198 * Returns an {@link ModelManager} for the current request. The {@link ModelManager} can be used 199 * to do model inference. It only supports Tensorflow Lite model inference now. 200 * 201 * @param requestToken an opaque token that identifies the current request to the service. 202 * @return An {@link ModelManager} that can be used for model inference. 203 */ 204 @NonNull getModelManager(@onNull RequestToken requestToken)205 public final ModelManager getModelManager(@NonNull RequestToken requestToken) { 206 return new ModelManager( 207 requestToken.getDataAccessService(), requestToken.getModelService()); 208 } 209 210 // TODO(b/228200518): Add onBidRequest()/onBidResponse() methods. 211 212 class ServiceBinder extends IIsolatedService.Stub { 213 @Override onRequest( int operationCode, @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback)214 public void onRequest( 215 int operationCode, 216 @NonNull Bundle params, 217 @NonNull IIsolatedServiceCallback resultCallback) { 218 Objects.requireNonNull(params); 219 Objects.requireNonNull(resultCallback); 220 final long token = Binder.clearCallingIdentity(); 221 // TODO(b/228200518): Ensure that caller is ODP Service. 222 // TODO(b/323592348): Add model inference in other flows. 223 try { 224 performRequest(operationCode, params, resultCallback); 225 } finally { 226 Binder.restoreCallingIdentity(token); 227 } 228 } 229 performRequest( int operationCode, @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback)230 private void performRequest( 231 int operationCode, 232 @NonNull Bundle params, 233 @NonNull IIsolatedServiceCallback resultCallback) { 234 235 if (operationCode == Constants.OP_EXECUTE) { 236 performExecute(params, resultCallback); 237 } else if (operationCode == Constants.OP_DOWNLOAD) { 238 performDownload(params, resultCallback); 239 } else if (operationCode == Constants.OP_RENDER) { 240 performRender(params, resultCallback); 241 } else if (operationCode == Constants.OP_WEB_VIEW_EVENT) { 242 performOnWebViewEvent(params, resultCallback); 243 } else if (operationCode == Constants.OP_TRAINING_EXAMPLE) { 244 performOnTrainingExample(params, resultCallback); 245 } else if (operationCode == Constants.OP_WEB_TRIGGER) { 246 performOnWebTrigger(params, resultCallback); 247 } else { 248 throw new IllegalArgumentException("Invalid op code: " + operationCode); 249 } 250 } 251 performOnWebTrigger( @onNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback)252 private void performOnWebTrigger( 253 @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) { 254 try { 255 WebTriggerInputParcel inputParcel = 256 Objects.requireNonNull( 257 params.getParcelable( 258 Constants.EXTRA_INPUT, WebTriggerInputParcel.class), 259 () -> 260 String.format( 261 "Missing '%s' from input params!", 262 Constants.EXTRA_INPUT)); 263 WebTriggerInput input = new WebTriggerInput(inputParcel); 264 IDataAccessService binder = getDataAccessService(params); 265 UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class); 266 RequestToken requestToken = new RequestToken(binder, null, null, userData); 267 IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken); 268 isolatedWorker.onWebTrigger( 269 input, 270 new WrappedCallback<WebTriggerOutput, WebTriggerOutputParcel>( 271 resultCallback, requestToken, v -> new WebTriggerOutputParcel(v))); 272 } catch (Exception e) { 273 sLogger.e(e, TAG + ": Exception during Isolated Service web trigger operation."); 274 sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e); 275 } 276 } 277 performOnTrainingExample( @onNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback)278 private void performOnTrainingExample( 279 @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) { 280 try { 281 TrainingExamplesInputParcel inputParcel = 282 Objects.requireNonNull( 283 params.getParcelable( 284 Constants.EXTRA_INPUT, TrainingExamplesInputParcel.class), 285 () -> 286 String.format( 287 "Missing '%s' from input params!", 288 Constants.EXTRA_INPUT)); 289 TrainingExamplesInput input = new TrainingExamplesInput(inputParcel); 290 IDataAccessService binder = getDataAccessService(params); 291 UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class); 292 RequestToken requestToken = new RequestToken(binder, null, null, userData); 293 IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken); 294 isolatedWorker.onTrainingExamples( 295 input, 296 new WrappedCallback<TrainingExamplesOutput, TrainingExamplesOutputParcel>( 297 resultCallback, 298 requestToken, 299 v -> 300 new TrainingExamplesOutputParcel.Builder() 301 .setTrainingExampleRecords( 302 new OdpParceledListSlice< 303 TrainingExampleRecord>( 304 v.getTrainingExampleRecords())) 305 .build())); 306 } catch (Exception e) { 307 sLogger.e(e, 308 TAG + ": Exception during Isolated Service training example operation."); 309 sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e); 310 } 311 } 312 performOnWebViewEvent( @onNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback)313 private void performOnWebViewEvent( 314 @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) { 315 try { 316 EventInputParcel inputParcel = 317 Objects.requireNonNull( 318 params.getParcelable(Constants.EXTRA_INPUT, EventInputParcel.class), 319 () -> 320 String.format( 321 "Missing '%s' from input params!", 322 Constants.EXTRA_INPUT)); 323 EventInput input = new EventInput(inputParcel); 324 IDataAccessService binder = getDataAccessService(params); 325 UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class); 326 RequestToken requestToken = new RequestToken(binder, null, null, userData); 327 IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken); 328 isolatedWorker.onEvent( 329 input, 330 new WrappedCallback<EventOutput, EventOutputParcel>( 331 resultCallback, requestToken, v -> new EventOutputParcel(v))); 332 } catch (Exception e) { 333 sLogger.e(e, TAG + ": Exception during Isolated Service web view event operation."); 334 sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e); 335 } 336 } 337 performRender( @onNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback)338 private void performRender( 339 @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) { 340 try { 341 RenderInputParcel inputParcel = 342 Objects.requireNonNull( 343 params.getParcelable( 344 Constants.EXTRA_INPUT, RenderInputParcel.class), 345 () -> 346 String.format( 347 "Missing '%s' from input params!", 348 Constants.EXTRA_INPUT)); 349 RenderInput input = new RenderInput(inputParcel); 350 Objects.requireNonNull(input.getRenderingConfig()); 351 IDataAccessService binder = getDataAccessService(params); 352 RequestToken requestToken = new RequestToken(binder, null, null, null); 353 IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken); 354 isolatedWorker.onRender( 355 input, 356 new WrappedCallback<RenderOutput, RenderOutputParcel>( 357 resultCallback, requestToken, v -> new RenderOutputParcel(v))); 358 } catch (Exception e) { 359 sLogger.e(e, TAG + ": Exception during Isolated Service render operation."); 360 sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e); 361 } 362 } 363 performDownload( @onNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback)364 private void performDownload( 365 @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) { 366 try { 367 DownloadInputParcel inputParcel = 368 Objects.requireNonNull( 369 params.getParcelable( 370 Constants.EXTRA_INPUT, DownloadInputParcel.class), 371 () -> 372 String.format( 373 "Missing '%s' from input params!", 374 Constants.EXTRA_INPUT)); 375 KeyValueStore downloadedContents = 376 new RemoteDataImpl( 377 IDataAccessService.Stub.asInterface( 378 Objects.requireNonNull( 379 inputParcel.getDataAccessServiceBinder(), 380 "Failed to get IDataAccessService binder from the input params!"))); 381 382 DownloadCompletedInput input = 383 new DownloadCompletedInput(downloadedContents); 384 385 IDataAccessService binder = getDataAccessService(params); 386 387 IFederatedComputeService fcBinder = getFederatedComputeService(params); 388 UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class); 389 RequestToken requestToken = new RequestToken(binder, fcBinder, null, userData); 390 IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken); 391 isolatedWorker.onDownloadCompleted( 392 input, 393 new WrappedCallback<DownloadCompletedOutput, DownloadCompletedOutputParcel>( 394 resultCallback, 395 requestToken, 396 v -> new DownloadCompletedOutputParcel(v))); 397 } catch (Exception e) { 398 sLogger.e(e, TAG + ": Exception during Isolated Service download operation."); 399 sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e); 400 } 401 } 402 getIsolatedModelService(@onNull Bundle params)403 private static IIsolatedModelService getIsolatedModelService(@NonNull Bundle params) { 404 IIsolatedModelService modelServiceBinder = 405 IIsolatedModelService.Stub.asInterface( 406 Objects.requireNonNull( 407 params.getBinder(Constants.EXTRA_MODEL_SERVICE_BINDER), 408 () -> 409 String.format( 410 "Missing '%s' from input params!", 411 Constants.EXTRA_MODEL_SERVICE_BINDER))); 412 Objects.requireNonNull( 413 modelServiceBinder, 414 "Failed to get IIsolatedModelService binder from the input params!"); 415 return modelServiceBinder; 416 } 417 getFederatedComputeService(@onNull Bundle params)418 private static IFederatedComputeService getFederatedComputeService(@NonNull Bundle params) { 419 IFederatedComputeService fcBinder = 420 IFederatedComputeService.Stub.asInterface( 421 Objects.requireNonNull( 422 params.getBinder( 423 Constants.EXTRA_FEDERATED_COMPUTE_SERVICE_BINDER), 424 () -> 425 String.format( 426 "Missing '%s' from input params!", 427 Constants 428 .EXTRA_FEDERATED_COMPUTE_SERVICE_BINDER))); 429 Objects.requireNonNull( 430 fcBinder, 431 "Failed to get IFederatedComputeService binder from the input params!"); 432 return fcBinder; 433 } 434 getDataAccessService(@onNull Bundle params)435 private static IDataAccessService getDataAccessService(@NonNull Bundle params) { 436 IDataAccessService binder = 437 IDataAccessService.Stub.asInterface( 438 Objects.requireNonNull( 439 params.getBinder(Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER), 440 () -> 441 String.format( 442 "Missing '%s' from input params!", 443 Constants.EXTRA_DATA_ACCESS_SERVICE_BINDER))); 444 Objects.requireNonNull( 445 binder, "Failed to get IDataAccessService binder from the input params!"); 446 return binder; 447 } 448 performExecute( @onNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback)449 private void performExecute( 450 @NonNull Bundle params, @NonNull IIsolatedServiceCallback resultCallback) { 451 try { 452 ExecuteInputParcel inputParcel = 453 Objects.requireNonNull( 454 params.getParcelable( 455 Constants.EXTRA_INPUT, ExecuteInputParcel.class), 456 () -> 457 String.format( 458 "Missing '%s' from input params!", 459 Constants.EXTRA_INPUT)); 460 ExecuteInput input = new ExecuteInput(inputParcel); 461 Objects.requireNonNull( 462 input.getAppPackageName(), 463 "Failed to get AppPackageName from the input params!"); 464 IDataAccessService binder = getDataAccessService(params); 465 IFederatedComputeService fcBinder = getFederatedComputeService(params); 466 IIsolatedModelService modelServiceBinder = getIsolatedModelService(params); 467 UserData userData = params.getParcelable(Constants.EXTRA_USER_DATA, UserData.class); 468 RequestToken requestToken = 469 new RequestToken(binder, fcBinder, modelServiceBinder, userData); 470 IsolatedWorker isolatedWorker = IsolatedService.this.onRequest(requestToken); 471 isolatedWorker.onExecute( 472 input, 473 new WrappedCallback<ExecuteOutput, ExecuteOutputParcel>( 474 resultCallback, requestToken, v -> new ExecuteOutputParcel(v))); 475 } catch (Exception e) { 476 sLogger.e(e, TAG + ": Exception during Isolated Service execute operation."); 477 sendError(resultCallback, Constants.STATUS_INTERNAL_ERROR, e); 478 } 479 } 480 } 481 sendError(IIsolatedServiceCallback resultCallback, int errorCode, Throwable t)482 private void sendError(IIsolatedServiceCallback resultCallback, int errorCode, Throwable t) { 483 try { 484 resultCallback.onError( 485 errorCode, 0, ExceptionInfo.toByteArray(t, MAX_EXCEPTION_CHAIN_DEPTH)); 486 } catch (RemoteException re) { 487 sLogger.e(re, TAG + ": Isolated Service Callback failed."); 488 } 489 } 490 491 private static class WrappedCallback<T, U extends Parcelable> 492 implements OutcomeReceiver<T, IsolatedServiceException> { 493 @NonNull private final IIsolatedServiceCallback mCallback; 494 @NonNull private final RequestToken mRequestToken; 495 @NonNull private final Function<T, U> mConverter; 496 WrappedCallback( IIsolatedServiceCallback callback, RequestToken requestToken, Function<T, U> converter)497 WrappedCallback( 498 IIsolatedServiceCallback callback, 499 RequestToken requestToken, 500 Function<T, U> converter) { 501 mCallback = Objects.requireNonNull(callback); 502 mRequestToken = Objects.requireNonNull(requestToken); 503 mConverter = Objects.requireNonNull(converter); 504 } 505 506 @Override onResult(T result)507 public void onResult(T result) { 508 long elapsedTimeMillis = 509 SystemClock.elapsedRealtime() - mRequestToken.getStartTimeMillis(); 510 if (result == null) { 511 sendError(0, new IllegalArgumentException("missing result")); 512 } else { 513 Bundle bundle = new Bundle(); 514 U wrappedResult = mConverter.apply(result); 515 bundle.putParcelable(Constants.EXTRA_RESULT, wrappedResult); 516 bundle.putParcelable(Constants.EXTRA_CALLEE_METADATA, 517 new CalleeMetadata.Builder() 518 .setElapsedTimeMillis(elapsedTimeMillis) 519 .build()); 520 try { 521 mCallback.onSuccess(bundle); 522 } catch (RemoteException e) { 523 sLogger.w(TAG + ": Callback failed.", e); 524 } 525 } 526 } 527 528 @Override onError(IsolatedServiceException e)529 public void onError(IsolatedServiceException e) { 530 sendError(e.getErrorCode(), e); 531 } 532 sendError(int isolatedServiceErrorCode, Throwable t)533 private void sendError(int isolatedServiceErrorCode, Throwable t) { 534 try { 535 // TODO(b/324478256): Log and report the error code from e. 536 mCallback.onError( 537 Constants.STATUS_SERVICE_FAILED, 538 isolatedServiceErrorCode, 539 ExceptionInfo.toByteArray(t, MAX_EXCEPTION_CHAIN_DEPTH)); 540 } catch (RemoteException re) { 541 sLogger.w(TAG + ": Callback failed.", re); 542 } 543 } 544 } 545 } 546