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