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