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