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.data; 18 19 import android.adservices.ondevicepersonalization.Constants; 20 import android.adservices.ondevicepersonalization.EventLogRecord; 21 import android.adservices.ondevicepersonalization.ModelId; 22 import android.adservices.ondevicepersonalization.RequestLogRecord; 23 import android.adservices.ondevicepersonalization.aidl.IDataAccessService; 24 import android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.pm.PackageManager; 30 import android.net.Uri; 31 import android.os.Bundle; 32 import android.os.ParcelFileDescriptor; 33 import android.os.PersistableBundle; 34 import android.os.RemoteException; 35 36 import com.android.internal.annotations.VisibleForTesting; 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.internal.util.OdpParceledListSlice; 41 import com.android.ondevicepersonalization.services.OnDevicePersonalizationExecutors; 42 import com.android.ondevicepersonalization.services.data.events.EventUrlHelper; 43 import com.android.ondevicepersonalization.services.data.events.EventUrlPayload; 44 import com.android.ondevicepersonalization.services.data.events.EventsDao; 45 import com.android.ondevicepersonalization.services.data.events.JoinedEvent; 46 import com.android.ondevicepersonalization.services.data.events.Query; 47 import com.android.ondevicepersonalization.services.data.vendor.LocalData; 48 import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationLocalDataDao; 49 import com.android.ondevicepersonalization.services.data.vendor.OnDevicePersonalizationVendorDataDao; 50 import com.android.ondevicepersonalization.services.statsd.ApiCallStats; 51 import com.android.ondevicepersonalization.services.statsd.OdpStatsdLogger; 52 import com.android.ondevicepersonalization.services.util.IoUtils; 53 import com.android.ondevicepersonalization.services.util.OnDevicePersonalizationFlatbufferUtils; 54 55 import com.google.common.util.concurrent.ListeningExecutorService; 56 57 import java.util.ArrayList; 58 import java.util.HashMap; 59 import java.util.HashSet; 60 import java.util.List; 61 import java.util.Map; 62 import java.util.Objects; 63 64 /** 65 * A class that exports methods that plugin code in the isolated process 66 * can use to request data from the managing service. 67 */ 68 public class DataAccessServiceImpl extends IDataAccessService.Stub { 69 private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger(); 70 private static final String TAG = "DataAccessServiceImpl"; 71 72 @NonNull 73 private final Context mApplicationContext; 74 @NonNull 75 private final ComponentName mService; 76 @Nullable 77 private OnDevicePersonalizationVendorDataDao mVendorDataDao = null; 78 @Nullable 79 private final OnDevicePersonalizationLocalDataDao mLocalDataDao; 80 @Nullable 81 private final EventsDao mEventsDao; 82 private final DataAccessPermission mLocalDataPermission; 83 private final DataAccessPermission mEventDataPermission; 84 @NonNull 85 private final Injector mInjector; 86 private Map<String, byte[]> mRemoteData = null; 87 DataAccessServiceImpl( @onNull ComponentName service, @NonNull Context applicationContext, @NonNull DataAccessPermission localDataPermission, @NonNull DataAccessPermission eventDataPermission)88 public DataAccessServiceImpl( 89 @NonNull ComponentName service, 90 @NonNull Context applicationContext, 91 @NonNull DataAccessPermission localDataPermission, 92 @NonNull DataAccessPermission eventDataPermission) { 93 this(service, applicationContext, null, localDataPermission, eventDataPermission, 94 new Injector()); 95 } 96 DataAccessServiceImpl( @onNull ComponentName service, @NonNull Context applicationContext, @NonNull Map<String, byte[]> remoteData, @NonNull DataAccessPermission localDataPermission, @NonNull DataAccessPermission eventDataPermission)97 public DataAccessServiceImpl( 98 @NonNull ComponentName service, 99 @NonNull Context applicationContext, 100 @NonNull Map<String, byte[]> remoteData, 101 @NonNull DataAccessPermission localDataPermission, 102 @NonNull DataAccessPermission eventDataPermission) { 103 this(service, applicationContext, remoteData, localDataPermission, eventDataPermission, 104 new Injector()); 105 } 106 107 @VisibleForTesting DataAccessServiceImpl( @onNull ComponentName service, @NonNull Context applicationContext, Map<String, byte[]> remoteData, @NonNull DataAccessPermission localDataPermission, @NonNull DataAccessPermission eventDataPermission, @NonNull Injector injector)108 DataAccessServiceImpl( 109 @NonNull ComponentName service, 110 @NonNull Context applicationContext, 111 Map<String, byte[]> remoteData, 112 @NonNull DataAccessPermission localDataPermission, 113 @NonNull DataAccessPermission eventDataPermission, 114 @NonNull Injector injector) { 115 mApplicationContext = Objects.requireNonNull(applicationContext, "applicationContext"); 116 mService = Objects.requireNonNull(service, "servicePackageName"); 117 mInjector = Objects.requireNonNull(injector, "injector"); 118 try { 119 if (remoteData != null) { 120 // Use provided remoteData instead of vendorData 121 mRemoteData = new HashMap<>(remoteData); 122 } else { 123 mVendorDataDao = mInjector.getVendorDataDao( 124 mApplicationContext, mService, 125 PackageUtils.getCertDigest( 126 mApplicationContext, mService.getPackageName())); 127 } 128 mLocalDataPermission = localDataPermission; 129 if (mLocalDataPermission != DataAccessPermission.DENIED) { 130 mLocalDataDao = mInjector.getLocalDataDao( 131 mApplicationContext, mService, 132 PackageUtils.getCertDigest( 133 mApplicationContext, mService.getPackageName())); 134 mLocalDataDao.createTable(); 135 } else { 136 mLocalDataDao = null; 137 } 138 } catch (PackageManager.NameNotFoundException nnfe) { 139 throw new IllegalArgumentException("Service: " + mService.toString() 140 + " does not exist.", nnfe); 141 } 142 mEventDataPermission = eventDataPermission; 143 if (mEventDataPermission != DataAccessPermission.DENIED) { 144 mEventsDao = mInjector.getEventsDao(mApplicationContext); 145 } else { 146 mEventsDao = null; 147 } 148 } 149 150 /** Handle a request from the isolated process. */ 151 @Override onRequest( int operation, @NonNull Bundle params, @NonNull IDataAccessServiceCallback callback)152 public void onRequest( 153 int operation, @NonNull Bundle params, @NonNull IDataAccessServiceCallback callback) { 154 sLogger.d(TAG + ": onRequest: op=" + operation + " params: " + params.toString()); 155 switch (operation) { 156 case Constants.DATA_ACCESS_OP_REMOTE_DATA_LOOKUP: 157 String lookupKey = params.getString(Constants.EXTRA_LOOKUP_KEYS); 158 if (lookupKey == null || lookupKey.isEmpty()) { 159 sLogger.w(TAG + "Missing lookup key."); 160 sendError(callback, Constants.STATUS_KEY_NOT_FOUND); 161 break; 162 } 163 mInjector.getExecutor().execute( 164 () -> remoteDataLookup( 165 lookupKey, callback)); 166 break; 167 case Constants.DATA_ACCESS_OP_REMOTE_DATA_KEYSET: 168 mInjector.getExecutor().execute( 169 () -> remoteDataKeyset(callback)); 170 break; 171 case Constants.DATA_ACCESS_OP_LOCAL_DATA_LOOKUP: 172 if (mLocalDataPermission == DataAccessPermission.DENIED) { 173 sLogger.w(TAG + "LocalData is not included for this instance."); 174 sendError(callback, Constants.STATUS_PERMISSION_DENIED); 175 break; 176 } 177 lookupKey = params.getString(Constants.EXTRA_LOOKUP_KEYS); 178 if (lookupKey == null || lookupKey.isEmpty()) { 179 sLogger.w(TAG + "Missing lookup key."); 180 sendError(callback, Constants.STATUS_KEY_NOT_FOUND); 181 break; 182 } 183 mInjector.getExecutor().execute( 184 () -> localDataLookup( 185 lookupKey, callback)); 186 break; 187 case Constants.DATA_ACCESS_OP_LOCAL_DATA_KEYSET: 188 if (mLocalDataPermission == DataAccessPermission.DENIED) { 189 sLogger.w(TAG + "LocalData is not included for this instance."); 190 sendError(callback, Constants.STATUS_PERMISSION_DENIED); 191 break; 192 } 193 mInjector.getExecutor().execute( 194 () -> localDataKeyset(callback)); 195 break; 196 case Constants.DATA_ACCESS_OP_LOCAL_DATA_PUT: 197 if (mLocalDataPermission == DataAccessPermission.DENIED) { 198 sLogger.w(TAG + "LocalData is not included for this instance."); 199 sendError(callback, Constants.STATUS_PERMISSION_DENIED); 200 break; 201 } 202 if (mLocalDataPermission == DataAccessPermission.READ_ONLY) { 203 sLogger.w(TAG + "LocalData is read-only for this instance."); 204 sendError(callback, Constants.STATUS_LOCAL_DATA_READ_ONLY); 205 break; 206 } 207 String putKey = params.getString(Constants.EXTRA_LOOKUP_KEYS); 208 ByteArrayParceledSlice parceledValue = params.getParcelable( 209 Constants.EXTRA_VALUE, ByteArrayParceledSlice.class); 210 if (parceledValue == null 211 || putKey == null || putKey.isEmpty()) { 212 throw new IllegalArgumentException("Invalid key or value for put."); 213 } 214 mInjector.getExecutor().execute( 215 () -> localDataPut(putKey, parceledValue, callback)); 216 break; 217 case Constants.DATA_ACCESS_OP_LOCAL_DATA_REMOVE: 218 if (mLocalDataPermission == DataAccessPermission.DENIED) { 219 sLogger.w(TAG + "LocalData is not included for this instance."); 220 sendError(callback, Constants.STATUS_PERMISSION_DENIED); 221 break; 222 } 223 if (mLocalDataPermission == DataAccessPermission.READ_ONLY) { 224 sLogger.w(TAG + "LocalData is read-only for this instance."); 225 sendError(callback, Constants.STATUS_LOCAL_DATA_READ_ONLY); 226 break; 227 } 228 String deleteKey = params.getString(Constants.EXTRA_LOOKUP_KEYS); 229 if (deleteKey == null || deleteKey.isEmpty()) { 230 sLogger.w(TAG + "Missing delete key."); 231 sendError(callback, Constants.STATUS_KEY_NOT_FOUND); 232 break; 233 } 234 mInjector.getExecutor().execute( 235 () -> localDataDelete(deleteKey, callback)); 236 break; 237 case Constants.DATA_ACCESS_OP_GET_EVENT_URL: 238 PersistableBundle eventParams = Objects.requireNonNull(params.getParcelable( 239 Constants.EXTRA_EVENT_PARAMS, PersistableBundle.class)); 240 byte[] responseData = params.getByteArray(Constants.EXTRA_RESPONSE_DATA); 241 String mimeType = params.getString(Constants.EXTRA_MIME_TYPE); 242 String destinationUrl = params.getString(Constants.EXTRA_DESTINATION_URL); 243 mInjector.getExecutor().execute( 244 () -> getEventUrl( 245 eventParams, responseData, mimeType, destinationUrl, callback) 246 ); 247 break; 248 case Constants.DATA_ACCESS_OP_GET_REQUESTS: 249 if (mEventDataPermission == DataAccessPermission.DENIED) { 250 sLogger.w(TAG + "Request and event data are not included for this instance."); 251 sendError(callback, Constants.STATUS_PERMISSION_DENIED); 252 break; 253 } 254 long[] requestTimes = Objects.requireNonNull(params.getLongArray( 255 Constants.EXTRA_LOOKUP_KEYS)); 256 if (requestTimes.length != 2) { 257 sLogger.w(TAG + "Invalid request timestamps provided."); 258 sendError(callback, Constants.STATUS_REQUEST_TIMESTAMPS_INVALID); 259 break; 260 } 261 mInjector.getExecutor().execute( 262 () -> getRequests(requestTimes[0], requestTimes[1], callback)); 263 break; 264 case Constants.DATA_ACCESS_OP_GET_JOINED_EVENTS: 265 if (mEventDataPermission == DataAccessPermission.DENIED) { 266 sLogger.w(TAG + "Request and event data are not included for this instance."); 267 sendError(callback, Constants.STATUS_PERMISSION_DENIED); 268 break; 269 } 270 long[] eventTimes = Objects.requireNonNull(params.getLongArray( 271 Constants.EXTRA_LOOKUP_KEYS)); 272 if (eventTimes.length != 2) { 273 sLogger.w(TAG + "Invalid request timestamps provided."); 274 sendError(callback, Constants.STATUS_REQUEST_TIMESTAMPS_INVALID); 275 break; 276 } 277 mInjector.getExecutor().execute( 278 () -> getJoinedEvents(eventTimes[0], eventTimes[1], callback)); 279 break; 280 case Constants.DATA_ACCESS_OP_GET_MODEL: 281 ModelId modelId = params.getParcelable(Constants.EXTRA_MODEL_ID, ModelId.class); 282 if (modelId == null) { 283 sLogger.w(TAG + "Model Id is not provided."); 284 sendError(callback, Constants.STATUS_KEY_NOT_FOUND); 285 break; 286 } 287 mInjector.getExecutor().execute(() -> getModelFileDescriptor(modelId, callback)); 288 break; 289 default: 290 sendError(callback, Constants.STATUS_DATA_ACCESS_UNSUPPORTED_OP); 291 } 292 } 293 294 @Override logApiCallStats( int apiName, long latencyMillis, int responseCode)295 public void logApiCallStats( 296 int apiName, long latencyMillis, int responseCode) { 297 mInjector.getExecutor().execute( 298 () -> handleLogApiCallStats(apiName, latencyMillis, responseCode)); 299 } 300 handleLogApiCallStats( int apiName, long latencyMillis, int responseCode)301 private void handleLogApiCallStats( 302 int apiName, long latencyMillis, int responseCode) { 303 try { 304 mInjector 305 .getOdpStatsdLogger() 306 .logApiCallStats( 307 new ApiCallStats.Builder(apiName) 308 .setResponseCode(responseCode) 309 .setLatencyMillis((int) latencyMillis) 310 .setSdkPackageName(mService.getPackageName()) 311 .build()); 312 } catch (Exception e) { 313 sLogger.e(e, TAG + ": error logging api call stats"); 314 } 315 } 316 remoteDataKeyset(@onNull IDataAccessServiceCallback callback)317 private void remoteDataKeyset(@NonNull IDataAccessServiceCallback callback) { 318 Bundle result = new Bundle(); 319 HashSet<String> keyset; 320 if (mRemoteData != null) { 321 keyset = new HashSet<>(mRemoteData.keySet()); 322 } else { 323 keyset = new HashSet<>(mVendorDataDao.readAllVendorDataKeys()); 324 } 325 result.putSerializable(Constants.EXTRA_RESULT, keyset); 326 sendResult(result, callback); 327 } 328 localDataKeyset(@onNull IDataAccessServiceCallback callback)329 private void localDataKeyset(@NonNull IDataAccessServiceCallback callback) { 330 Bundle result = new Bundle(); 331 result.putSerializable(Constants.EXTRA_RESULT, 332 new HashSet<>(mLocalDataDao.readAllLocalDataKeys())); 333 sendResult(result, callback); 334 } 335 remoteDataLookup(String key, @NonNull IDataAccessServiceCallback callback)336 private void remoteDataLookup(String key, @NonNull IDataAccessServiceCallback callback) { 337 try { 338 byte[] data; 339 if (mRemoteData != null) { 340 data = mRemoteData.get(key); 341 } else { 342 data = mVendorDataDao.readSingleVendorDataRow(key); 343 } 344 Bundle result = new Bundle(); 345 result.putParcelable( 346 Constants.EXTRA_RESULT, new ByteArrayParceledSlice(data)); 347 sendResult(result, callback); 348 } catch (Exception e) { 349 sendError(callback, Constants.STATUS_DATA_ACCESS_FAILURE); 350 } 351 } 352 localDataLookup(String key, @NonNull IDataAccessServiceCallback callback)353 private void localDataLookup(String key, @NonNull IDataAccessServiceCallback callback) { 354 try { 355 byte[] data = mLocalDataDao.readSingleLocalDataRow(key); 356 Bundle result = new Bundle(); 357 result.putParcelable( 358 Constants.EXTRA_RESULT, new ByteArrayParceledSlice(data)); 359 sendResult(result, callback); 360 } catch (Exception e) { 361 sendError(callback, Constants.STATUS_DATA_ACCESS_FAILURE); 362 } 363 } 364 localDataPut(String key, ByteArrayParceledSlice parceledData, @NonNull IDataAccessServiceCallback callback)365 private void localDataPut(String key, ByteArrayParceledSlice parceledData, 366 @NonNull IDataAccessServiceCallback callback) { 367 try { 368 byte[] data = parceledData.getByteArray(); 369 byte[] existingData = mLocalDataDao.readSingleLocalDataRow(key); 370 if (!mLocalDataDao.updateOrInsertLocalData( 371 new LocalData.Builder().setKey(key).setData(data).build())) { 372 sendError(callback, Constants.STATUS_LOCAL_WRITE_DATA_ACCESS_FAILURE); 373 } 374 Bundle result = new Bundle(); 375 result.putParcelable( 376 Constants.EXTRA_RESULT, new ByteArrayParceledSlice(existingData)); 377 sendResult(result, callback); 378 } catch (Exception e) { 379 sendError(callback, Constants.STATUS_DATA_ACCESS_FAILURE); 380 } 381 } 382 localDataDelete(String key, @NonNull IDataAccessServiceCallback callback)383 private void localDataDelete(String key, @NonNull IDataAccessServiceCallback callback) { 384 try { 385 byte[] existingData = mLocalDataDao.readSingleLocalDataRow(key); 386 mLocalDataDao.deleteLocalDataRow(key); 387 Bundle result = new Bundle(); 388 result.putParcelable( 389 Constants.EXTRA_RESULT, new ByteArrayParceledSlice(existingData)); 390 sendResult(result, callback); 391 } catch (Exception e) { 392 sendError(callback, Constants.STATUS_DATA_ACCESS_FAILURE); 393 } 394 } 395 getEventUrl( @onNull PersistableBundle eventParams, @Nullable byte[] responseData, @Nullable String mimeType, @Nullable String destinationUrl, @NonNull IDataAccessServiceCallback callback)396 private void getEventUrl( 397 @NonNull PersistableBundle eventParams, 398 @Nullable byte[] responseData, 399 @Nullable String mimeType, 400 @Nullable String destinationUrl, 401 @NonNull IDataAccessServiceCallback callback) { 402 try { 403 sLogger.d(TAG, ": getEventUrl() started."); 404 EventUrlPayload payload = new EventUrlPayload(eventParams, responseData, mimeType); 405 Uri eventUrl; 406 if (destinationUrl == null || destinationUrl.isEmpty()) { 407 eventUrl = EventUrlHelper.getEncryptedOdpEventUrl(payload); 408 } else { 409 eventUrl = EventUrlHelper.getEncryptedClickTrackingUrl( 410 payload, destinationUrl); 411 } 412 Bundle result = new Bundle(); 413 result.putParcelable(Constants.EXTRA_RESULT, eventUrl); 414 sLogger.d(TAG + ": getEventUrl() success. Url: " + eventUrl.toString()); 415 sendResult(result, callback); 416 } catch (Exception e) { 417 sLogger.d(TAG + ": getEventUrl() failed.", e); 418 sendError(callback, Constants.STATUS_DATA_ACCESS_FAILURE); 419 } 420 } 421 getRequests(long startTimeMillis, long endTimeMillis, @NonNull IDataAccessServiceCallback callback)422 private void getRequests(long startTimeMillis, long endTimeMillis, 423 @NonNull IDataAccessServiceCallback callback) { 424 try { 425 List<Query> queries = mEventsDao.readAllQueries( 426 startTimeMillis, endTimeMillis, mService); 427 List<RequestLogRecord> requestLogRecords = new ArrayList<>(); 428 for (Query query : queries) { 429 RequestLogRecord record = new RequestLogRecord.Builder() 430 .setRows(OnDevicePersonalizationFlatbufferUtils 431 .getContentValuesFromQueryData(query.getQueryData())) 432 .setRequestId(query.getQueryId()) 433 .setTimeMillis(query.getTimeMillis()) 434 .build(); 435 requestLogRecords.add(record); 436 } 437 Bundle result = new Bundle(); 438 result.putParcelable(Constants.EXTRA_RESULT, 439 new OdpParceledListSlice<>(requestLogRecords)); 440 sendResult(result, callback); 441 } catch (Exception e) { 442 sendError(callback, Constants.STATUS_DATA_ACCESS_FAILURE); 443 } 444 } 445 getJoinedEvents(long startTimeMillis, long endTimeMillis, @NonNull IDataAccessServiceCallback callback)446 private void getJoinedEvents(long startTimeMillis, long endTimeMillis, 447 @NonNull IDataAccessServiceCallback callback) { 448 try { 449 List<JoinedEvent> joinedEvents = mEventsDao.readJoinedTableRows( 450 startTimeMillis, endTimeMillis, mService); 451 List<EventLogRecord> joinedLogRecords = new ArrayList<>(); 452 for (JoinedEvent joinedEvent : joinedEvents) { 453 RequestLogRecord requestLogRecord = new RequestLogRecord.Builder() 454 .setRequestId(joinedEvent.getQueryId()) 455 .setRows(OnDevicePersonalizationFlatbufferUtils 456 .getContentValuesFromQueryData(joinedEvent.getQueryData())) 457 .setTimeMillis(joinedEvent.getQueryTimeMillis()) 458 .build(); 459 EventLogRecord record = new EventLogRecord.Builder() 460 .setTimeMillis(joinedEvent.getEventTimeMillis()) 461 .setType(joinedEvent.getType()) 462 .setData( 463 OnDevicePersonalizationFlatbufferUtils 464 .getContentValuesFromEventData(joinedEvent.getEventData())) 465 .setRequestLogRecord(requestLogRecord) 466 .build(); 467 joinedLogRecords.add(record); 468 } 469 Bundle result = new Bundle(); 470 result.putParcelable(Constants.EXTRA_RESULT, 471 new OdpParceledListSlice<>(joinedLogRecords)); 472 sendResult(result, callback); 473 } catch (Exception e) { 474 sendError(callback, Constants.STATUS_DATA_ACCESS_FAILURE); 475 } 476 } 477 getModelFileDescriptor( ModelId modelId, @NonNull IDataAccessServiceCallback callback)478 private void getModelFileDescriptor( 479 ModelId modelId, @NonNull IDataAccessServiceCallback callback) { 480 try { 481 byte[] modelData = null; 482 switch (modelId.getTableId()) { 483 case ModelId.TABLE_ID_REMOTE_DATA -> 484 modelData = mVendorDataDao.readSingleVendorDataRow(modelId.getKey()); 485 case ModelId.TABLE_ID_LOCAL_DATA -> 486 modelData = mLocalDataDao.readSingleLocalDataRow(modelId.getKey()); 487 default -> { 488 sLogger.e(TAG + "Unsupported model table Id %d", modelId.getTableId()); 489 sendError(callback, Constants.STATUS_MODEL_TABLE_ID_INVALID); 490 return; 491 } 492 } 493 494 if (modelData == null) { 495 sLogger.e(TAG + " Failed to find model data from database: " + modelId.getKey()); 496 sendError(callback, Constants.STATUS_MODEL_DB_LOOKUP_FAILED); 497 return; 498 } 499 String modelFile = 500 IoUtils.writeToTempFile( 501 modelId.getKey() + "_" + mInjector.getTimeMillis(), modelData); 502 ParcelFileDescriptor modelFd = 503 IoUtils.createFileDescriptor(modelFile, ParcelFileDescriptor.MODE_READ_ONLY); 504 505 Bundle result = new Bundle(); 506 result.putParcelable(Constants.EXTRA_RESULT, modelFd); 507 sendResult(result, callback); 508 } catch (Exception e) { 509 sLogger.e(e, TAG + " Failed to find model data: %s ", modelId.getKey()); 510 sendError(callback, Constants.STATUS_MODEL_LOOKUP_FAILURE); 511 } 512 } 513 sendResult( @onNull Bundle result, @NonNull IDataAccessServiceCallback callback)514 private static void sendResult( 515 @NonNull Bundle result, @NonNull IDataAccessServiceCallback callback) { 516 try { 517 callback.onSuccess(result); 518 } catch (RemoteException e) { 519 sLogger.e(TAG + ": Callback error", e); 520 } 521 } 522 sendError(@onNull IDataAccessServiceCallback callback, int errorCode)523 private static void sendError(@NonNull IDataAccessServiceCallback callback, int errorCode) { 524 try { 525 callback.onError(errorCode); 526 } catch (RemoteException e) { 527 sLogger.e(e, TAG + ": Callback error! Failed to set error code %d", errorCode); 528 } 529 } 530 531 @VisibleForTesting 532 static class Injector { getTimeMillis()533 long getTimeMillis() { 534 return System.currentTimeMillis(); 535 } 536 getExecutor()537 ListeningExecutorService getExecutor() { 538 return OnDevicePersonalizationExecutors.getBackgroundExecutor(); 539 } 540 getVendorDataDao( Context context, ComponentName service, String certDigest)541 OnDevicePersonalizationVendorDataDao getVendorDataDao( 542 Context context, ComponentName service, String certDigest) { 543 return OnDevicePersonalizationVendorDataDao.getInstance(context, 544 service, certDigest); 545 } 546 getLocalDataDao( Context context, ComponentName service, String certDigest)547 OnDevicePersonalizationLocalDataDao getLocalDataDao( 548 Context context, ComponentName service, String certDigest) { 549 return OnDevicePersonalizationLocalDataDao.getInstance(context, 550 service, certDigest); 551 } 552 getEventsDao(Context context)553 EventsDao getEventsDao(Context context) { 554 return EventsDao.getInstance(context); 555 } 556 getOdpStatsdLogger()557 OdpStatsdLogger getOdpStatsdLogger() { 558 return OdpStatsdLogger.getInstance(); 559 } 560 } 561 } 562