• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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