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.events;
18 
19 import android.annotation.NonNull;
20 import android.content.ComponentName;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.database.Cursor;
24 import android.database.sqlite.SQLiteDatabase;
25 import android.database.sqlite.SQLiteException;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
29 import com.android.ondevicepersonalization.services.data.DbUtils;
30 import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper;
31 
32 import java.util.ArrayList;
33 import java.util.List;
34 
35 /**
36  * Dao used to manage access to Events and Queries tables
37  */
38 public class EventsDao {
39     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
40     private static final String TAG = "EventsDao";
41     private static final String JOINED_EVENT_TIME_MILLIS = "eventTimeMillis";
42     private static final String JOINED_QUERY_TIME_MILLIS = "queryTimeMillis";
43 
44     private static volatile EventsDao sSingleton;
45 
46     private final OnDevicePersonalizationDbHelper mDbHelper;
47 
EventsDao(@onNull OnDevicePersonalizationDbHelper dbHelper)48     private EventsDao(@NonNull OnDevicePersonalizationDbHelper dbHelper) {
49         this.mDbHelper = dbHelper;
50     }
51 
52     /** Returns an instance of the EventsDao given a context. */
getInstance(@onNull Context context)53     public static EventsDao getInstance(@NonNull Context context) {
54         if (sSingleton == null) {
55             synchronized (EventsDao.class) {
56                 if (sSingleton == null) {
57                     OnDevicePersonalizationDbHelper dbHelper =
58                             OnDevicePersonalizationDbHelper.getInstance(context);
59                     sSingleton = new EventsDao(dbHelper);
60                 }
61             }
62         }
63         return sSingleton;
64     }
65 
66     /**
67      * Returns an instance of the EventsDao given a context. This is used
68      * for testing only.
69      */
70     @VisibleForTesting
getInstanceForTest(@onNull Context context)71     public static EventsDao getInstanceForTest(@NonNull Context context) {
72         synchronized (EventsDao.class) {
73             if (sSingleton == null) {
74                 OnDevicePersonalizationDbHelper dbHelper =
75                         OnDevicePersonalizationDbHelper.getInstanceForTest(context);
76                 sSingleton = new EventsDao(dbHelper);
77             }
78             return sSingleton;
79         }
80     }
81 
82     /**
83      * Inserts the Event into the Events table.
84      *
85      * @return The row id of the newly inserted row if successful, -1 otherwise
86      */
insertEvent(@onNull Event event)87     public long insertEvent(@NonNull Event event) {
88         try {
89             SQLiteDatabase db = mDbHelper.getWritableDatabase();
90             ContentValues values = new ContentValues();
91             values.put(EventsContract.EventsEntry.QUERY_ID, event.getQueryId());
92             values.put(EventsContract.EventsEntry.ROW_INDEX, event.getRowIndex());
93             values.put(EventsContract.EventsEntry.TIME_MILLIS, event.getTimeMillis());
94             values.put(EventsContract.EventsEntry.SERVICE_NAME,
95                     event.getServiceName());
96             values.put(EventsContract.EventsEntry.TYPE, event.getType());
97             values.put(EventsContract.EventsEntry.EVENT_DATA, event.getEventData());
98             return db.insert(EventsContract.EventsEntry.TABLE_NAME, null,
99                     values);
100         } catch (SQLiteException e) {
101             sLogger.e(TAG + ": Failed to insert event", e);
102         }
103         return -1;
104     }
105 
106 
107     /**
108      * Inserts the List of Events into the Events table.
109      *
110      * @return true if all inserts succeeded, false otherwise.
111      */
insertEvents(@onNull List<Event> events)112     public boolean insertEvents(@NonNull List<Event> events) {
113         SQLiteDatabase db = mDbHelper.getWritableDatabase();
114         try {
115             db.beginTransactionNonExclusive();
116             for (Event event : events) {
117                 if (insertEvent(event) == -1) {
118                     return false;
119                 }
120             }
121             db.setTransactionSuccessful();
122         } catch (Exception e) {
123             sLogger.e(TAG + ": Failed to insert events", e);
124             return false;
125         } finally {
126             db.endTransaction();
127         }
128         return true;
129     }
130 
131     /**
132      * Inserts the Query into the Queries table.
133      *
134      * @return The row id of the newly inserted row if successful, -1 otherwise
135      */
insertQuery(@onNull Query query)136     public long insertQuery(@NonNull Query query) {
137         try {
138             SQLiteDatabase db = mDbHelper.getWritableDatabase();
139             ContentValues values = new ContentValues();
140             values.put(QueriesContract.QueriesEntry.TIME_MILLIS, query.getTimeMillis());
141             values.put(QueriesContract.QueriesEntry.SERVICE_NAME,
142                     query.getServiceName());
143             values.put(QueriesContract.QueriesEntry.QUERY_DATA, query.getQueryData());
144             values.put(QueriesContract.QueriesEntry.APP_PACKAGE_NAME, query.getAppPackageName());
145             values.put(QueriesContract.QueriesEntry.SERVICE_CERT_DIGEST,
146                     query.getServiceCertDigest());
147             return db.insert(QueriesContract.QueriesEntry.TABLE_NAME, null,
148                     values);
149         } catch (SQLiteException e) {
150             sLogger.e(TAG + ": Failed to insert query", e);
151         }
152         return -1;
153     }
154 
155     /**
156      * Updates the eventState, adds it if it doesn't already exist.
157      *
158      * @return true if the update/insert succeeded, false otherwise
159      */
updateOrInsertEventState(EventState eventState)160     public boolean updateOrInsertEventState(EventState eventState) {
161         try {
162             SQLiteDatabase db = mDbHelper.getWritableDatabase();
163             ContentValues values = new ContentValues();
164             values.put(EventStateContract.EventStateEntry.TOKEN, eventState.getToken());
165             values.put(EventStateContract.EventStateEntry.SERVICE_NAME,
166                     eventState.getServiceName());
167             values.put(EventStateContract.EventStateEntry.TASK_IDENTIFIER,
168                     eventState.getTaskIdentifier());
169             return db.insertWithOnConflict(EventStateContract.EventStateEntry.TABLE_NAME,
170                     null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1;
171         } catch (SQLiteException e) {
172             sLogger.e(TAG + ": Failed to update or insert eventState", e);
173         }
174         return false;
175     }
176 
177     /**
178      * Updates/inserts a list of EventStates as a transaction
179      *
180      * @return true if the all the update/inserts succeeded, false otherwise
181      */
updateOrInsertEventStatesTransaction(List<EventState> eventStates)182     public boolean updateOrInsertEventStatesTransaction(List<EventState> eventStates) {
183         SQLiteDatabase db = mDbHelper.getWritableDatabase();
184         try {
185             db.beginTransactionNonExclusive();
186             for (EventState eventState : eventStates) {
187                 if (!updateOrInsertEventState(eventState)) {
188                     return false;
189                 }
190             }
191 
192             db.setTransactionSuccessful();
193         } catch (Exception e) {
194             sLogger.e(TAG + ": Failed to insert/update eventstates", e);
195             return false;
196         } finally {
197             db.endTransaction();
198         }
199         return true;
200     }
201 
202     /**
203      * Gets the eventState for the given package and task
204      *
205      * @return eventState if found, null otherwise
206      */
getEventState(String taskIdentifier, ComponentName service)207     public EventState getEventState(String taskIdentifier, ComponentName service) {
208         SQLiteDatabase db = mDbHelper.getReadableDatabase();
209         String selection = EventStateContract.EventStateEntry.TASK_IDENTIFIER + " = ? AND "
210                 + EventStateContract.EventStateEntry.SERVICE_NAME + " = ?";
211         String[] selectionArgs = {taskIdentifier, DbUtils.toTableValue(service)};
212         String[] projection = {EventStateContract.EventStateEntry.TOKEN};
213         try (Cursor cursor = db.query(
214                 EventStateContract.EventStateEntry.TABLE_NAME,
215                 projection,
216                 selection,
217                 selectionArgs,
218                 /* groupBy= */ null,
219                 /* having= */ null,
220                 /* orderBy= */ null
221         )) {
222             if (cursor.moveToFirst()) {
223                 byte[] token = cursor.getBlob(cursor.getColumnIndexOrThrow(
224                         EventStateContract.EventStateEntry.TOKEN));
225 
226                 return new EventState.Builder()
227                         .setToken(token)
228                         .setService(service)
229                         .setTaskIdentifier(taskIdentifier)
230                         .build();
231             }
232         } catch (SQLiteException e) {
233             sLogger.e(TAG + ": Failed to read eventState", e);
234         }
235         return null;
236     }
237 
238     /**
239      * Queries the events and queries table to return all new rows from given ids for the given
240      * package
241      *
242      * @param service            Name of the service to read rows for
243      * @param fromEventId        EventId to find all new rows from
244      * @param fromQueryId        QueryId to find all new rows from
245      * @return List of JoinedEvents.
246      */
readAllNewRowsForPackage(ComponentName service, long fromEventId, long fromQueryId)247     public List<JoinedEvent> readAllNewRowsForPackage(ComponentName service,
248             long fromEventId, long fromQueryId) {
249         String serviceName = DbUtils.toTableValue(service);
250         // Query on the joined query & event table
251         String joinedSelection = EventsContract.EventsEntry.EVENT_ID + " > ?"
252                 + " AND " + EventsContract.EventsEntry.TABLE_NAME + "."
253                 + EventsContract.EventsEntry.SERVICE_NAME + " = ?";
254         String[] joinedSelectionArgs = {String.valueOf(fromEventId), serviceName};
255         List<JoinedEvent> joinedEventList = readJoinedTableRows(joinedSelection,
256                 joinedSelectionArgs);
257 
258         // Query on the queries table
259         String queriesSelection = QueriesContract.QueriesEntry.QUERY_ID + " > ?"
260                 + " AND " + QueriesContract.QueriesEntry.SERVICE_NAME + " = ?";
261         String[] queriesSelectionArgs = {String.valueOf(fromQueryId), serviceName};
262         List<Query> queryList = readQueryRows(queriesSelection, queriesSelectionArgs);
263         for (Query query : queryList) {
264             joinedEventList.add(new JoinedEvent.Builder()
265                     .setQueryId(query.getQueryId())
266                     .setQueryData(query.getQueryData())
267                     .setQueryTimeMillis(query.getTimeMillis())
268                     .setService(query.getService())
269                     .build());
270         }
271         return joinedEventList;
272     }
273 
274     /**
275      * Queries the events and queries table to return all new rows from given ids for all packages
276      *
277      * @param fromEventId EventId to find all new rows from
278      * @param fromQueryId QueryId to find all new rows from
279      * @return List of JoinedEvents.
280      */
readAllNewRows(long fromEventId, long fromQueryId)281     public List<JoinedEvent> readAllNewRows(long fromEventId, long fromQueryId) {
282         // Query on the joined query & event table
283         String joinedSelection = EventsContract.EventsEntry.EVENT_ID + " > ?";
284         String[] joinedSelectionArgs = {String.valueOf(fromEventId)};
285         List<JoinedEvent> joinedEventList = readJoinedTableRows(joinedSelection,
286                 joinedSelectionArgs);
287 
288         // Query on the queries table
289         String queriesSelection = QueriesContract.QueriesEntry.QUERY_ID + " > ?";
290         String[] queriesSelectionArgs = {String.valueOf(fromQueryId)};
291         List<Query> queryList = readQueryRows(queriesSelection, queriesSelectionArgs);
292         for (Query query : queryList) {
293             joinedEventList.add(new JoinedEvent.Builder()
294                     .setQueryId(query.getQueryId())
295                     .setQueryData(query.getQueryData())
296                     .setQueryTimeMillis(query.getTimeMillis())
297                     .setService(query.getService())
298                     .build());
299         }
300         return joinedEventList;
301     }
302 
readQueryRows(String selection, String[] selectionArgs)303     private List<Query> readQueryRows(String selection, String[] selectionArgs) {
304         List<Query> queries = new ArrayList<>();
305         SQLiteDatabase db = mDbHelper.getReadableDatabase();
306         String orderBy = QueriesContract.QueriesEntry.QUERY_ID;
307         try (Cursor cursor = db.query(
308                 QueriesContract.QueriesEntry.TABLE_NAME,
309                 /* projection= */ null,
310                 selection,
311                 selectionArgs,
312                 /* groupBy= */ null,
313                 /* having= */ null,
314                 orderBy
315         )) {
316             while (cursor.moveToNext()) {
317                 long queryId = cursor.getLong(
318                         cursor.getColumnIndexOrThrow(QueriesContract.QueriesEntry.QUERY_ID));
319                 byte[] queryData = cursor.getBlob(
320                         cursor.getColumnIndexOrThrow(QueriesContract.QueriesEntry.QUERY_DATA));
321                 long timeMillis = cursor.getLong(
322                         cursor.getColumnIndexOrThrow(QueriesContract.QueriesEntry.TIME_MILLIS));
323                 String serviceName = cursor.getString(
324                         cursor.getColumnIndexOrThrow(
325                                 QueriesContract.QueriesEntry.SERVICE_NAME));
326                 String appPackageName = cursor.getString(cursor.getColumnIndexOrThrow(
327                         QueriesContract.QueriesEntry.APP_PACKAGE_NAME));
328                 String certDigest = cursor.getString(cursor.getColumnIndexOrThrow(
329                         QueriesContract.QueriesEntry.SERVICE_CERT_DIGEST));
330                 queries.add(new Query.Builder(
331                         timeMillis, appPackageName, DbUtils.fromTableValue(serviceName),
332                                 certDigest, queryData)
333                         .setQueryId(queryId)
334                         .build());
335             }
336         } catch (IllegalArgumentException e) {
337             sLogger.e(e, TAG + ": Failed parse resulting query");
338             return new ArrayList<>();
339         }
340         return queries;
341     }
342 
readJoinedTableRows(String selection, String[] selectionArgs)343     private List<JoinedEvent> readJoinedTableRows(String selection, String[] selectionArgs) {
344         List<JoinedEvent> joinedEventList = new ArrayList<>();
345 
346         SQLiteDatabase db = mDbHelper.getReadableDatabase();
347         String select = "SELECT "
348                 + EventsContract.EventsEntry.EVENT_ID + ","
349                 + EventsContract.EventsEntry.ROW_INDEX + ","
350                 + EventsContract.EventsEntry.TYPE + ","
351                 + EventsContract.EventsEntry.TABLE_NAME + "."
352                 + EventsContract.EventsEntry.SERVICE_NAME + ","
353                 + EventsContract.EventsEntry.EVENT_DATA + ","
354                 + EventsContract.EventsEntry.TABLE_NAME + "."
355                 + EventsContract.EventsEntry.TIME_MILLIS + " AS " + JOINED_EVENT_TIME_MILLIS + ","
356                 + EventsContract.EventsEntry.TABLE_NAME + "."
357                 + EventsContract.EventsEntry.QUERY_ID + ","
358                 + QueriesContract.QueriesEntry.QUERY_DATA + ","
359                 + QueriesContract.QueriesEntry.TABLE_NAME + "."
360                 + QueriesContract.QueriesEntry.TIME_MILLIS + " AS " + JOINED_QUERY_TIME_MILLIS;
361         String from = " FROM " + EventsContract.EventsEntry.TABLE_NAME
362                 + " INNER JOIN " + QueriesContract.QueriesEntry.TABLE_NAME
363                 + " ON "
364                 + QueriesContract.QueriesEntry.TABLE_NAME + "."
365                 + QueriesContract.QueriesEntry.QUERY_ID + " = "
366                 + EventsContract.EventsEntry.TABLE_NAME + "." + EventsContract.EventsEntry.QUERY_ID;
367         String where = " WHERE " + selection;
368         String orderBy = " ORDER BY " + EventsContract.EventsEntry.EVENT_ID;
369         String query = select + from + where + orderBy;
370         try (Cursor cursor = db.rawQuery(query, selectionArgs)) {
371             while (cursor.moveToNext()) {
372                 long eventId = cursor.getLong(
373                         cursor.getColumnIndexOrThrow(EventsContract.EventsEntry.EVENT_ID));
374                 int rowIndex = cursor.getInt(
375                         cursor.getColumnIndexOrThrow(EventsContract.EventsEntry.ROW_INDEX));
376                 int type = cursor.getInt(
377                         cursor.getColumnIndexOrThrow(EventsContract.EventsEntry.TYPE));
378                 String serviceName = cursor.getString(
379                         cursor.getColumnIndexOrThrow(
380                                 EventsContract.EventsEntry.SERVICE_NAME));
381                 byte[] eventData = cursor.getBlob(
382                         cursor.getColumnIndexOrThrow(EventsContract.EventsEntry.EVENT_DATA));
383                 long eventTimeMillis = cursor.getLong(
384                         cursor.getColumnIndexOrThrow(JOINED_EVENT_TIME_MILLIS));
385                 long queryId = cursor.getLong(
386                         cursor.getColumnIndexOrThrow(QueriesContract.QueriesEntry.QUERY_ID));
387                 byte[] queryData = cursor.getBlob(
388                         cursor.getColumnIndexOrThrow(QueriesContract.QueriesEntry.QUERY_DATA));
389                 long queryTimeMillis = cursor.getLong(
390                         cursor.getColumnIndexOrThrow(JOINED_QUERY_TIME_MILLIS));
391                 joinedEventList.add(new JoinedEvent.Builder()
392                         .setEventId(eventId)
393                         .setRowIndex(rowIndex)
394                         .setType(type)
395                         .setEventData(eventData)
396                         .setEventTimeMillis(eventTimeMillis)
397                         .setQueryId(queryId)
398                         .setQueryData(queryData)
399                         .setQueryTimeMillis(queryTimeMillis)
400                         .setService(DbUtils.fromTableValue(serviceName))
401                         .build()
402                 );
403             }
404         } catch (IllegalArgumentException e) {
405             sLogger.e(e, TAG + ": Failed parse resulting query of join statement");
406             return new ArrayList<>();
407         }
408         return joinedEventList;
409     }
410 
411     /**
412      * Deletes all eventStates for the given packageName
413      *
414      * @return true if the delete executed successfully, false otherwise.
415      */
deleteEventState(ComponentName service)416     public boolean deleteEventState(ComponentName service) {
417         SQLiteDatabase db = mDbHelper.getWritableDatabase();
418         try {
419             String selection = EventStateContract.EventStateEntry.SERVICE_NAME + " = ?";
420             String[] selectionArgs = {DbUtils.toTableValue(service)};
421             db.delete(EventStateContract.EventStateEntry.TABLE_NAME, selection,
422                     selectionArgs);
423         } catch (Exception e) {
424             sLogger.e(e, TAG + ": Failed to delete eventState for: " + service.toString());
425             return false;
426         }
427         return true;
428     }
429 
430     /**
431      * Deletes all events and queries older than the given timestamp
432      *
433      * @return true if the delete executed successfully, false otherwise.
434      */
deleteEventsAndQueries(long timestamp)435     public boolean deleteEventsAndQueries(long timestamp) {
436         SQLiteDatabase db = mDbHelper.getWritableDatabase();
437         try {
438             db.beginTransactionNonExclusive();
439             // Delete from events table first to satisfy FK requirements.
440             String eventsSelection = EventsContract.EventsEntry.TIME_MILLIS + " < ?";
441             String[] eventsSelectionArgs = {String.valueOf(timestamp)};
442             db.delete(EventsContract.EventsEntry.TABLE_NAME, eventsSelection,
443                     eventsSelectionArgs);
444 
445             // Delete from queries table older than timestamp AND have no events left.
446             String queriesSelection = QueriesContract.QueriesEntry.TIME_MILLIS + " < ?"
447                     + " AND " + QueriesContract.QueriesEntry.QUERY_ID
448                     + " NOT IN (SELECT " + EventsContract.EventsEntry.QUERY_ID
449                     + " FROM " + EventsContract.EventsEntry.TABLE_NAME + ")";
450             String[] queriesSelectionArgs = {String.valueOf(timestamp)};
451             db.delete(QueriesContract.QueriesEntry.TABLE_NAME, queriesSelection,
452                     queriesSelectionArgs);
453 
454             db.setTransactionSuccessful();
455         } catch (Exception e) {
456             sLogger.e(e, TAG + ": Failed to delete events and queries older than: " + timestamp);
457             return false;
458         } finally {
459             db.endTransaction();
460         }
461         return true;
462     }
463 
464     /**
465      * Reads all queries in the query table between the given timestamps.
466      *
467      * @return List of Query in the query table.
468      */
readAllQueries(long startTimeMillis, long endTimeMillis, ComponentName service)469     public List<Query> readAllQueries(long startTimeMillis, long endTimeMillis,
470             ComponentName service) {
471         String selection = QueriesContract.QueriesEntry.TIME_MILLIS + " > ?"
472                 + " AND " + QueriesContract.QueriesEntry.TIME_MILLIS + " < ?"
473                 + " AND " + QueriesContract.QueriesEntry.SERVICE_NAME + " = ?";
474         String[] selectionArgs = {String.valueOf(startTimeMillis), String.valueOf(
475                 endTimeMillis), DbUtils.toTableValue(service)};
476         return readQueryRows(selection, selectionArgs);
477     }
478 
479     /**
480      * Reads all ids in the event table between the given timestamps.
481      *
482      * @return List of ids in the event table.
483      */
readAllEventIds(long startTimeMillis, long endTimeMillis, ComponentName service)484     public List<Long> readAllEventIds(long startTimeMillis, long endTimeMillis,
485             ComponentName service) {
486         List<Long> idList = new ArrayList<>();
487         try {
488             SQLiteDatabase db = mDbHelper.getReadableDatabase();
489             String[] projection = {EventsContract.EventsEntry.EVENT_ID};
490             String selection = EventsContract.EventsEntry.TIME_MILLIS + " > ?"
491                     + " AND " + EventsContract.EventsEntry.TIME_MILLIS + " < ?"
492                     + " AND " + EventsContract.EventsEntry.SERVICE_NAME + " = ?";
493             String[] selectionArgs = {String.valueOf(startTimeMillis), String.valueOf(
494                     endTimeMillis), DbUtils.toTableValue(service)};
495             String orderBy = EventsContract.EventsEntry.EVENT_ID;
496             try (Cursor cursor = db.query(
497                     EventsContract.EventsEntry.TABLE_NAME,
498                     projection,
499                     selection,
500                     selectionArgs,
501                     /* groupBy= */ null,
502                     /* having= */ null,
503                     orderBy
504             )) {
505                 while (cursor.moveToNext()) {
506                     Long id = cursor.getLong(
507                             cursor.getColumnIndexOrThrow(EventsContract.EventsEntry.EVENT_ID));
508                     idList.add(id);
509                 }
510                 cursor.close();
511                 return idList;
512             }
513         } catch (SQLiteException e) {
514             sLogger.e(TAG + ": Failed to read event ids", e);
515         }
516         return idList;
517     }
518 
519     /**
520      * Returns whether an event with (queryId, type, rowIndex, service) exists.
521      */
hasEvent(long queryId, int type, int rowIndex, ComponentName service)522     public boolean hasEvent(long queryId, int type, int rowIndex, ComponentName service) {
523         try {
524             int count = 0;
525             SQLiteDatabase db = mDbHelper.getReadableDatabase();
526             String[] projection = {EventsContract.EventsEntry.EVENT_ID};
527             String selection = EventsContract.EventsEntry.QUERY_ID + " = ?"
528                     + " AND " + EventsContract.EventsEntry.TYPE + " = ?"
529                     + " AND " + EventsContract.EventsEntry.ROW_INDEX + " = ?"
530                     + " AND " + EventsContract.EventsEntry.SERVICE_NAME + " = ?";
531             String[] selectionArgs = {
532                     String.valueOf(queryId),
533                     String.valueOf(type),
534                     String.valueOf(rowIndex),
535                     DbUtils.toTableValue(service)
536             };
537             try (Cursor cursor = db.query(
538                     EventsContract.EventsEntry.TABLE_NAME,
539                     projection,
540                     selection,
541                     selectionArgs,
542                     /* groupBy= */ null,
543                     /* having= */ null,
544                     null
545             )) {
546                 if (cursor.moveToNext()) {
547                     return true;
548                 }
549             }
550         } catch (SQLiteException e) {
551             sLogger.e(TAG + ": Failed to read event ids for specified queryid", e);
552         }
553         return false;
554     }
555 
556     /**
557      * Reads all ids in the event table associated with the specified queryId
558      *
559      * @return List of ids in the event table.
560      */
readAllEventIdsForQuery(long queryId, ComponentName service)561     public List<Long> readAllEventIdsForQuery(long queryId, ComponentName service) {
562         List<Long> idList = new ArrayList<>();
563         try {
564             SQLiteDatabase db = mDbHelper.getReadableDatabase();
565             String[] projection = {EventsContract.EventsEntry.EVENT_ID};
566             String selection = EventsContract.EventsEntry.QUERY_ID + " = ?"
567                     + " AND " + EventsContract.EventsEntry.SERVICE_NAME + " = ?";
568             String[] selectionArgs = {String.valueOf(queryId), DbUtils.toTableValue(service)};
569             String orderBy = EventsContract.EventsEntry.EVENT_ID;
570             try (Cursor cursor = db.query(
571                     EventsContract.EventsEntry.TABLE_NAME,
572                     projection,
573                     selection,
574                     selectionArgs,
575                     /* groupBy= */ null,
576                     /* having= */ null,
577                     orderBy
578             )) {
579                 while (cursor.moveToNext()) {
580                     Long id = cursor.getLong(
581                             cursor.getColumnIndexOrThrow(EventsContract.EventsEntry.EVENT_ID));
582                     idList.add(id);
583                 }
584                 cursor.close();
585                 return idList;
586             }
587         } catch (SQLiteException e) {
588             sLogger.e(TAG + ": Failed to read event ids for specified queryid", e);
589         }
590         return idList;
591     }
592 
593     /**
594      * Reads single row in the query table
595      *
596      * @return Query object for the single row requested
597      */
readSingleQueryRow(long queryId, ComponentName service)598     public Query readSingleQueryRow(long queryId, ComponentName service) {
599         try {
600             SQLiteDatabase db = mDbHelper.getReadableDatabase();
601             String selection = QueriesContract.QueriesEntry.QUERY_ID + " = ?"
602                     + " AND " + QueriesContract.QueriesEntry.SERVICE_NAME + " = ?";
603             String[] selectionArgs = {String.valueOf(queryId), DbUtils.toTableValue(service)};
604             try (Cursor cursor = db.query(
605                     QueriesContract.QueriesEntry.TABLE_NAME,
606                     /* projection= */ null,
607                     selection,
608                     selectionArgs,
609                     /* groupBy= */ null,
610                     /* having= */ null,
611                     /* orderBy= */ null
612             )) {
613                 if (cursor.getCount() < 1) {
614                     sLogger.d(TAG + ": Failed to find requested id: " + queryId);
615                     return null;
616                 }
617                 cursor.moveToNext();
618                 long id = cursor.getLong(
619                         cursor.getColumnIndexOrThrow(QueriesContract.QueriesEntry.QUERY_ID));
620                 byte[] queryData = cursor.getBlob(
621                         cursor.getColumnIndexOrThrow(QueriesContract.QueriesEntry.QUERY_DATA));
622                 long timeMillis = cursor.getLong(
623                         cursor.getColumnIndexOrThrow(QueriesContract.QueriesEntry.TIME_MILLIS));
624                 String appPackageName = cursor.getString(cursor.getColumnIndexOrThrow(
625                         QueriesContract.QueriesEntry.APP_PACKAGE_NAME));
626                 String certDigest = cursor.getString(cursor.getColumnIndexOrThrow(
627                         QueriesContract.QueriesEntry.SERVICE_CERT_DIGEST));
628                 String serviceName = cursor.getString(
629                         cursor.getColumnIndexOrThrow(
630                                 QueriesContract.QueriesEntry.SERVICE_NAME));
631                 return new Query.Builder(
632                         timeMillis, appPackageName, service, certDigest, queryData)
633                         .setQueryId(id)
634                         .build();
635             }
636         } catch (SQLiteException e) {
637             sLogger.e(TAG + ": Failed to read query row", e);
638         }
639         return null;
640     }
641 
642     /**
643      * Reads single row in the event table joined with its corresponding query
644      *
645      * @return JoinedEvent representing the event joined with its query
646      */
readSingleJoinedTableRow(long eventId, ComponentName service)647     public JoinedEvent readSingleJoinedTableRow(long eventId, ComponentName service) {
648         String selection = EventsContract.EventsEntry.EVENT_ID + " = ?"
649                 + " AND " + EventsContract.EventsEntry.TABLE_NAME + "."
650                 + EventsContract.EventsEntry.SERVICE_NAME + " = ?";
651         String[] selectionArgs = {String.valueOf(eventId), DbUtils.toTableValue(service)};
652         List<JoinedEvent> joinedEventList = readJoinedTableRows(selection, selectionArgs);
653         if (joinedEventList.size() < 1) {
654             sLogger.d(TAG + ": Failed to find requested id: " + eventId);
655             return null;
656         }
657         return joinedEventList.get(0);
658     }
659 
660     /**
661      * Reads all row in the event table joined with its corresponding query within the given time
662      * range.
663      *
664      * @return List of JoinedEvents representing the event joined with its query
665      */
readJoinedTableRows(long startTimeMillis, long endTimeMillis, ComponentName service)666     public List<JoinedEvent> readJoinedTableRows(long startTimeMillis, long endTimeMillis,
667             ComponentName service) {
668         String selection = JOINED_EVENT_TIME_MILLIS + " > ?"
669                 + " AND " + JOINED_EVENT_TIME_MILLIS + " < ?"
670                 + " AND " + EventsContract.EventsEntry.TABLE_NAME + "."
671                 + EventsContract.EventsEntry.SERVICE_NAME + " = ?";
672         String[] selectionArgs = {String.valueOf(startTimeMillis), String.valueOf(
673                 endTimeMillis), DbUtils.toTableValue(service)};
674         return readJoinedTableRows(selection, selectionArgs);
675     }
676 }
677