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