• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 package com.android.providers.telephony;
17 
18 import static android.provider.Telephony.RcsColumns.CONTENT_AND_AUTHORITY;
19 import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.RCS_1_TO_1_THREAD_URI_PART;
20 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_TYPE_COLUMN;
21 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_URI_COLUMN;
22 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_SIZE_COLUMN;
23 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_TRANSFER_ID_COLUMN;
24 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.HEIGHT_COLUMN;
25 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.DURATION_MILLIS_COLUMN;
26 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_TYPE_COLUMN;
27 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_URI_COLUMN;
28 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SESSION_ID_COLUMN;
29 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SUCCESSFULLY_TRANSFERRED_BYTES;
30 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.TRANSFER_STATUS_COLUMN;
31 import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.WIDTH_COLUMN;
32 import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.RCS_GROUP_THREAD_URI_PART;
33 import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.ARRIVAL_TIMESTAMP_COLUMN;
34 import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.INCOMING_MESSAGE_URI_PART;
35 import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.SENDER_PARTICIPANT_ID_COLUMN;
36 import static android.provider.Telephony.RcsColumns.RcsMessageColumns.GLOBAL_ID_COLUMN;
37 import static android.provider.Telephony.RcsColumns.RcsMessageColumns.LATITUDE_COLUMN;
38 import static android.provider.Telephony.RcsColumns.RcsMessageColumns.LONGITUDE_COLUMN;
39 import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_ID_COLUMN;
40 import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TEXT_COLUMN;
41 import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
42 import static android.provider.Telephony.RcsColumns.RcsMessageColumns.STATUS_COLUMN;
43 import static android.provider.Telephony.RcsColumns.RcsMessageColumns.SUB_ID_COLUMN;
44 import static android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns.DELIVERED_TIMESTAMP_COLUMN;
45 import static android.provider.Telephony.RcsColumns.RcsOutgoingMessageColumns.OUTGOING_MESSAGE_URI_PART;
46 import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
47 import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
48 import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_COLUMN;
49 import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_INCOMING;
50 import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_OUTGOING;
51 import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.UNIFIED_INCOMING_MESSAGE_VIEW;
52 import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.UNIFIED_OUTGOING_MESSAGE_VIEW;
53 import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
54 import static android.telephony.ims.RcsMessageQueryParams.MESSAGE_QUERY_PARAMETERS_KEY;
55 import static android.telephony.ims.RcsMessageQueryParams.THREAD_ID_NOT_SET;
56 
57 import static android.telephony.ims.RcsQueryContinuationToken.MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE;
58 import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
59 import static com.android.providers.telephony.RcsProvider.RCS_FILE_TRANSFER_TABLE;
60 import static com.android.providers.telephony.RcsProvider.RCS_INCOMING_MESSAGE_TABLE;
61 import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_DELIVERY_TABLE;
62 import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_TABLE;
63 import static com.android.providers.telephony.RcsProvider.RCS_OUTGOING_MESSAGE_TABLE;
64 import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_TABLE;
65 import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_THREAD_JUNCTION_TABLE;
66 import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
67 import static com.android.providers.telephony.RcsProvider.TAG;
68 import static com.android.providers.telephony.RcsProvider.UNIFIED_MESSAGE_VIEW;
69 import static com.android.providers.telephony.RcsProviderThreadHelper.getThreadIdFromUri;
70 import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
71 
72 import android.content.ContentValues;
73 import android.database.Cursor;
74 import android.database.sqlite.SQLiteDatabase;
75 import android.database.sqlite.SQLiteOpenHelper;
76 import android.net.Uri;
77 import android.os.Bundle;
78 import android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns;
79 import android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns;
80 import android.telephony.ims.RcsMessageQueryParams;
81 import android.telephony.ims.RcsQueryContinuationToken;
82 import android.text.TextUtils;
83 import android.util.Log;
84 
85 import com.android.internal.annotations.VisibleForTesting;
86 
87 /**
88  * Constants and helpers related to messages for {@link RcsProvider} to keep the code clean.
89  *
90  * @hide
91  */
92 public class RcsProviderMessageHelper {
93     private static final int MESSAGE_ID_INDEX_IN_URI = 1;
94     private static final int MESSAGE_ID_INDEX_IN_THREAD_URI = 3;
95 
96     private final SQLiteOpenHelper mSqLiteOpenHelper;
97 
98     @VisibleForTesting
createRcsMessageTables(SQLiteDatabase db)99     public static void createRcsMessageTables(SQLiteDatabase db) {
100         Log.d(TAG, "Creating message tables");
101 
102         // Add the message tables
103         db.execSQL("CREATE TABLE " + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
104                 + " INTEGER PRIMARY KEY AUTOINCREMENT, " + RCS_THREAD_ID_COLUMN + " INTEGER, "
105                 + GLOBAL_ID_COLUMN + " TEXT, " + SUB_ID_COLUMN + " INTEGER, " + MESSAGE_TEXT_COLUMN
106                 + " TEXT," + LATITUDE_COLUMN + " REAL, " + LONGITUDE_COLUMN + " REAL, "
107                 + STATUS_COLUMN + " INTEGER, " + ORIGINATION_TIMESTAMP_COLUMN
108                 + " INTEGER, FOREIGN KEY(" + RCS_THREAD_ID_COLUMN + ") REFERENCES "
109                 + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + "))");
110 
111         db.execSQL("CREATE TABLE " + RCS_INCOMING_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
112                 + " INTEGER PRIMARY KEY, " + SENDER_PARTICIPANT_ID_COLUMN + " INTEGER, "
113                 + ARRIVAL_TIMESTAMP_COLUMN + " INTEGER, "
114                 + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN + " INTEGER, FOREIGN KEY ("
115                 + MESSAGE_ID_COLUMN + ") REFERENCES " + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
116                 + "))");
117 
118         db.execSQL("CREATE TABLE " + RCS_OUTGOING_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
119                 + " INTEGER PRIMARY KEY, FOREIGN KEY (" + MESSAGE_ID_COLUMN + ") REFERENCES "
120                 + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN + "))");
121 
122         db.execSQL("CREATE TABLE " + RCS_MESSAGE_DELIVERY_TABLE + "(" + MESSAGE_ID_COLUMN
123                 + " INTEGER, " + RCS_PARTICIPANT_ID_COLUMN + " INTEGER, "
124                 + DELIVERED_TIMESTAMP_COLUMN + " INTEGER, "
125                 + RcsMessageDeliveryColumns.SEEN_TIMESTAMP_COLUMN + " INTEGER, "
126                 + "CONSTRAINT message_delivery PRIMARY KEY (" + MESSAGE_ID_COLUMN + ", "
127                 + RCS_PARTICIPANT_ID_COLUMN + "), FOREIGN KEY (" + MESSAGE_ID_COLUMN
128                 + ") REFERENCES " + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN + "), FOREIGN KEY ("
129                 + RCS_PARTICIPANT_ID_COLUMN + ") REFERENCES " + RCS_PARTICIPANT_TABLE + "("
130                 + RCS_PARTICIPANT_ID_COLUMN + "))");
131 
132         db.execSQL("CREATE TABLE " + RCS_FILE_TRANSFER_TABLE + " (" + FILE_TRANSFER_ID_COLUMN
133                 + " INTEGER PRIMARY KEY AUTOINCREMENT, " + MESSAGE_ID_COLUMN + " INTEGER, "
134                 + SESSION_ID_COLUMN + " TEXT, " + CONTENT_URI_COLUMN + " TEXT, "
135                 + CONTENT_TYPE_COLUMN + " TEXT, " + FILE_SIZE_COLUMN + " INTEGER, "
136                 + SUCCESSFULLY_TRANSFERRED_BYTES + " INTEGER, " + TRANSFER_STATUS_COLUMN +
137                 " INTEGER, " + WIDTH_COLUMN + " INTEGER, " + HEIGHT_COLUMN + " INTEGER, "
138                 + DURATION_MILLIS_COLUMN + " INTEGER, " + PREVIEW_URI_COLUMN + " TEXT, "
139                 + PREVIEW_TYPE_COLUMN + " TEXT, FOREIGN KEY (" + MESSAGE_ID_COLUMN + ") REFERENCES "
140                 + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN + "))");
141 
142         // Add the views
143         //
144         // The following view inner joins incoming messages with all messages, inner joins outgoing
145         // messages with all messages, and unions them together, while also adding an is_incoming
146         // column for easily telling where the record came from. This may have been achieved with
147         // an outer join but SQLite doesn't support them.
148         //
149         // CREATE VIEW unified_message_view AS
150         //
151         // SELECT rcs_message.rcs_message_row_id,
152         //        rcs_message.rcs_thread_id,
153         //        rcs_message.rcs_message_global_id,
154         //        rcs_message.sub_id,
155         //        rcs_message.status,
156         //        rcs_message.origination_timestamp,
157         //        rcs_message.rcs_text,
158         //        rcs_message.latitude,
159         //        rcs_message.longitude,
160         //        0 AS sender_participant,
161         //        0 AS arrival_timestamp,
162         //        0 AS seen_timestamp,
163         //        outgoing AS message_type
164         //
165         // FROM rcs_message INNER JOIN rcs_outgoing_message
166         //          ON rcs_message.rcs_message_row_id=rcs_outgoing_message.rcs_message_row_id
167         //
168         // UNION
169         //
170         // SELECT rcs_message.rcs_message_row_id,
171         //        rcs_message.rcs_thread_id,
172         //        rcs_message.rcs_message_global_id,
173         //        rcs_message.sub_id,
174         //        rcs_message.status,
175         //        rcs_message.origination_timestamp,
176         //        rcs_message.rcs_text,
177         //        rcs_message.latitude,
178         //        rcs_message.longitude,
179         //        rcs_incoming_message.sender_participant,
180         //        rcs_incoming_message.arrival_timestamp,
181         //        rcs_incoming_message.seen_timestamp,
182         //        incoming AS message_type
183         //
184         // FROM rcs_message INNER JOIN rcs_incoming_message
185         //          ON rcs_message.rcs_message_row_id=rcs_incoming_message.rcs_message_row_id
186         //
187         db.execSQL("CREATE VIEW " + UNIFIED_MESSAGE_VIEW + " AS SELECT "
188                 + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
189                 + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
190                 + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
191                 + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
192                 + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
193                 + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
194                 + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
195                 + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
196                 + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN + ", "
197                 + "0 AS " + SENDER_PARTICIPANT_ID_COLUMN + ", "
198                 + "0 AS " + ARRIVAL_TIMESTAMP_COLUMN + ", "
199                 + "0 AS " + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN + ", "
200                 + MESSAGE_TYPE_OUTGOING + " AS " + MESSAGE_TYPE_COLUMN
201                 + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_OUTGOING_MESSAGE_TABLE
202                 + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
203                 + RCS_OUTGOING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN
204                 + " UNION SELECT "
205                 + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
206                 + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
207                 + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
208                 + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
209                 + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
210                 + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
211                 + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
212                 + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
213                 + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN + ", "
214                 + RCS_INCOMING_MESSAGE_TABLE + "." + SENDER_PARTICIPANT_ID_COLUMN + ", "
215                 + RCS_INCOMING_MESSAGE_TABLE + "." + ARRIVAL_TIMESTAMP_COLUMN + ", "
216                 + RCS_INCOMING_MESSAGE_TABLE + "." + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN
217                 + ", "
218                 + MESSAGE_TYPE_INCOMING + " AS " + MESSAGE_TYPE_COLUMN
219                 + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_INCOMING_MESSAGE_TABLE
220                 + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
221                 + RCS_INCOMING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN);
222 
223         // The following view inner joins incoming messages with all messages
224         //
225         // CREATE VIEW unified_incoming_message_view AS
226         //
227         // SELECT rcs_message.rcs_message_row_id,
228         //        rcs_message.rcs_thread_id,
229         //        rcs_message.rcs_message_global_id,
230         //        rcs_message.sub_id,
231         //        rcs_message.status,
232         //        rcs_message.origination_timestamp,
233         //        rcs_message.rcs_text,
234         //        rcs_message.latitude,
235         //        rcs_message.longitude,
236         //        rcs_incoming_message.sender_participant,
237         //        rcs_incoming_message.arrival_timestamp,
238         //        rcs_incoming_message.seen_timestamp,
239         //
240         // FROM rcs_message INNER JOIN rcs_incoming_message
241         //          ON rcs_message.rcs_message_row_id=rcs_incoming_message.rcs_message_row_id
242 
243         db.execSQL("CREATE VIEW " + UNIFIED_INCOMING_MESSAGE_VIEW + " AS SELECT "
244                 + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
245                 + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
246                 + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
247                 + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
248                 + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
249                 + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
250                 + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
251                 + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
252                 + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN + ", "
253                 + RCS_INCOMING_MESSAGE_TABLE + "." + SENDER_PARTICIPANT_ID_COLUMN + ", "
254                 + RCS_INCOMING_MESSAGE_TABLE + "." + ARRIVAL_TIMESTAMP_COLUMN + ", "
255                 + RCS_INCOMING_MESSAGE_TABLE + "." + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN
256                 + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_INCOMING_MESSAGE_TABLE
257                 + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
258                 + RCS_INCOMING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN);
259 
260         // The following view inner joins outgoing messages with all messages.
261         //
262         // CREATE VIEW unified_outgoing_message AS
263         //
264         // SELECT rcs_message.rcs_message_row_id,
265         //        rcs_message.rcs_thread_id,
266         //        rcs_message.rcs_message_global_id,
267         //        rcs_message.sub_id,
268         //        rcs_message.status,
269         //        rcs_message.origination_timestamp
270         //        rcs_message.rcs_text,
271         //        rcs_message.latitude,
272         //        rcs_message.longitude,
273         //
274         // FROM rcs_message INNER JOIN rcs_outgoing_message
275         //          ON rcs_message.rcs_message_row_id=rcs_outgoing_message.rcs_message_row_id
276 
277         db.execSQL("CREATE VIEW " + UNIFIED_OUTGOING_MESSAGE_VIEW + " AS SELECT "
278                 + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
279                 + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
280                 + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
281                 + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
282                 + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
283                 + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
284                 + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
285                 + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
286                 + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN
287                 + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_OUTGOING_MESSAGE_TABLE
288                 + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
289                 + RCS_OUTGOING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN);
290 
291         // Add triggers
292 
293         // Delete the corresponding rcs_message row upon deleting a row in rcs_incoming_message
294         //
295         // CREATE TRIGGER delete_common_message_after_incoming
296         //  AFTER DELETE ON rcs_incoming_message
297         // BEGIN
298         //  DELETE FROM rcs_message WHERE rcs_message.rcs_message_row_id=OLD.rcs_message_row_id;
299         // END
300         db.execSQL("CREATE TRIGGER deleteCommonMessageAfterIncoming AFTER DELETE ON "
301                 + RCS_INCOMING_MESSAGE_TABLE + " BEGIN DELETE FROM " + RCS_MESSAGE_TABLE
302                 + " WHERE " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "=OLD."
303                 + MESSAGE_ID_COLUMN + "; END");
304 
305         // Delete the corresponding rcs_message row upon deleting a row in rcs_outgoing_message
306         //
307         // CREATE TRIGGER delete_common_message_after_outgoing
308         //  AFTER DELETE ON rcs_outgoing_message
309         // BEGIN
310         //  DELETE FROM rcs_message WHERE rcs_message.rcs_message_row_id=OLD.rcs_message_row_id;
311         // END
312         db.execSQL("CREATE TRIGGER deleteCommonMessageAfterOutgoing AFTER DELETE ON "
313                 + RCS_OUTGOING_MESSAGE_TABLE + " BEGIN DELETE FROM " + RCS_MESSAGE_TABLE
314                 + " WHERE " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "=OLD."
315                 + MESSAGE_ID_COLUMN + "; END");
316     }
317 
RcsProviderMessageHelper(SQLiteOpenHelper sqLiteOpenHelper)318     RcsProviderMessageHelper(SQLiteOpenHelper sqLiteOpenHelper) {
319         mSqLiteOpenHelper = sqLiteOpenHelper;
320     }
321 
queryMessages(Bundle bundle)322     Cursor queryMessages(Bundle bundle) {
323         RcsMessageQueryParams queryParameters = null;
324         RcsQueryContinuationToken continuationToken = null;
325 
326         if (bundle != null) {
327             queryParameters = bundle.getParcelable(MESSAGE_QUERY_PARAMETERS_KEY);
328             continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
329         }
330 
331         if (continuationToken != null) {
332             return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
333                     continuationToken);
334         }
335 
336         // if no parameters were entered, build an empty query parameters object
337         if (queryParameters == null) {
338             queryParameters = new RcsMessageQueryParams.Builder().build();
339         }
340 
341         return performInitialQuery(queryParameters);
342     }
343 
performInitialQuery(RcsMessageQueryParams queryParameters)344     private Cursor performInitialQuery(RcsMessageQueryParams queryParameters) {
345         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
346 
347         StringBuilder rawQuery = new StringBuilder("SELECT * FROM ").append(UNIFIED_MESSAGE_VIEW);
348 
349         int messageType = queryParameters.getMessageType();
350         String messageLike = queryParameters.getMessageLike();
351         int threadId = queryParameters.getThreadId();
352 
353         boolean isMessageLikePresent = !TextUtils.isEmpty(messageLike);
354         boolean isMessageTypeFiltered = (messageType == MESSAGE_TYPE_INCOMING)
355                 || (messageType == MESSAGE_TYPE_OUTGOING);
356         boolean isThreadFiltered = threadId != THREAD_ID_NOT_SET;
357 
358         if (isMessageLikePresent || isMessageTypeFiltered || isThreadFiltered) {
359             rawQuery.append(" WHERE ");
360         }
361 
362         if (messageType == MESSAGE_TYPE_INCOMING) {
363             rawQuery.append(MESSAGE_TYPE_COLUMN).append("=").append(MESSAGE_TYPE_INCOMING);
364         } else if (messageType == MESSAGE_TYPE_OUTGOING) {
365             rawQuery.append(MESSAGE_TYPE_COLUMN).append("=").append(MESSAGE_TYPE_OUTGOING);
366         }
367 
368         if (isMessageLikePresent) {
369             if (isMessageTypeFiltered) {
370                 rawQuery.append(" AND ");
371             }
372             rawQuery.append(MESSAGE_TEXT_COLUMN).append(" LIKE \"").append(messageLike)
373                     .append("\"");
374         }
375 
376         if (isThreadFiltered) {
377             if (isMessageLikePresent || isMessageTypeFiltered) {
378                 rawQuery.append(" AND ");
379             }
380             rawQuery.append(RCS_THREAD_ID_COLUMN).append("=").append(threadId);
381         }
382 
383         // TODO - figure out a way to see if this message has file transfer or not. Ideally we
384         // should join the unified table with file transfer table, but using a trigger to change a
385         // flag on rcs_message would also work
386 
387         rawQuery.append(" ORDER BY ");
388 
389         int sortingProperty = queryParameters.getSortingProperty();
390         if (sortingProperty == RcsMessageQueryParams.SORT_BY_TIMESTAMP) {
391             rawQuery.append(ORIGINATION_TIMESTAMP_COLUMN);
392         } else {
393             rawQuery.append(MESSAGE_ID_COLUMN);
394         }
395 
396         rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
397 
398         RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
399         String rawQueryAsString = rawQuery.toString();
400         Cursor cursor = db.rawQuery(rawQueryAsString, null);
401 
402         // If the query was paginated, build the next query
403         int limit = queryParameters.getLimit();
404         if (limit > 0) {
405             RcsProviderUtil.createContinuationTokenBundle(cursor,
406                     new RcsQueryContinuationToken(MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE,
407                             rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
408         }
409 
410         return cursor;
411     }
412 
queryUnifiedMessageWithId(Uri uri)413     Cursor queryUnifiedMessageWithId(Uri uri) {
414         return queryUnifiedMessageWithSelection(getMessageIdSelection(uri), null);
415     }
416 
queryUnifiedMessageWithIdInThread(Uri uri)417     Cursor queryUnifiedMessageWithIdInThread(Uri uri) {
418         return queryUnifiedMessageWithSelection(getMessageIdSelectionInThreadUri(uri), null);
419     }
420 
queryUnifiedMessageWithSelection(String selection, String[] selectionArgs)421     Cursor queryUnifiedMessageWithSelection(String selection, String[] selectionArgs) {
422         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
423         return db.query(UNIFIED_MESSAGE_VIEW, null, selection, selectionArgs, null, null, null,
424                 null);
425     }
426 
queryIncomingMessageWithId(Uri uri)427     Cursor queryIncomingMessageWithId(Uri uri) {
428         return queryIncomingMessageWithSelection(getMessageIdSelection(uri), null);
429     }
430 
queryIncomingMessageWithSelection(String selection, String[] selectionArgs)431     Cursor queryIncomingMessageWithSelection(String selection, String[] selectionArgs) {
432         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
433         return db.query(UNIFIED_INCOMING_MESSAGE_VIEW, null, selection, selectionArgs, null, null,
434                 null, null);
435     }
436 
queryOutgoingMessageWithId(Uri uri)437     Cursor queryOutgoingMessageWithId(Uri uri) {
438         return queryOutgoingMessageWithSelection(getMessageIdSelection(uri), null);
439     }
440 
queryOutgoingMessageWithSelection(String selection, String[] selectionArgs)441     Cursor queryOutgoingMessageWithSelection(String selection, String[] selectionArgs) {
442         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
443         return db.query(UNIFIED_OUTGOING_MESSAGE_VIEW, null, selection, selectionArgs, null, null,
444                 null, null);
445     }
446 
queryAllMessagesOnThread(Uri uri, String selection, String[] selectionArgs)447     Cursor queryAllMessagesOnThread(Uri uri, String selection, String[] selectionArgs) {
448         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
449 
450         String appendedSelection = appendThreadIdToSelection(uri, selection);
451         return db.query(UNIFIED_MESSAGE_VIEW, null, appendedSelection, null, null, null, null);
452     }
453 
insertMessageOnThread(Uri uri, ContentValues valuesParameter, boolean isIncoming, boolean is1To1)454     Uri insertMessageOnThread(Uri uri, ContentValues valuesParameter, boolean isIncoming,
455             boolean is1To1) {
456         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
457 
458         String threadId = RcsProviderThreadHelper.getThreadIdFromUri(uri);
459         ContentValues values = new ContentValues(valuesParameter);
460         values.put(RCS_THREAD_ID_COLUMN, Integer.parseInt(threadId));
461 
462         db.beginTransaction();
463 
464         ContentValues subMessageTableValues = new ContentValues();
465         if (isIncoming) {
466             subMessageTableValues = getIncomingMessageValues(values);
467         }
468 
469         long rowId;
470         try {
471             rowId = db.insert(RCS_MESSAGE_TABLE, MESSAGE_ID_COLUMN, values);
472             if (rowId == INSERTION_FAILED) {
473                 return null;
474             }
475 
476             subMessageTableValues.put(MESSAGE_ID_COLUMN, rowId);
477             long tempId = db.insert(
478                     isIncoming ? RCS_INCOMING_MESSAGE_TABLE : RCS_OUTGOING_MESSAGE_TABLE,
479                     MESSAGE_ID_COLUMN, subMessageTableValues);
480             if (tempId == INSERTION_FAILED) {
481                 return null;
482             }
483 
484             // if the thread is outgoing, insert message deliveries
485             if (!isIncoming && !insertMessageDeliveriesForOutgoingMessageCreation(db, tempId,
486                     threadId)) {
487                 return null;
488             }
489 
490             db.setTransactionSuccessful();
491         } finally {
492             db.endTransaction();
493         }
494 
495         values.remove(RCS_THREAD_ID_COLUMN);
496 
497         String threadPart =  is1To1 ? RCS_1_TO_1_THREAD_URI_PART : RCS_GROUP_THREAD_URI_PART;
498         String messagePart = isIncoming ? INCOMING_MESSAGE_URI_PART : OUTGOING_MESSAGE_URI_PART;
499 
500         return CONTENT_AND_AUTHORITY.buildUpon().appendPath(threadPart).appendPath(threadId).
501                 appendPath(messagePart).appendPath(Long.toString(rowId)).build();
502     }
503 
504     // Tries to insert deliveries for outgoing message, returns false if it fails.
insertMessageDeliveriesForOutgoingMessageCreation( SQLiteDatabase dbInTransaction, long messageId, String threadId)505     private boolean insertMessageDeliveriesForOutgoingMessageCreation(
506             SQLiteDatabase dbInTransaction, long messageId, String threadId) {
507         try (Cursor participantsInThreadCursor = dbInTransaction.query(
508                 RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, null, RCS_THREAD_ID_COLUMN + "=?",
509                 new String[]{threadId}, null, null, null)) {
510             if (participantsInThreadCursor == null) {
511                 return false;
512             }
513 
514             while (participantsInThreadCursor.moveToNext()) {
515                 String participantId = participantsInThreadCursor.getString(
516                         participantsInThreadCursor.getColumnIndex(
517                                 RCS_PARTICIPANT_ID_COLUMN));
518 
519                 long insertionRow = insertMessageDelivery(Long.toString(messageId), participantId,
520                         new ContentValues());
521 
522                 if (insertionRow == INSERTION_FAILED) {
523                     return false;
524                 }
525             }
526         }
527         return true;
528     }
529 
insertMessageDelivery(Uri uri, ContentValues values)530     long insertMessageDelivery(Uri uri, ContentValues values) {
531         String messageId = getMessageIdFromUri(uri);
532         String participantId = getParticipantIdFromDeliveryUri(uri);
533         return insertMessageDelivery(messageId, participantId, values);
534     }
535 
insertMessageDelivery(String messageId, String participantId, ContentValues valuesParameter)536     private long insertMessageDelivery(String messageId, String participantId,
537             ContentValues valuesParameter) {
538         ContentValues values = new ContentValues(valuesParameter);
539         values.put(MESSAGE_ID_COLUMN, messageId);
540         values.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
541 
542         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
543         return db.insert(RCS_MESSAGE_DELIVERY_TABLE, MESSAGE_ID_COLUMN, values);
544     }
545 
updateDelivery(Uri uri, ContentValues contentValues)546     int updateDelivery(Uri uri, ContentValues contentValues) {
547         String messageId = getMessageIdFromUri(uri);
548         String participantId = getParticipantIdFromDeliveryUri(uri);
549 
550         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
551         return db.update(RCS_MESSAGE_DELIVERY_TABLE, contentValues,
552                 MESSAGE_ID_COLUMN + "=? AND " + RCS_PARTICIPANT_ID_COLUMN + "=?",
553                 new String[]{messageId, participantId});
554     }
555 
deleteIncomingMessageWithId(Uri uri)556     int deleteIncomingMessageWithId(Uri uri) {
557         return deleteIncomingMessageWithSelection(getMessageIdSelection(uri), null);
558     }
559 
deleteIncomingMessageWithSelection(String selection, String[] selectionArgs)560     int deleteIncomingMessageWithSelection(String selection, String[] selectionArgs) {
561         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
562         return db.delete(RCS_INCOMING_MESSAGE_TABLE, selection, selectionArgs);
563     }
564 
deleteOutgoingMessageWithId(Uri uri)565     int deleteOutgoingMessageWithId(Uri uri) {
566         return deleteOutgoingMessageWithSelection(getMessageIdSelection(uri), null);
567     }
568 
deleteOutgoingMessageWithSelection(String selection, String[] selectionArgs)569     int deleteOutgoingMessageWithSelection(String selection, String[] selectionArgs) {
570         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
571         return db.delete(RCS_OUTGOING_MESSAGE_TABLE, selection, selectionArgs);
572     }
573 
updateIncomingMessage(Uri uri, ContentValues values)574     int updateIncomingMessage(Uri uri, ContentValues values) {
575         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
576 
577         ContentValues incomingMessageValues = getIncomingMessageValues(values);
578 
579         int updateCountInIncoming = 0;
580         int updateCountInCommon = 0;
581         db.beginTransaction();
582         if (!incomingMessageValues.isEmpty()) {
583             updateCountInIncoming = db.update(RCS_INCOMING_MESSAGE_TABLE, incomingMessageValues,
584                     getMessageIdSelection(uri), null);
585         }
586         if (!values.isEmpty()) {
587             updateCountInCommon = db.update(RCS_MESSAGE_TABLE, values, getMessageIdSelection(uri),
588                     null);
589         }
590         db.setTransactionSuccessful();
591         db.endTransaction();
592 
593         return Math.max(updateCountInIncoming, updateCountInCommon);
594     }
595 
updateOutgoingMessage(Uri uri, ContentValues values)596     int updateOutgoingMessage(Uri uri, ContentValues values) {
597         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
598         return db.update(RCS_MESSAGE_TABLE, values, getMessageIdSelection(uri), null);
599     }
600 
queryOutgoingMessageDeliveries(Uri uri)601     Cursor queryOutgoingMessageDeliveries(Uri uri) {
602         String messageId = getMessageIdFromUri(uri);
603 
604         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
605         return db.query(RCS_MESSAGE_DELIVERY_TABLE, null, MESSAGE_ID_COLUMN + "=?",
606                 new String[]{messageId}, null, null, null);
607     }
608 
queryFileTransfer(Uri uri)609     Cursor queryFileTransfer(Uri uri) {
610         SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
611         return db.query(RCS_FILE_TRANSFER_TABLE, null, FILE_TRANSFER_ID_COLUMN + "=?",
612                 new String[]{getFileTransferIdFromUri(uri)}, null, null, null, null);
613     }
614 
insertFileTransferToMessage(Uri uri, ContentValues values)615     long insertFileTransferToMessage(Uri uri, ContentValues values) {
616         String messageId = getMessageIdFromUri(uri);
617         values.put(MESSAGE_ID_COLUMN, messageId);
618 
619         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
620         long rowId = db.insert(RCS_FILE_TRANSFER_TABLE, MESSAGE_ID_COLUMN, values);
621         values.remove(MESSAGE_ID_COLUMN);
622 
623         if (rowId == INSERTION_FAILED) {
624             rowId = TRANSACTION_FAILED;
625         }
626 
627         return rowId;
628     }
629 
deleteFileTransfer(Uri uri)630     int deleteFileTransfer(Uri uri) {
631         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
632         return db.delete(RCS_FILE_TRANSFER_TABLE, FILE_TRANSFER_ID_COLUMN + "=?",
633                 new String[]{getFileTransferIdFromUri(uri)});
634     }
635 
updateFileTransfer(Uri uri, ContentValues values)636     int updateFileTransfer(Uri uri, ContentValues values) {
637         SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
638         return db.update(RCS_FILE_TRANSFER_TABLE, values,
639                 FILE_TRANSFER_ID_COLUMN + "=?", new String[]{getFileTransferIdFromUri(uri)});
640     }
641 
642     /**
643      * Removes the incoming message values out of all values and returns as a separate content
644      * values object.
645      */
getIncomingMessageValues(ContentValues allValues)646     private ContentValues getIncomingMessageValues(ContentValues allValues) {
647         ContentValues incomingMessageValues = new ContentValues();
648 
649         if (allValues.containsKey(SENDER_PARTICIPANT_ID_COLUMN)) {
650             incomingMessageValues.put(SENDER_PARTICIPANT_ID_COLUMN,
651                     allValues.getAsInteger(SENDER_PARTICIPANT_ID_COLUMN));
652             allValues.remove(SENDER_PARTICIPANT_ID_COLUMN);
653         }
654 
655         if (allValues.containsKey(ARRIVAL_TIMESTAMP_COLUMN)) {
656             incomingMessageValues.put(
657                     ARRIVAL_TIMESTAMP_COLUMN, allValues.getAsLong(ARRIVAL_TIMESTAMP_COLUMN));
658             allValues.remove(ARRIVAL_TIMESTAMP_COLUMN);
659         }
660 
661         if (allValues.containsKey(RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN)) {
662             incomingMessageValues.put(
663                     RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN,
664                     allValues.getAsLong(RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN));
665             allValues.remove(RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN);
666         }
667 
668         return incomingMessageValues;
669     }
670 
appendThreadIdToSelection(Uri uri, String selection)671     private String appendThreadIdToSelection(Uri uri, String selection) {
672         String threadIdSelection = RCS_THREAD_ID_COLUMN + "=" + getThreadIdFromUri(uri);
673 
674         if (TextUtils.isEmpty(selection)) {
675             return threadIdSelection;
676         }
677 
678         return "(" + selection + ") AND " + threadIdSelection;
679     }
680 
getMessageIdSelection(Uri uri)681     private String getMessageIdSelection(Uri uri) {
682         return MESSAGE_ID_COLUMN + "=" + getMessageIdFromUri(uri);
683     }
684 
getMessageIdSelectionInThreadUri(Uri uri)685     private String getMessageIdSelectionInThreadUri(Uri uri) {
686         return MESSAGE_ID_COLUMN + "=" + getMessageIdFromThreadUri(uri);
687     }
688 
getMessageIdFromUri(Uri uri)689     private String getMessageIdFromUri(Uri uri) {
690         return uri.getPathSegments().get(MESSAGE_ID_INDEX_IN_URI);
691     }
692 
getFileTransferIdFromUri(Uri uri)693     private String getFileTransferIdFromUri(Uri uri) {
694         // this works because messages and file transfer uri's have the same indices.
695         return getMessageIdFromUri(uri);
696     }
697 
getParticipantIdFromDeliveryUri(Uri uri)698     private String getParticipantIdFromDeliveryUri(Uri uri) {
699         // this works because messages in threads and participants in deliveries have the same
700         // indices.
701         return getMessageIdFromThreadUri(uri);
702     }
703 
getMessageIdFromThreadUri(Uri uri)704     private String getMessageIdFromThreadUri(Uri uri) {
705         return uri.getPathSegments().get(MESSAGE_ID_INDEX_IN_THREAD_URI);
706     }
707 }
708