Implementation for RcsMessageStore [provider]
This change adds the RcsMessageStore APIs. Please see attached bug for
the design and one-pager.
Test: Added unit tests
Bug: 109759350
Change-Id: Iff559416b8345ccde09b51b6d502ec4dd4e7bd9f
diff --git a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
index feac8f4..761e2df 100644
--- a/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
+++ b/src/com/android/providers/telephony/MmsSmsDatabaseHelper.java
@@ -289,7 +289,8 @@
}
}
- private MmsSmsDatabaseHelper(Context context, MmsSmsDatabaseErrorHandler dbErrorHandler) {
+ @VisibleForTesting
+ MmsSmsDatabaseHelper(Context context, MmsSmsDatabaseErrorHandler dbErrorHandler) {
super(context, DATABASE_NAME, null, DATABASE_VERSION, dbErrorHandler);
mContext = context;
// Memory optimization - close idle connections after 30s of inactivity
@@ -528,7 +529,7 @@
localLog("onCreate: Creating all SMS-MMS tables.");
// if FBE is not supported, or if this onCreate is for CE partition database
if (!StorageManager.isFileEncryptedNativeOrEmulated()
- || mContext.isCredentialProtectedStorage()) {
+ || (mContext != null && mContext.isCredentialProtectedStorage())) {
localLog("onCreate: broadcasting ACTION_SMS_MMS_DB_CREATED");
// Broadcast ACTION_SMS_MMS_DB_CREATED
Intent intent = new Intent(Sms.Intents.ACTION_SMS_MMS_DB_CREATED);
@@ -554,6 +555,8 @@
if (IS_RCS_TABLE_SCHEMA_CODE_COMPLETE) {
RcsProviderThreadHelper.createThreadTables(db);
RcsProviderParticipantHelper.createParticipantTables(db);
+ RcsProviderMessageHelper.createRcsMessageTables(db);
+ RcsProviderEventHelper.createRcsEventTables(db);
}
createCommonTriggers(db);
@@ -719,7 +722,8 @@
}
}
- private void createMmsTables(SQLiteDatabase db) {
+ @VisibleForTesting
+ void createMmsTables(SQLiteDatabase db) {
// N.B.: Whenever the columns here are changed, the columns in
// {@ref MmsSmsProvider} must be changed to match.
db.execSQL("CREATE TABLE " + MmsProvider.TABLE_PDU + " (" +
@@ -1041,7 +1045,8 @@
"message_body TEXT," + // message body
"display_originating_addr TEXT);";
// email address if from an email gateway, otherwise same as address
- private void createSmsTables(SQLiteDatabase db) {
+ @VisibleForTesting
+ void createSmsTables(SQLiteDatabase db) {
// N.B.: Whenever the columns here are changed, the columns in
// {@ref MmsSmsProvider} must be changed to match.
db.execSQL(CREATE_SMS_TABLE_STRING);
@@ -1067,7 +1072,8 @@
Sms.TYPE + "=" + Sms.MESSAGE_TYPE_SENT + ";");
}
- private void createCommonTables(SQLiteDatabase db) {
+ @VisibleForTesting
+ void createCommonTables(SQLiteDatabase db) {
// TODO Ensure that each entry is removed when the last use of
// any address equivalent to its address is removed.
@@ -1643,6 +1649,8 @@
}
RcsProviderThreadHelper.createThreadTables(db);
RcsProviderParticipantHelper.createParticipantTables(db);
+ RcsProviderMessageHelper.createRcsMessageTables(db);
+ RcsProviderEventHelper.createRcsEventTables(db);
return;
}
diff --git a/src/com/android/providers/telephony/RcsProvider.java b/src/com/android/providers/telephony/RcsProvider.java
index af9212e..db45402 100644
--- a/src/com/android/providers/telephony/RcsProvider.java
+++ b/src/com/android/providers/telephony/RcsProvider.java
@@ -15,40 +15,285 @@
*/
package com.android.providers.telephony;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+
+import static com.android.providers.telephony.RcsProviderUtil.buildUriWithRowIdAppended;
+import static com.android.providers.telephony.RcsProviderUtil.returnUriAsIsIfSuccessful;
+
import android.app.AppOpsManager;
import android.content.ContentProvider;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Content provider to handle RCS messages. The functionality here is similar to SmsProvider,
- * MmsProvider etc. This is not meant to be public.
+ * MmsProvider etc.
+ *
+ * The provider has constraints around inserting, updating and deleting - the user needs to know
+ * whether they are inserting a message that is incoming/outgoing, or the thread they are inserting
+ * is a group or p2p etc. This is in order to keep the implementation simple and avoid complex
+ * queries.
*
* @hide
*/
public class RcsProvider extends ContentProvider {
static final String TAG = "RcsProvider";
static final String AUTHORITY = "rcs";
+ private static final String CONTENT_AUTHORITY = "content://" + AUTHORITY;
- private static final Uri THREAD_URI_PREFIX = Uri.parse("content://" + AUTHORITY + "/thread");
- private static final Uri PARTICIPANT_URI_PREFIX = Uri.parse(
- "content://" + AUTHORITY + "/participant");
+ private static final Uri PARTICIPANT_URI_PREFIX =
+ Uri.parse(CONTENT_AUTHORITY + "/participant/");
+ private static final Uri P2P_THREAD_URI_PREFIX = Uri.parse(CONTENT_AUTHORITY + "/p2p_thread/");
+ private static final Uri FILE_TRANSFER_PREFIX = Uri.parse(
+ CONTENT_AUTHORITY + "/file_transfer/");
+ static final Uri GROUP_THREAD_URI_PREFIX = Uri.parse(CONTENT_AUTHORITY + "/group_thread/");
+
+ // Rcs table names
+ static final String RCS_THREAD_TABLE = "rcs_thread";
+ static final String RCS_1_TO_1_THREAD_TABLE = "rcs_1_to_1_thread";
+ static final String RCS_GROUP_THREAD_TABLE = "rcs_group_thread";
+ static final String UNIFIED_RCS_THREAD_VIEW = "rcs_unified_rcs_thread_view";
+ static final String RCS_PARTICIPANT_TABLE = "rcs_participant";
+ static final String RCS_PARTICIPANT_THREAD_JUNCTION_TABLE = "rcs_thread_participant";
+ static final String RCS_MESSAGE_TABLE = "rcs_message";
+ static final String RCS_INCOMING_MESSAGE_TABLE = "rcs_incoming_message";
+ static final String RCS_OUTGOING_MESSAGE_TABLE = "rcs_outgoing_message";
+ static final String RCS_MESSAGE_DELIVERY_TABLE = "rcs_message_delivery";
+ static final String UNIFIED_MESSAGE_VIEW = "rcs_unified_message_view";
+ static final String RCS_FILE_TRANSFER_TABLE = "rcs_file_transfer";
+ static final String RCS_THREAD_EVENT_TABLE = "rcs_thread_event";
+ static final String RCS_PARTICIPANT_EVENT_TABLE = "rcs_participant_event";
+ static final String RCS_UNIFIED_EVENT_VIEW = "rcs_unified_event_view";
private static final UriMatcher URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
- private static final int THREAD = 1;
- private static final int PARTICIPANT = 2;
+
+ private static final int UNIFIED_RCS_THREAD = 1;
+ private static final int UNIFIED_RCS_THREAD_WITH_ID = 2;
+ private static final int PARTICIPANT = 3;
+ private static final int PARTICIPANT_WITH_ID = 4;
+ private static final int PARTICIPANT_ALIAS_CHANGE_EVENT = 5;
+ private static final int PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID = 6;
+ private static final int P2P_THREAD = 7;
+ private static final int P2P_THREAD_WITH_ID = 8;
+ private static final int P2P_THREAD_PARTICIPANT = 9;
+ private static final int P2P_THREAD_PARTICIPANT_WITH_ID = 10;
+ private static final int GROUP_THREAD = 11;
+ private static final int GROUP_THREAD_WITH_ID = 12;
+ private static final int GROUP_THREAD_PARTICIPANT = 13;
+ private static final int GROUP_THREAD_PARTICIPANT_WITH_ID = 14;
+ private static final int GROUP_THREAD_PARTICIPANT_JOINED_EVENT = 15;
+ private static final int GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID = 16;
+ private static final int GROUP_THREAD_PARTICIPANT_LEFT_EVENT = 17;
+ private static final int GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID = 18;
+ private static final int GROUP_THREAD_NAME_CHANGE_EVENT = 19;
+ private static final int GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID = 20;
+ private static final int GROUP_THREAD_ICON_CHANGE_EVENT = 21;
+ private static final int GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID = 22;
+ private static final int UNIFIED_MESSAGE = 23;
+ private static final int UNIFIED_MESSAGE_WITH_ID = 24;
+ private static final int UNIFIED_MESSAGE_WITH_FILE_TRANSFER = 25;
+ private static final int INCOMING_MESSAGE = 26;
+ private static final int INCOMING_MESSAGE_WITH_ID = 27;
+ private static final int OUTGOING_MESSAGE = 28;
+ private static final int OUTGOING_MESSAGE_WITH_ID = 29;
+ private static final int OUTGOING_MESSAGE_DELIVERY = 30;
+ private static final int OUTGOING_MESSAGE_DELIVERY_WITH_ID = 31;
+ private static final int UNIFIED_MESSAGE_ON_THREAD = 32;
+ private static final int UNIFIED_MESSAGE_ON_THREAD_WITH_ID = 33;
+ private static final int INCOMING_MESSAGE_ON_P2P_THREAD = 34;
+ private static final int INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID = 35;
+ private static final int OUTGOING_MESSAGE_ON_P2P_THREAD = 36;
+ private static final int OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID = 37;
+ private static final int INCOMING_MESSAGE_ON_GROUP_THREAD = 38;
+ private static final int INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID = 39;
+ private static final int OUTGOING_MESSAGE_ON_GROUP_THREAD = 40;
+ private static final int OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID = 41;
+ private static final int FILE_TRANSFER_WITH_ID = 42;
+ private static final int EVENT = 43;
SQLiteOpenHelper mDbOpenHelper;
+ @VisibleForTesting
+ RcsProviderThreadHelper mThreadHelper;
+ @VisibleForTesting
+ RcsProviderParticipantHelper mParticipantHelper;
+ @VisibleForTesting
+ RcsProviderMessageHelper mMessageHelper;
+ @VisibleForTesting
+ RcsProviderEventHelper mEventHelper;
+
static {
- URL_MATCHER.addURI(AUTHORITY, "thread", THREAD);
+ // example URI: content://rcs/thread
+ URL_MATCHER.addURI(AUTHORITY, "thread", UNIFIED_RCS_THREAD);
+
+ // example URI: content://rcs/thread/4, where 4 is the thread id.
+ URL_MATCHER.addURI(AUTHORITY, "thread/#", UNIFIED_RCS_THREAD_WITH_ID);
+
+ // example URI: content://rcs/participant
URL_MATCHER.addURI(AUTHORITY, "participant", PARTICIPANT);
+
+ // example URI: content://rcs/participant/12, where 12 is the participant id
+ URL_MATCHER.addURI(AUTHORITY, "participant/#", PARTICIPANT_WITH_ID);
+
+ // example URI: content://rcs/participant/12/alias_change_event, where 12 is the participant
+ // id.
+ URL_MATCHER.addURI(AUTHORITY, "participant/#/alias_change_event",
+ PARTICIPANT_ALIAS_CHANGE_EVENT);
+
+ // example URI: content://rcs/participant/12/alias_change_event/4, where 12 is the
+ // participant id, and 4 is the event id
+ URL_MATCHER.addURI(AUTHORITY, "participant/#/alias_change_event/#",
+ PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID);
+
+ // example URI: content://rcs/p2p_thread
+ URL_MATCHER.addURI(AUTHORITY, "p2p_thread", P2P_THREAD);
+
+ // example URI: content://rcs/p2p_thread/4, where 4 is the thread id
+ URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#", P2P_THREAD_WITH_ID);
+
+ // example URI: content://rcs/p2p_thread/4/participant, where 4 is the thread id
+ URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/participant", P2P_THREAD_PARTICIPANT);
+
+ // example URI: content://rcs/p2p_thread/9/participant/3", only supports a 1 time insert.
+ // 9 is the thread ID, 3 is the participant ID.
+ URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/participant/#", P2P_THREAD_PARTICIPANT_WITH_ID);
+
+ // example URI: content://rcs/group_thread
+ URL_MATCHER.addURI(AUTHORITY, "group_thread", GROUP_THREAD);
+
+ // example URI: content://rcs/group_thread/13, where 13 is the _id in rcs_threads table.
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#", GROUP_THREAD_WITH_ID);
+
+ // example URI: content://rcs/group_thread/13/participant_joined_event. Supports
+ // queries and inserts
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant_joined_event",
+ GROUP_THREAD_PARTICIPANT_JOINED_EVENT);
+
+ // example URI: content://rcs/group_thread/13/participant_joined_event/3. Supports deletes.
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant_joined_event/#",
+ GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID);
+
+ // example URI: content://rcs/group_thread/13/participant_left_event. Supports queries
+ // and inserts
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant_left_event",
+ GROUP_THREAD_PARTICIPANT_LEFT_EVENT);
+
+ // example URI: content://rcs/group_thread/13/participant_left_event/5. Supports deletes
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant_left_event/#",
+ GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID);
+
+ // example URI: content://rcs/group_thread/13/name_changed_event. Supports queries and
+ // inserts
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/name_changed_event",
+ GROUP_THREAD_NAME_CHANGE_EVENT);
+
+ // example URI: content://rcs/group_thread/13/name_changed_event/7. Supports deletes
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/name_changed_event/#",
+ GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID);
+
+ // example URI: content://rcs/group_thread/13/icon_changed_event. Supports queries and
+ // inserts
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/icon_changed_event",
+ GROUP_THREAD_ICON_CHANGE_EVENT);
+
+ // example URI: content://rcs/group_thread/13/icon_changed_event/9. Supports deletes
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/icon_changed_event/#",
+ GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID);
+
+ // example URI: content://rcs/group_thread/18/participant
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant",
+ GROUP_THREAD_PARTICIPANT);
+
+ // example URI: content://rcs/group_thread/21/participant/4, only supports inserts and
+ // deletes
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/participant/#",
+ GROUP_THREAD_PARTICIPANT_WITH_ID);
+
+ // example URI: content://rcs/message
+ URL_MATCHER.addURI(AUTHORITY, "message", UNIFIED_MESSAGE);
+
+ // example URI: content://rcs/message/4, where 4 is the message id.
+ URL_MATCHER.addURI(AUTHORITY, "message/#", UNIFIED_MESSAGE_WITH_ID);
+
+ // example URI: content://rcs/message/4/file_transfer, only supports inserts
+ URL_MATCHER.addURI(AUTHORITY, "message/#/file_transfer",
+ UNIFIED_MESSAGE_WITH_FILE_TRANSFER);
+
+ // example URI: content://rcs/incoming_message
+ URL_MATCHER.addURI(AUTHORITY, "incoming_message", INCOMING_MESSAGE);
+
+ // example URI: content://rcs/incoming_message/45
+ URL_MATCHER.addURI(AUTHORITY, "incoming_message/#", INCOMING_MESSAGE_WITH_ID);
+
+ // example URI: content://rcs/outgoing_message
+ URL_MATCHER.addURI(AUTHORITY, "outgoing_message", OUTGOING_MESSAGE);
+
+ // example URI: content://rcs/outgoing_message/54
+ URL_MATCHER.addURI(AUTHORITY, "outgoing_message/#", OUTGOING_MESSAGE_WITH_ID);
+
+ // example URI: content://rcs/outgoing_message/54/delivery. Only supports queries
+ URL_MATCHER.addURI(AUTHORITY, "outgoing_message/#/delivery", OUTGOING_MESSAGE_DELIVERY);
+
+ // example URI: content://rcs/outgoing_message/9/delivery/4. Does not support queries
+ URL_MATCHER.addURI(AUTHORITY, "outgoing_message/#/delivery/#",
+ OUTGOING_MESSAGE_DELIVERY_WITH_ID);
+
+ // example URI: content://rcs/thread/5/message, only supports querying.
+ URL_MATCHER.addURI(AUTHORITY, "thread/#/message", UNIFIED_MESSAGE_ON_THREAD);
+
+ // example URI: content://rcs/thread/5/message/40, only supports querying.
+ URL_MATCHER.addURI(AUTHORITY, "thread/#/message/#", UNIFIED_MESSAGE_ON_THREAD_WITH_ID);
+
+ // example URI: content://rcs/p2p_thread/3/incoming_message. Only available for inserting
+ // incoming messages onto a 1 to 1 thread.
+ URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/incoming_message",
+ INCOMING_MESSAGE_ON_P2P_THREAD);
+
+ // example URI: content://rcs/p2p_thread/11/incoming_message/45. Only supports querying
+ URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/incoming_message/#",
+ INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID);
+
+ // example URI: content://rcs/p2p_thread/3/outgoing_message. Only available for inserting
+ // outgoing messages onto a 1 to 1 thread.
+ URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/outgoing_message",
+ OUTGOING_MESSAGE_ON_P2P_THREAD);
+
+ // example URI: content://rcs/p2p_thread/11/outgoing_message/46. Only supports querying
+ URL_MATCHER.addURI(AUTHORITY, "p2p_thread/#/outgoing_message/#",
+ OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID);
+
+ // example URI: content://rcs/group_thread/3/incoming_message. Only available for inserting
+ // incoming messages onto a group thread.
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/incoming_message",
+ INCOMING_MESSAGE_ON_GROUP_THREAD);
+
+ // example URI: content://rcs/group_thread/3/incoming_message/71. Only supports querying
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/incoming_message/#",
+ INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID);
+
+ // example URI: content://rcs/group_thread/3/outgoing_message. Only available for inserting
+ // outgoing messages onto a group thread.
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/outgoing_message",
+ OUTGOING_MESSAGE_ON_GROUP_THREAD);
+
+ // example URI: content://rcs/group_thread/13/outgoing_message/72. Only supports querying
+ URL_MATCHER.addURI(AUTHORITY, "group_thread/#/outgoing_message/#",
+ OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID);
+
+ // example URI: content://rcs/file_transfer/1. Does not support insertion
+ URL_MATCHER.addURI(AUTHORITY, "file_transfer/#", FILE_TRANSFER_WITH_ID);
+
+ // example URI: content://rcs/event
+ URL_MATCHER.addURI(AUTHORITY, "event", EVENT);
}
@Override
@@ -56,29 +301,157 @@
setAppOps(AppOpsManager.OP_READ_SMS, AppOpsManager.OP_WRITE_SMS);
// Use the credential encrypted mmssms.db for RCS messages.
mDbOpenHelper = MmsSmsDatabaseHelper.getInstanceForCe(getContext());
+ mParticipantHelper = new RcsProviderParticipantHelper(mDbOpenHelper);
+ mThreadHelper = new RcsProviderThreadHelper(mDbOpenHelper);
+ mMessageHelper = new RcsProviderMessageHelper(mDbOpenHelper);
+ mEventHelper = new RcsProviderEventHelper(mDbOpenHelper);
return true;
}
+ /**
+ * ContentResolver has a weird bug that if both query methods are overridden, it will always
+ * pick the bundle one to call, but will still require us to override this one as it is
+ * abstract. Work around by putting parameters in a bundle.
+ */
@Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
- String sortOrder) {
+ public synchronized Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ Bundle bundle = new Bundle();
+ bundle.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selection);
+ bundle.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
+ bundle.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER, sortOrder);
+ return query(uri, projection, bundle, null);
+ }
+
+ @Override
+ public synchronized Cursor query(Uri uri, String[] projection, Bundle queryArgs,
+ CancellationSignal unused) {
int match = URL_MATCHER.match(uri);
- SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- SQLiteDatabase readableDatabase = mDbOpenHelper.getReadableDatabase();
+
+ String selection = null;
+ String[] selectionArgs = null;
+ String sortOrder = null;
+
+ if (queryArgs != null) {
+ selection = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SELECTION);
+ selectionArgs = queryArgs.getStringArray(
+ ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS);
+ sortOrder = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
+ }
switch (match) {
- case THREAD:
- RcsProviderThreadHelper.buildThreadQuery(qb);
- break;
+ case UNIFIED_RCS_THREAD:
+ return mThreadHelper.queryUnifiedThread(queryArgs);
+ case UNIFIED_RCS_THREAD_WITH_ID:
+ return mThreadHelper.queryUnifiedThreadUsingId(uri, projection);
case PARTICIPANT:
- RcsProviderParticipantHelper.buildParticipantsQuery(qb);
+ return mParticipantHelper.queryParticipant(queryArgs);
+ case PARTICIPANT_WITH_ID:
+ return mParticipantHelper.queryParticipantWithId(uri, projection);
+ case PARTICIPANT_ALIAS_CHANGE_EVENT:
+ Log.e(TAG, "Querying individual event types is not supported, uri: " + uri);
break;
+ case PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID:
+ Log.e(TAG, "Querying participant events with id's is not supported, uri: " + uri);
+ break;
+ case P2P_THREAD:
+ return mThreadHelper.query1to1Thread(projection, selection,
+ selectionArgs, sortOrder);
+ case P2P_THREAD_WITH_ID:
+ return mThreadHelper.query1To1ThreadUsingId(uri, projection);
+ case P2P_THREAD_PARTICIPANT:
+ return mParticipantHelper.queryParticipantIn1To1Thread(uri);
+ case P2P_THREAD_PARTICIPANT_WITH_ID:
+ Log.e(TAG,
+ "Querying participants in 1 to 1 threads via id's is not supported, uri: "
+ + uri);
+ break;
+ case GROUP_THREAD:
+ return mThreadHelper.queryGroupThread(projection, selection,
+ selectionArgs, sortOrder);
+ case GROUP_THREAD_WITH_ID:
+ return mThreadHelper.queryGroupThreadUsingId(uri, projection);
+ case GROUP_THREAD_PARTICIPANT:
+ return mParticipantHelper.queryParticipantsInGroupThread(uri);
+ case GROUP_THREAD_PARTICIPANT_WITH_ID:
+ return mParticipantHelper.queryParticipantInGroupThreadWithId(uri);
+ case GROUP_THREAD_PARTICIPANT_JOINED_EVENT:
+ case GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID:
+ case GROUP_THREAD_PARTICIPANT_LEFT_EVENT:
+ case GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID:
+ case GROUP_THREAD_NAME_CHANGE_EVENT:
+ case GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID:
+ case GROUP_THREAD_ICON_CHANGE_EVENT:
+ case GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID:
+ Log.e(TAG, "Querying individual event types is not supported, uri: " + uri);
+ break;
+ case UNIFIED_MESSAGE:
+ return mMessageHelper.queryMessages(queryArgs);
+ case UNIFIED_MESSAGE_WITH_ID:
+ return mMessageHelper.queryUnifiedMessageWithId(uri);
+ case UNIFIED_MESSAGE_WITH_FILE_TRANSFER:
+ Log.e(TAG,
+ "Querying file transfers through messages is not supported, uri: " + uri);
+ case INCOMING_MESSAGE:
+ return mMessageHelper.queryIncomingMessageWithSelection(selection, selectionArgs);
+ case INCOMING_MESSAGE_WITH_ID:
+ return mMessageHelper.queryIncomingMessageWithId(uri);
+ case OUTGOING_MESSAGE:
+ return mMessageHelper.queryOutgoingMessageWithSelection(selection, selectionArgs);
+ case OUTGOING_MESSAGE_WITH_ID:
+ return mMessageHelper.queryOutgoingMessageWithId(uri);
+ case OUTGOING_MESSAGE_DELIVERY:
+ return mMessageHelper.queryOutgoingMessageDeliveries(uri);
+ case OUTGOING_MESSAGE_DELIVERY_WITH_ID:
+ Log.e(TAG,
+ "Querying deliveries with message and participant ids is not supported, "
+ + "uri: "
+ + uri);
+ case UNIFIED_MESSAGE_ON_THREAD:
+ return mMessageHelper.queryAllMessagesOnThread(uri, selection, selectionArgs);
+ case UNIFIED_MESSAGE_ON_THREAD_WITH_ID:
+ return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+ case INCOMING_MESSAGE_ON_P2P_THREAD:
+ Log.e(TAG,
+ "Querying incoming messages on P2P thread with selection is not "
+ + "supported, uri: "
+ + uri);
+ break;
+ case INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+ return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+ case OUTGOING_MESSAGE_ON_P2P_THREAD:
+ Log.e(TAG,
+ "Querying outgoing messages on P2P thread with selection is not "
+ + "supported, uri: "
+ + uri);
+ break;
+ case OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+ return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+ case INCOMING_MESSAGE_ON_GROUP_THREAD:
+ Log.e(TAG,
+ "Querying incoming messages on group thread with selection is not "
+ + "supported, uri: "
+ + uri);
+ break;
+ case INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+ return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+ case OUTGOING_MESSAGE_ON_GROUP_THREAD:
+ Log.e(TAG,
+ "Querying outgoing messages on group thread with selection is not "
+ + "supported, uri: "
+ + uri);
+ break;
+ case OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+ return mMessageHelper.queryUnifiedMessageWithIdInThread(uri);
+ case FILE_TRANSFER_WITH_ID:
+ return mMessageHelper.queryFileTransfer(uri);
+ case EVENT:
+ return mEventHelper.queryEvents(queryArgs);
default:
Log.e(TAG, "Invalid query: " + uri);
}
- return qb.query(
- readableDatabase, projection, selection, selectionArgs, null, null, sortOrder);
+ return null;
}
@Override
@@ -87,86 +460,334 @@
}
@Override
- public Uri insert(Uri uri, ContentValues values) {
+ public synchronized Uri insert(Uri uri, ContentValues values) {
int match = URL_MATCHER.match(uri);
- SQLiteDatabase writableDatabase = mDbOpenHelper.getWritableDatabase();
- writableDatabase.beginTransaction();
-
- Uri returnUri = null;
long rowId;
- try {
- switch (match) {
- case THREAD:
- rowId = RcsProviderThreadHelper.insert(writableDatabase, values);
- returnUri = THREAD_URI_PREFIX.buildUpon().appendPath(
- Long.toString(rowId)).build();
- break;
- case PARTICIPANT:
- rowId = RcsProviderParticipantHelper.insert(writableDatabase, values);
- returnUri = PARTICIPANT_URI_PREFIX.buildUpon().appendPath(
- Long.toString(rowId)).build();
- break;
- default:
- Log.e(TAG, "Invalid insert: " + uri);
- }
- } finally {
- writableDatabase.endTransaction();
+ switch (match) {
+ case UNIFIED_RCS_THREAD:
+ case UNIFIED_RCS_THREAD_WITH_ID:
+ Log.e(TAG, "Inserting into unified thread view is not supported, uri: " + uri);
+ break;
+ case PARTICIPANT:
+ return buildUriWithRowIdAppended(PARTICIPANT_URI_PREFIX,
+ mParticipantHelper.insertParticipant(values));
+ case PARTICIPANT_WITH_ID:
+ Log.e(TAG, "Inserting participant with a specified ID is not supported, uri: "
+ + uri);
+ break;
+ case PARTICIPANT_ALIAS_CHANGE_EVENT:
+ return buildUriWithRowIdAppended(uri,
+ mEventHelper.insertParticipantEvent(uri, values));
+ case PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID:
+ Log.e(TAG, "Inserting participant events with id's is not supported, uri: " + uri);
+ break;
+ case P2P_THREAD:
+ return buildUriWithRowIdAppended(P2P_THREAD_URI_PREFIX,
+ mThreadHelper.insert1To1Thread(values));
+ case P2P_THREAD_WITH_ID:
+ Log.e(TAG, "Inserting a thread with a specified ID is not supported, uri: " + uri);
+ break;
+ case P2P_THREAD_PARTICIPANT:
+ Log.e(TAG,
+ "Inserting a participant into a thread via content values is not "
+ + "supported, uri: "
+ + uri);
+ break;
+ case P2P_THREAD_PARTICIPANT_WITH_ID:
+ return returnUriAsIsIfSuccessful(uri,
+ mParticipantHelper.insertParticipantIntoP2pThread(uri));
+ case GROUP_THREAD:
+ return buildUriWithRowIdAppended(GROUP_THREAD_URI_PREFIX,
+ mThreadHelper.insertGroupThread(values));
+ case GROUP_THREAD_WITH_ID:
+ Log.e(TAG, "Inserting a thread with a specified ID is not supported, uri: " + uri);
+ break;
+ case GROUP_THREAD_PARTICIPANT_JOINED_EVENT:
+ return buildUriWithRowIdAppended(uri,
+ mEventHelper.insertParticipantJoinedEvent(uri, values));
+ case GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID:
+ Log.e(TAG, "Inserting thread events with id's is not supported, uri: " + uri);
+ break;
+ case GROUP_THREAD_PARTICIPANT_LEFT_EVENT:
+ return buildUriWithRowIdAppended(uri,
+ mEventHelper.insertParticipantLeftEvent(uri, values));
+ case GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID:
+ Log.e(TAG, "Inserting thread events with id's is not supported, uri: " + uri);
+ break;
+ case GROUP_THREAD_NAME_CHANGE_EVENT:
+ return buildUriWithRowIdAppended(uri,
+ mEventHelper.insertThreadNameChangeEvent(uri, values));
+ case GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID:
+ Log.e(TAG, "Inserting thread events with id's is not supported, uri: " + uri);
+ break;
+ case GROUP_THREAD_ICON_CHANGE_EVENT:
+ return buildUriWithRowIdAppended(uri,
+ mEventHelper.insertThreadIconChangeEvent(uri, values));
+ case GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID:
+ Log.e(TAG, "Inserting thread events with id's is not supported, uri: " + uri);
+ break;
+ case GROUP_THREAD_PARTICIPANT:
+ rowId = mParticipantHelper.insertParticipantIntoGroupThread(values);
+ if (rowId == TRANSACTION_FAILED) {
+ return null;
+ }
+ return mParticipantHelper.getParticipantInThreadUri(values, rowId);
+ case GROUP_THREAD_PARTICIPANT_WITH_ID:
+ return returnUriAsIsIfSuccessful(uri,
+ mParticipantHelper.insertParticipantIntoGroupThreadWithId(uri));
+ case UNIFIED_MESSAGE:
+ case UNIFIED_MESSAGE_WITH_ID:
+ Log.e(TAG, "Inserting into unified message view is not supported, uri: " + uri);
+ break;
+ case UNIFIED_MESSAGE_WITH_FILE_TRANSFER:
+ return buildUriWithRowIdAppended(FILE_TRANSFER_PREFIX,
+ mMessageHelper.insertFileTransferToMessage(uri, values));
+ case INCOMING_MESSAGE:
+ case INCOMING_MESSAGE_WITH_ID:
+ case OUTGOING_MESSAGE:
+ case OUTGOING_MESSAGE_WITH_ID:
+ Log.e(TAG, "Inserting a message without a thread is not supported, uri: "
+ + uri);
+ break;
+ case OUTGOING_MESSAGE_DELIVERY:
+ Log.e(TAG,
+ "Inserting an outgoing message delivery without a participant is not "
+ + "supported, uri: "
+ + uri);
+ break;
+ case OUTGOING_MESSAGE_DELIVERY_WITH_ID:
+ return returnUriAsIsIfSuccessful(uri,
+ mMessageHelper.insertMessageDelivery(uri, values));
+ case UNIFIED_MESSAGE_ON_THREAD:
+ case UNIFIED_MESSAGE_ON_THREAD_WITH_ID:
+ Log.e(TAG,
+ "Inserting a message on unified thread view is not supported, uri: " + uri);
+ break;
+ case INCOMING_MESSAGE_ON_P2P_THREAD:
+ return mMessageHelper.insertMessageOnThread(uri, values, /* isIncoming= */
+ true, /* is1To1 */ true);
+ case OUTGOING_MESSAGE_ON_P2P_THREAD:
+ return mMessageHelper.insertMessageOnThread(uri, values, /* isIncoming= */
+ false, /* is1To1 */ true);
+ case INCOMING_MESSAGE_ON_GROUP_THREAD:
+ return mMessageHelper.insertMessageOnThread(uri, values, /* isIncoming= */
+ true, /* is1To1 */ false);
+ case OUTGOING_MESSAGE_ON_GROUP_THREAD:
+ return mMessageHelper.insertMessageOnThread(uri, values, /* isIncoming= */
+ false, /* is1To1 */ false);
+ case INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+ case OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+ case INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+ case OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+ Log.e(TAG, "Inserting a message with a specific id is not supported, uri: " + uri);
+ break;
+ case FILE_TRANSFER_WITH_ID:
+ Log.e(TAG, "Inserting a file transfer without a message is not supported, uri: "
+ + uri);
+ break;
+ case EVENT:
+ Log.e(TAG,
+ "Inserting event using unified event query is not supported, uri: " + uri);
+ break;
+ default:
+ Log.e(TAG, "Invalid insert: " + uri);
}
- return returnUri;
+ return null;
}
@Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
+ public synchronized int delete(Uri uri, String selection, String[] selectionArgs) {
int match = URL_MATCHER.match(uri);
int deletedCount = 0;
- SQLiteDatabase writableDatabase = mDbOpenHelper.getWritableDatabase();
- writableDatabase.beginTransaction();
- try {
- switch (match) {
- case THREAD:
- deletedCount = RcsProviderThreadHelper.delete(writableDatabase, selection,
- selectionArgs);
- break;
- case PARTICIPANT:
- deletedCount = RcsProviderParticipantHelper.delete(writableDatabase, selection,
- selectionArgs);
- break;
- default:
- Log.e(TAG, "Invalid delete: " + uri);
- }
- } finally {
- writableDatabase.endTransaction();
+ switch (match) {
+ case UNIFIED_RCS_THREAD:
+ case UNIFIED_RCS_THREAD_WITH_ID:
+ Log.e(TAG, "Deleting entries from unified view is not allowed: " + uri);
+ break;
+ case PARTICIPANT:
+ Log.e(TAG, "Deleting participant with selection is not allowed: " + uri);
+ break;
+ case PARTICIPANT_WITH_ID:
+ return mParticipantHelper.deleteParticipantWithId(uri);
+ case PARTICIPANT_ALIAS_CHANGE_EVENT:
+ Log.e(TAG, "Deleting participant events without id is not allowed: " + uri);
+ break;
+ case PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID:
+ return mEventHelper.deleteParticipantEvent(uri);
+ case P2P_THREAD:
+ return mThreadHelper.delete1To1Thread(selection, selectionArgs);
+ case P2P_THREAD_WITH_ID:
+ return mThreadHelper.delete1To1ThreadWithId(uri);
+ case P2P_THREAD_PARTICIPANT:
+ Log.e(TAG, "Removing participant from 1 to 1 thread is not allowed, uri: " + uri);
+ break;
+ case GROUP_THREAD:
+ return mThreadHelper.deleteGroupThread(selection, selectionArgs);
+ case GROUP_THREAD_WITH_ID:
+ return mThreadHelper.deleteGroupThreadWithId(uri);
+ case GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID:
+ return mEventHelper.deleteGroupThreadEvent(uri);
+ case GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID:
+ return mEventHelper.deleteGroupThreadEvent(uri);
+ case GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID:
+ return mEventHelper.deleteGroupThreadEvent(uri);
+ case GROUP_THREAD_PARTICIPANT_JOINED_EVENT:
+ case GROUP_THREAD_PARTICIPANT_LEFT_EVENT:
+ case GROUP_THREAD_NAME_CHANGE_EVENT:
+ case GROUP_THREAD_ICON_CHANGE_EVENT:
+ Log.e(TAG, "Deleting thread events via selection is not allowed, uri: " + uri);
+ break;
+ case GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID:
+ return mEventHelper.deleteGroupThreadEvent(uri);
+ case GROUP_THREAD_PARTICIPANT:
+ Log.e(TAG,
+ "Deleting a participant from group thread via selection is not allowed, "
+ + "uri: "
+ + uri);
+ break;
+ case GROUP_THREAD_PARTICIPANT_WITH_ID:
+ return mParticipantHelper.deleteParticipantFromGroupThread(uri);
+ case UNIFIED_MESSAGE:
+ Log.e(TAG,
+ "Deleting message from unified view with selection is not allowed: " + uri);
+ break;
+ case UNIFIED_MESSAGE_WITH_ID:
+ Log.e(TAG, "Deleting message from unified view with id is not allowed: " + uri);
+ break;
+ case UNIFIED_MESSAGE_WITH_FILE_TRANSFER:
+ Log.e(TAG, "Deleting file transfer using message uri is not allowed, uri: " + uri);
+ break;
+ case INCOMING_MESSAGE:
+ return mMessageHelper.deleteIncomingMessageWithSelection(selection, selectionArgs);
+ case INCOMING_MESSAGE_WITH_ID:
+ return mMessageHelper.deleteIncomingMessageWithId(uri);
+ case OUTGOING_MESSAGE:
+ return mMessageHelper.deleteOutgoingMessageWithSelection(selection, selectionArgs);
+ case OUTGOING_MESSAGE_WITH_ID:
+ return mMessageHelper.deleteOutgoingMessageWithId(uri);
+ case OUTGOING_MESSAGE_DELIVERY:
+ case OUTGOING_MESSAGE_DELIVERY_WITH_ID:
+ Log.e(TAG, "Deleting message deliveries is not supported, uri: " + uri);
+ break;
+ case UNIFIED_MESSAGE_ON_THREAD:
+ case UNIFIED_MESSAGE_ON_THREAD_WITH_ID:
+ case INCOMING_MESSAGE_ON_P2P_THREAD:
+ case INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+ case OUTGOING_MESSAGE_ON_P2P_THREAD:
+ case OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+ case INCOMING_MESSAGE_ON_GROUP_THREAD:
+ case INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+ case OUTGOING_MESSAGE_ON_GROUP_THREAD:
+ case OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+ Log.e(TAG, "Deleting messages using thread uris is not supported, uri: " + uri);
+ break;
+ case FILE_TRANSFER_WITH_ID:
+ return mMessageHelper.deleteFileTransfer(uri);
+ case EVENT:
+ Log.e(TAG, "Deleting events using unified event uri is not supported, uri: " + uri);
+ break;
+ default:
+ Log.e(TAG, "Invalid delete: " + uri);
}
return deletedCount;
}
@Override
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ public synchronized int update(Uri uri, ContentValues values, String selection,
+ String[] selectionArgs) {
int match = URL_MATCHER.match(uri);
int updatedCount = 0;
- SQLiteDatabase writableDatabase = mDbOpenHelper.getWritableDatabase();
- writableDatabase.beginTransaction();
- try {
- switch (match) {
- case THREAD:
- updatedCount = RcsProviderThreadHelper.update(
- writableDatabase, values, selection, selectionArgs);
- break;
- case PARTICIPANT:
- updatedCount = RcsProviderParticipantHelper.update(
- writableDatabase, values, selection, selectionArgs);
- break;
- default:
- Log.e(TAG, "Invalid update: " + uri);
- }
- } finally {
- writableDatabase.endTransaction();
+ switch (match) {
+ case UNIFIED_RCS_THREAD:
+ case UNIFIED_RCS_THREAD_WITH_ID:
+ Log.e(TAG, "Updating unified thread view is not supported, uri: " + uri);
+ break;
+ case PARTICIPANT:
+ Log.e(TAG, "Updating participants with selection is not supported, uri: " + uri);
+ break;
+ case PARTICIPANT_WITH_ID:
+ return mParticipantHelper.updateParticipantWithId(values, uri);
+ case PARTICIPANT_ALIAS_CHANGE_EVENT:
+ case PARTICIPANT_ALIAS_CHANGE_EVENT_WITH_ID:
+ Log.e(TAG, "Updating events is not supported, uri: " + uri);
+ break;
+ case P2P_THREAD:
+ return mThreadHelper.update1To1Thread(values, selection, selectionArgs);
+ case P2P_THREAD_WITH_ID:
+ return mThreadHelper.update1To1ThreadWithId(values, uri);
+ case P2P_THREAD_PARTICIPANT:
+ Log.e(TAG, "Updating junction table entries is not supported, uri: " + uri);
+ break;
+ case GROUP_THREAD:
+ return mThreadHelper.updateGroupThread(values, selection, selectionArgs);
+ case GROUP_THREAD_WITH_ID:
+ return mThreadHelper.updateGroupThreadWithId(values, uri);
+ case GROUP_THREAD_PARTICIPANT_JOINED_EVENT:
+ case GROUP_THREAD_PARTICIPANT_JOINED_EVENT_WITH_ID:
+ case GROUP_THREAD_PARTICIPANT_LEFT_EVENT:
+ case GROUP_THREAD_PARTICIPANT_LEFT_EVENT_WITH_ID:
+ case GROUP_THREAD_NAME_CHANGE_EVENT:
+ case GROUP_THREAD_NAME_CHANGE_EVENT_WITH_ID:
+ case GROUP_THREAD_ICON_CHANGE_EVENT:
+ case GROUP_THREAD_ICON_CHANGE_EVENT_WITH_ID:
+ Log.e(TAG, "Updating thread events is not supported, uri: " + uri);
+ break;
+ case GROUP_THREAD_PARTICIPANT:
+ case GROUP_THREAD_PARTICIPANT_WITH_ID:
+ Log.e(TAG, "Updating junction table entries is not supported, uri: " + uri);
+ break;
+ case UNIFIED_MESSAGE:
+ case UNIFIED_MESSAGE_WITH_ID:
+ Log.e(TAG, "Updating unified message view is not supported, uri: " + uri);
+ break;
+ case UNIFIED_MESSAGE_WITH_FILE_TRANSFER:
+ Log.e(TAG,
+ "Updating file transfer using unified message uri is not supported, uri: "
+ + uri);
+ case INCOMING_MESSAGE:
+ Log.e(TAG,
+ "Updating an incoming message via selection is not supported, uri: " + uri);
+ break;
+ case INCOMING_MESSAGE_WITH_ID:
+ return mMessageHelper.updateIncomingMessage(uri, values);
+ case OUTGOING_MESSAGE:
+ Log.e(TAG,
+ "Updating an outgoing message via selection is not supported, uri: " + uri);
+ break;
+ case OUTGOING_MESSAGE_WITH_ID:
+ return mMessageHelper.updateOutgoingMessage(uri, values);
+ case OUTGOING_MESSAGE_DELIVERY:
+ Log.e(TAG, "Updating message deliveries using message uris is not supported, uri: "
+ + uri);
+ break;
+ case OUTGOING_MESSAGE_DELIVERY_WITH_ID:
+ return mMessageHelper.updateDelivery(uri, values);
+ case UNIFIED_MESSAGE_ON_THREAD:
+ case UNIFIED_MESSAGE_ON_THREAD_WITH_ID:
+ case INCOMING_MESSAGE_ON_P2P_THREAD:
+ case INCOMING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+ case OUTGOING_MESSAGE_ON_P2P_THREAD:
+ case OUTGOING_MESSAGE_ON_P2P_THREAD_WITH_ID:
+ case INCOMING_MESSAGE_ON_GROUP_THREAD:
+ case INCOMING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+ case OUTGOING_MESSAGE_ON_GROUP_THREAD:
+ case OUTGOING_MESSAGE_ON_GROUP_THREAD_WITH_ID:
+ Log.e(TAG, "Updating messages using threads uris is not supported, uri: " + uri);
+ break;
+ case FILE_TRANSFER_WITH_ID:
+ return mMessageHelper.updateFileTransfer(uri, values);
+ case EVENT:
+ Log.e(TAG, "Updating events is not supported, uri: " + uri);
+ break;
+ default:
+ Log.e(TAG, "Invalid update: " + uri);
}
+
return updatedCount;
}
}
diff --git a/src/com/android/providers/telephony/RcsProviderEventHelper.java b/src/com/android/providers/telephony/RcsProviderEventHelper.java
new file mode 100644
index 0000000..9d89e28
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProviderEventHelper.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.NEW_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.ICON_CHANGED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.NAME_CHANGED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_LEFT_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.DESTINATION_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.EVENT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.EVENT_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_ICON_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.SOURCE_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+import static android.telephony.ims.RcsEventQueryParameters.ALL_EVENTS;
+import static android.telephony.ims.RcsEventQueryParameters.ALL_GROUP_THREAD_EVENTS;
+import static android.telephony.ims.RcsEventQueryParameters.EVENT_QUERY_PARAMETERS_KEY;
+
+import static android.telephony.ims.RcsQueryContinuationToken.EVENT_QUERY_CONTINUATION_TOKEN_TYPE;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_EVENT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_EVENT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_UNIFIED_EVENT_VIEW;
+import static com.android.providers.telephony.RcsProvider.TAG;
+import static com.android.providers.telephony.RcsProviderThreadHelper.getThreadIdFromUri;
+import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.ims.RcsEventQueryParameters;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Constants and helpers related to events for {@link RcsProvider} to keep the code clean.
+ *
+ * @hide
+ */
+class RcsProviderEventHelper {
+ private static final int PARTICIPANT_INDEX_IN_EVENT_URI = 1;
+ private static final int EVENT_INDEX_IN_EVENT_URI = 3;
+
+ @VisibleForTesting
+ public static void createRcsEventTables(SQLiteDatabase db) {
+ Log.d(TAG, "Creating event tables");
+
+ // Add the event tables
+ db.execSQL("CREATE TABLE " + RCS_THREAD_EVENT_TABLE + "(" + EVENT_ID_COLUMN
+ + " INTEGER PRIMARY KEY AUTOINCREMENT, " + RCS_THREAD_ID_COLUMN + " INTEGER, "
+ + SOURCE_PARTICIPANT_ID_COLUMN + " INTEGER, " + EVENT_TYPE_COLUMN + " INTEGER, "
+ + TIMESTAMP_COLUMN + " INTEGER, " + DESTINATION_PARTICIPANT_ID_COLUMN + " INTEGER, "
+ + NEW_ICON_URI_COLUMN + " TEXT, " + NEW_NAME_COLUMN + " TEXT, " + " FOREIGN KEY ("
+ + RCS_THREAD_ID_COLUMN + ") REFERENCES " + RCS_THREAD_TABLE + " ("
+ + RCS_THREAD_ID_COLUMN + "), FOREIGN KEY (" + SOURCE_PARTICIPANT_ID_COLUMN
+ + ") REFERENCES " + RCS_PARTICIPANT_TABLE + " (" + RCS_PARTICIPANT_ID_COLUMN
+ + "))");
+
+ db.execSQL("CREATE TABLE " + RCS_PARTICIPANT_EVENT_TABLE + "(" + EVENT_ID_COLUMN
+ + " INTEGER PRIMARY KEY AUTOINCREMENT, " + SOURCE_PARTICIPANT_ID_COLUMN +
+ " INTEGER, " + TIMESTAMP_COLUMN + " INTEGER, "
+ + NEW_ALIAS_COLUMN + " TEXT," + " FOREIGN KEY (" + SOURCE_PARTICIPANT_ID_COLUMN
+ + ") REFERENCES " + RCS_PARTICIPANT_TABLE + " (" + RCS_PARTICIPANT_ID_COLUMN
+ + "))");
+
+ // Add the views
+
+ // The following is a unified event view that puts every entry in both tables into one query
+ db.execSQL("CREATE VIEW " + RCS_UNIFIED_EVENT_VIEW + " AS "
+ + "SELECT 1 AS event_type, event_id, originating_participant, "
+ + "origination_timestamp, old_alias, new_alias, NULL as rcs_thread_id, NULL as "
+ + "destination_participant, NULL as old_icon_uri, NULL as new_icon_uri, NULL as "
+ + "old_name, NULL as new_name "
+ + "FROM rcs_participant_event"
+ + " UNION "
+ + "SELECT event_type, event_id, originating_participant, origination_timestamp, "
+ + "NULL as old_alias, NULL as new_alias, rcs_thread_id, destination_participant, "
+ + "old_icon_uri, new_icon_uri, old_name, new_name "
+ + "FROM rcs_thread_event");
+ }
+
+ private final SQLiteOpenHelper mSqLiteOpenHelper;
+
+ Cursor queryEvents(Bundle bundle) {
+ RcsEventQueryParameters queryParameters = null;
+ RcsQueryContinuationToken continuationToken = null;
+
+ if (bundle != null) {
+ queryParameters = bundle.getParcelable(EVENT_QUERY_PARAMETERS_KEY);
+ continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
+ }
+
+ if (continuationToken != null) {
+ return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
+ continuationToken);
+ }
+
+ // if no query parameters were entered, build an empty query parameters object
+ if (queryParameters == null) {
+ queryParameters = new RcsEventQueryParameters.Builder().build();
+ }
+
+ return performInitialQuery(queryParameters);
+ }
+
+ private Cursor performInitialQuery(RcsEventQueryParameters queryParameters) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ StringBuilder rawQuery = new StringBuilder("SELECT * FROM ").append(RCS_UNIFIED_EVENT_VIEW);
+
+ int eventType = queryParameters.getEventType();
+ if (eventType != ALL_EVENTS) {
+ rawQuery.append(" WHERE ").append(EVENT_TYPE_COLUMN);
+ if (eventType == ALL_GROUP_THREAD_EVENTS) {
+ rawQuery.append(" IN (").append(
+ PARTICIPANT_JOINED_EVENT_TYPE).append(", ").append(
+ PARTICIPANT_LEFT_EVENT_TYPE).append(", ").append(
+ ICON_CHANGED_EVENT_TYPE).append(", ").append(
+ NAME_CHANGED_EVENT_TYPE).append(
+ ")");
+ } else {
+ rawQuery.append("=").append(eventType);
+ }
+ }
+
+ rawQuery.append(" ORDER BY ");
+
+ int sortingProperty = queryParameters.getSortingProperty();
+ if (sortingProperty == RcsEventQueryParameters.SORT_BY_TIMESTAMP) {
+ rawQuery.append(TIMESTAMP_COLUMN);
+ } else {
+ rawQuery.append(EVENT_ID_COLUMN);
+ }
+
+ rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
+
+ RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
+ String rawQueryAsString = rawQuery.toString();
+ Cursor cursor = db.rawQuery(rawQueryAsString, null);
+
+ // if the query was paginated, build the next query
+ int limit = queryParameters.getLimit();
+ if (limit > 0) {
+ RcsProviderUtil.createContinuationTokenBundle(cursor,
+ new RcsQueryContinuationToken(EVENT_QUERY_CONTINUATION_TOKEN_TYPE,
+ rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
+ }
+
+ return cursor;
+ }
+
+ RcsProviderEventHelper(SQLiteOpenHelper sqLiteOpenHelper) {
+ mSqLiteOpenHelper = sqLiteOpenHelper;
+ }
+
+ long insertParticipantEvent(Uri uri, ContentValues values) {
+ String participantId = getParticipantIdFromUri(uri);
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ values.put(SOURCE_PARTICIPANT_ID_COLUMN, participantId);
+ long rowId = db.insert(RCS_PARTICIPANT_EVENT_TABLE, SOURCE_PARTICIPANT_ID_COLUMN, values);
+ values.remove(SOURCE_PARTICIPANT_ID_COLUMN);
+
+ if (rowId == INSERTION_FAILED) {
+ return TRANSACTION_FAILED;
+ }
+
+ return rowId;
+ }
+
+ long insertParticipantJoinedEvent(Uri uri, ContentValues values) {
+ return insertGroupThreadEvent(uri, values, PARTICIPANT_JOINED_EVENT_TYPE);
+ }
+
+ long insertParticipantLeftEvent(Uri uri, ContentValues values) {
+ return insertGroupThreadEvent(uri, values, PARTICIPANT_LEFT_EVENT_TYPE);
+ }
+
+ long insertThreadNameChangeEvent(Uri uri, ContentValues values) {
+ return insertGroupThreadEvent(uri, values, NAME_CHANGED_EVENT_TYPE);
+ }
+
+ long insertThreadIconChangeEvent(Uri uri, ContentValues values) {
+ return insertGroupThreadEvent(uri, values, ICON_CHANGED_EVENT_TYPE);
+ }
+
+ private long insertGroupThreadEvent(Uri uri, ContentValues valuesParameter,
+ int eventType) {
+ String threadId = getThreadIdFromUri(uri);
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ ContentValues values = new ContentValues(valuesParameter);
+ values.put(EVENT_TYPE_COLUMN, eventType);
+ values.put(RCS_THREAD_ID_COLUMN, threadId);
+ long rowId = db.insert(RCS_THREAD_EVENT_TABLE, EVENT_ID_COLUMN, values);
+
+ if (rowId == INSERTION_FAILED) {
+ return TRANSACTION_FAILED;
+ }
+
+ return rowId;
+ }
+
+ int deleteParticipantEvent(Uri uri) {
+ String eventId = getEventIdFromEventUri(uri);
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+ return db.delete(RCS_PARTICIPANT_EVENT_TABLE, EVENT_ID_COLUMN + "=?",
+ new String[]{eventId});
+ }
+
+ int deleteGroupThreadEvent(Uri uri) {
+ String eventId = getEventIdFromEventUri(uri);
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+ return db.delete(RCS_THREAD_EVENT_TABLE, EVENT_ID_COLUMN + "=?", new String[]{eventId});
+ }
+
+ private String getEventIdFromEventUri(Uri uri) {
+ return uri.getPathSegments().get(EVENT_INDEX_IN_EVENT_URI);
+ }
+
+ private String getParticipantIdFromUri(Uri uri) {
+ return uri.getPathSegments().get(PARTICIPANT_INDEX_IN_EVENT_URI);
+ }
+}
diff --git a/src/com/android/providers/telephony/RcsProviderMessageHelper.java b/src/com/android/providers/telephony/RcsProviderMessageHelper.java
new file mode 100644
index 0000000..ba89e8c
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProviderMessageHelper.java
@@ -0,0 +1,707 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.CONTENT_AND_AUTHORITY;
+import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.RCS_1_TO_1_THREAD_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.CONTENT_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_SIZE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_TRANSFER_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.HEIGHT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.DURATION_MILLIS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.PREVIEW_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SESSION_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SUCCESSFULLY_TRANSFERRED_BYTES;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.TRANSFER_STATUS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.WIDTH_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.RCS_GROUP_THREAD_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.ARRIVAL_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.INCOMING_MESSAGE_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.SENDER_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.GLOBAL_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.LATITUDE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.LONGITUDE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TEXT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.STATUS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.SUB_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns.DELIVERED_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsOutgoingMessageColumns.OUTGOING_MESSAGE_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_INCOMING;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_OUTGOING;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.UNIFIED_INCOMING_MESSAGE_VIEW;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.UNIFIED_OUTGOING_MESSAGE_VIEW;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+import static android.telephony.ims.RcsMessageQueryParameters.MESSAGE_QUERY_PARAMETERS_KEY;
+import static android.telephony.ims.RcsMessageQueryParameters.THREAD_ID_NOT_SET;
+
+import static android.telephony.ims.RcsQueryContinuationToken.MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static com.android.providers.telephony.RcsProvider.RCS_FILE_TRANSFER_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_INCOMING_MESSAGE_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_DELIVERY_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_OUTGOING_MESSAGE_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_THREAD_JUNCTION_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.TAG;
+import static com.android.providers.telephony.RcsProvider.UNIFIED_MESSAGE_VIEW;
+import static com.android.providers.telephony.RcsProviderThreadHelper.getThreadIdFromUri;
+import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns;
+import android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns;
+import android.telephony.ims.RcsMessageQueryParameters;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Constants and helpers related to messages for {@link RcsProvider} to keep the code clean.
+ *
+ * @hide
+ */
+public class RcsProviderMessageHelper {
+ private static final int MESSAGE_ID_INDEX_IN_URI = 1;
+ private static final int MESSAGE_ID_INDEX_IN_THREAD_URI = 3;
+
+ private final SQLiteOpenHelper mSqLiteOpenHelper;
+
+ @VisibleForTesting
+ public static void createRcsMessageTables(SQLiteDatabase db) {
+ Log.d(TAG, "Creating message tables");
+
+ // Add the message tables
+ db.execSQL("CREATE TABLE " + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
+ + " INTEGER PRIMARY KEY AUTOINCREMENT, " + RCS_THREAD_ID_COLUMN + " INTEGER, "
+ + GLOBAL_ID_COLUMN + " TEXT, " + SUB_ID_COLUMN + " INTEGER, " + MESSAGE_TEXT_COLUMN
+ + " TEXT," + LATITUDE_COLUMN + " REAL, " + LONGITUDE_COLUMN + " REAL, "
+ + STATUS_COLUMN + " INTEGER, " + ORIGINATION_TIMESTAMP_COLUMN
+ + " INTEGER, FOREIGN KEY(" + RCS_THREAD_ID_COLUMN + ") REFERENCES "
+ + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + "))");
+
+ db.execSQL("CREATE TABLE " + RCS_INCOMING_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
+ + " INTEGER PRIMARY KEY, " + SENDER_PARTICIPANT_ID_COLUMN + " INTEGER, "
+ + ARRIVAL_TIMESTAMP_COLUMN + " INTEGER, "
+ + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN + " INTEGER, FOREIGN KEY ("
+ + MESSAGE_ID_COLUMN + ") REFERENCES " + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
+ + "))");
+
+ db.execSQL("CREATE TABLE " + RCS_OUTGOING_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN
+ + " INTEGER PRIMARY KEY, FOREIGN KEY (" + MESSAGE_ID_COLUMN + ") REFERENCES "
+ + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN + "))");
+
+ db.execSQL("CREATE TABLE " + RCS_MESSAGE_DELIVERY_TABLE + "(" + MESSAGE_ID_COLUMN
+ + " INTEGER, " + RCS_PARTICIPANT_ID_COLUMN + " INTEGER, "
+ + DELIVERED_TIMESTAMP_COLUMN + " INTEGER, "
+ + RcsMessageDeliveryColumns.SEEN_TIMESTAMP_COLUMN + " INTEGER, "
+ + "CONSTRAINT message_delivery PRIMARY KEY (" + MESSAGE_ID_COLUMN + ", "
+ + RCS_PARTICIPANT_ID_COLUMN + "), FOREIGN KEY (" + MESSAGE_ID_COLUMN
+ + ") REFERENCES " + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN + "), FOREIGN KEY ("
+ + RCS_PARTICIPANT_ID_COLUMN + ") REFERENCES " + RCS_PARTICIPANT_TABLE + "("
+ + RCS_PARTICIPANT_ID_COLUMN + "))");
+
+ db.execSQL("CREATE TABLE " + RCS_FILE_TRANSFER_TABLE + " (" + FILE_TRANSFER_ID_COLUMN
+ + " INTEGER PRIMARY KEY AUTOINCREMENT, " + MESSAGE_ID_COLUMN + " INTEGER, "
+ + SESSION_ID_COLUMN + " TEXT, " + CONTENT_URI_COLUMN + " TEXT, "
+ + CONTENT_TYPE_COLUMN + " TEXT, " + FILE_SIZE_COLUMN + " INTEGER, "
+ + SUCCESSFULLY_TRANSFERRED_BYTES + " INTEGER, " + TRANSFER_STATUS_COLUMN +
+ " INTEGER, " + WIDTH_COLUMN + " INTEGER, " + HEIGHT_COLUMN + " INTEGER, "
+ + DURATION_MILLIS_COLUMN + " INTEGER, " + PREVIEW_URI_COLUMN + " TEXT, "
+ + PREVIEW_TYPE_COLUMN + " TEXT, FOREIGN KEY (" + MESSAGE_ID_COLUMN + ") REFERENCES "
+ + RCS_MESSAGE_TABLE + "(" + MESSAGE_ID_COLUMN + "))");
+
+ // Add the views
+ //
+ // The following view inner joins incoming messages with all messages, inner joins outgoing
+ // messages with all messages, and unions them together, while also adding an is_incoming
+ // column for easily telling where the record came from. This may have been achieved with
+ // an outer join but SQLite doesn't support them.
+ //
+ // CREATE VIEW unified_message_view AS
+ //
+ // SELECT rcs_message.rcs_message_row_id,
+ // rcs_message.rcs_thread_id,
+ // rcs_message.rcs_message_global_id,
+ // rcs_message.sub_id,
+ // rcs_message.status,
+ // rcs_message.origination_timestamp,
+ // rcs_message.rcs_text,
+ // rcs_message.latitude,
+ // rcs_message.longitude,
+ // 0 AS sender_participant,
+ // 0 AS arrival_timestamp,
+ // 0 AS seen_timestamp,
+ // outgoing AS message_type
+ //
+ // FROM rcs_message INNER JOIN rcs_outgoing_message
+ // ON rcs_message.rcs_message_row_id=rcs_outgoing_message.rcs_message_row_id
+ //
+ // UNION
+ //
+ // SELECT rcs_message.rcs_message_row_id,
+ // rcs_message.rcs_thread_id,
+ // rcs_message.rcs_message_global_id,
+ // rcs_message.sub_id,
+ // rcs_message.status,
+ // rcs_message.origination_timestamp,
+ // rcs_message.rcs_text,
+ // rcs_message.latitude,
+ // rcs_message.longitude,
+ // rcs_incoming_message.sender_participant,
+ // rcs_incoming_message.arrival_timestamp,
+ // rcs_incoming_message.seen_timestamp,
+ // incoming AS message_type
+ //
+ // FROM rcs_message INNER JOIN rcs_incoming_message
+ // ON rcs_message.rcs_message_row_id=rcs_incoming_message.rcs_message_row_id
+ //
+ db.execSQL("CREATE VIEW " + UNIFIED_MESSAGE_VIEW + " AS SELECT "
+ + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN + ", "
+ + "0 AS " + SENDER_PARTICIPANT_ID_COLUMN + ", "
+ + "0 AS " + ARRIVAL_TIMESTAMP_COLUMN + ", "
+ + "0 AS " + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN + ", "
+ + MESSAGE_TYPE_OUTGOING + " AS " + MESSAGE_TYPE_COLUMN
+ + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_OUTGOING_MESSAGE_TABLE
+ + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
+ + RCS_OUTGOING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN
+ + " UNION SELECT "
+ + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN + ", "
+ + RCS_INCOMING_MESSAGE_TABLE + "." + SENDER_PARTICIPANT_ID_COLUMN + ", "
+ + RCS_INCOMING_MESSAGE_TABLE + "." + ARRIVAL_TIMESTAMP_COLUMN + ", "
+ + RCS_INCOMING_MESSAGE_TABLE + "." + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN
+ + ", "
+ + MESSAGE_TYPE_INCOMING + " AS " + MESSAGE_TYPE_COLUMN
+ + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_INCOMING_MESSAGE_TABLE
+ + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
+ + RCS_INCOMING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN);
+
+ // The following view inner joins incoming messages with all messages
+ //
+ // CREATE VIEW unified_incoming_message_view AS
+ //
+ // SELECT rcs_message.rcs_message_row_id,
+ // rcs_message.rcs_thread_id,
+ // rcs_message.rcs_message_global_id,
+ // rcs_message.sub_id,
+ // rcs_message.status,
+ // rcs_message.origination_timestamp,
+ // rcs_message.rcs_text,
+ // rcs_message.latitude,
+ // rcs_message.longitude,
+ // rcs_incoming_message.sender_participant,
+ // rcs_incoming_message.arrival_timestamp,
+ // rcs_incoming_message.seen_timestamp,
+ //
+ // FROM rcs_message INNER JOIN rcs_incoming_message
+ // ON rcs_message.rcs_message_row_id=rcs_incoming_message.rcs_message_row_id
+
+ db.execSQL("CREATE VIEW " + UNIFIED_INCOMING_MESSAGE_VIEW + " AS SELECT "
+ + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN + ", "
+ + RCS_INCOMING_MESSAGE_TABLE + "." + SENDER_PARTICIPANT_ID_COLUMN + ", "
+ + RCS_INCOMING_MESSAGE_TABLE + "." + ARRIVAL_TIMESTAMP_COLUMN + ", "
+ + RCS_INCOMING_MESSAGE_TABLE + "." + RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN
+ + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_INCOMING_MESSAGE_TABLE
+ + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
+ + RCS_INCOMING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN);
+
+ // The following view inner joins outgoing messages with all messages.
+ //
+ // CREATE VIEW unified_outgoing_message AS
+ //
+ // SELECT rcs_message.rcs_message_row_id,
+ // rcs_message.rcs_thread_id,
+ // rcs_message.rcs_message_global_id,
+ // rcs_message.sub_id,
+ // rcs_message.status,
+ // rcs_message.origination_timestamp
+ // rcs_message.rcs_text,
+ // rcs_message.latitude,
+ // rcs_message.longitude,
+ //
+ // FROM rcs_message INNER JOIN rcs_outgoing_message
+ // ON rcs_message.rcs_message_row_id=rcs_outgoing_message.rcs_message_row_id
+
+ db.execSQL("CREATE VIEW " + UNIFIED_OUTGOING_MESSAGE_VIEW + " AS SELECT "
+ + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + GLOBAL_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + SUB_ID_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + STATUS_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + ORIGINATION_TIMESTAMP_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + MESSAGE_TEXT_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + LATITUDE_COLUMN + ", "
+ + RCS_MESSAGE_TABLE + "." + LONGITUDE_COLUMN
+ + " FROM " + RCS_MESSAGE_TABLE + " INNER JOIN " + RCS_OUTGOING_MESSAGE_TABLE
+ + " ON " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "="
+ + RCS_OUTGOING_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN);
+
+ // Add triggers
+
+ // Delete the corresponding rcs_message row upon deleting a row in rcs_incoming_message
+ //
+ // CREATE TRIGGER delete_common_message_after_incoming
+ // AFTER DELETE ON rcs_incoming_message
+ // BEGIN
+ // DELETE FROM rcs_message WHERE rcs_message.rcs_message_row_id=OLD.rcs_message_row_id;
+ // END
+ db.execSQL("CREATE TRIGGER deleteCommonMessageAfterIncoming AFTER DELETE ON "
+ + RCS_INCOMING_MESSAGE_TABLE + " BEGIN DELETE FROM " + RCS_MESSAGE_TABLE
+ + " WHERE " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "=OLD."
+ + MESSAGE_ID_COLUMN + "; END");
+
+ // Delete the corresponding rcs_message row upon deleting a row in rcs_outgoing_message
+ //
+ // CREATE TRIGGER delete_common_message_after_outgoing
+ // AFTER DELETE ON rcs_outgoing_message
+ // BEGIN
+ // DELETE FROM rcs_message WHERE rcs_message.rcs_message_row_id=OLD.rcs_message_row_id;
+ // END
+ db.execSQL("CREATE TRIGGER deleteCommonMessageAfterOutgoing AFTER DELETE ON "
+ + RCS_OUTGOING_MESSAGE_TABLE + " BEGIN DELETE FROM " + RCS_MESSAGE_TABLE
+ + " WHERE " + RCS_MESSAGE_TABLE + "." + MESSAGE_ID_COLUMN + "=OLD."
+ + MESSAGE_ID_COLUMN + "; END");
+ }
+
+ RcsProviderMessageHelper(SQLiteOpenHelper sqLiteOpenHelper) {
+ mSqLiteOpenHelper = sqLiteOpenHelper;
+ }
+
+ Cursor queryMessages(Bundle bundle) {
+ RcsMessageQueryParameters queryParameters = null;
+ RcsQueryContinuationToken continuationToken = null;
+
+ if (bundle != null) {
+ queryParameters = bundle.getParcelable(MESSAGE_QUERY_PARAMETERS_KEY);
+ continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
+ }
+
+ if (continuationToken != null) {
+ return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
+ continuationToken);
+ }
+
+ // if no parameters were entered, build an empty query parameters object
+ if (queryParameters == null) {
+ queryParameters = new RcsMessageQueryParameters.Builder().build();
+ }
+
+ return performInitialQuery(queryParameters);
+ }
+
+ private Cursor performInitialQuery(RcsMessageQueryParameters queryParameters) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+ StringBuilder rawQuery = new StringBuilder("SELECT * FROM ").append(UNIFIED_MESSAGE_VIEW);
+
+ int messageType = queryParameters.getMessageType();
+ String messageLike = queryParameters.getMessageLike();
+ int threadId = queryParameters.getThreadId();
+
+ boolean isMessageLikePresent = !TextUtils.isEmpty(messageLike);
+ boolean isMessageTypeFiltered = (messageType == MESSAGE_TYPE_INCOMING)
+ || (messageType == MESSAGE_TYPE_OUTGOING);
+ boolean isThreadFiltered = threadId != THREAD_ID_NOT_SET;
+
+ if (isMessageLikePresent || isMessageTypeFiltered || isThreadFiltered) {
+ rawQuery.append(" WHERE ");
+ }
+
+ if (messageType == MESSAGE_TYPE_INCOMING) {
+ rawQuery.append(MESSAGE_TYPE_COLUMN).append("=").append(MESSAGE_TYPE_INCOMING);
+ } else if (messageType == MESSAGE_TYPE_OUTGOING) {
+ rawQuery.append(MESSAGE_TYPE_COLUMN).append("=").append(MESSAGE_TYPE_OUTGOING);
+ }
+
+ if (isMessageLikePresent) {
+ if (isMessageTypeFiltered) {
+ rawQuery.append(" AND ");
+ }
+ rawQuery.append(MESSAGE_TEXT_COLUMN).append(" LIKE \"").append(messageLike)
+ .append("\"");
+ }
+
+ if (isThreadFiltered) {
+ if (isMessageLikePresent || isMessageTypeFiltered) {
+ rawQuery.append(" AND ");
+ }
+ rawQuery.append(RCS_THREAD_ID_COLUMN).append("=").append(threadId);
+ }
+
+ // TODO - figure out a way to see if this message has file transfer or not. Ideally we
+ // should join the unified table with file transfer table, but using a trigger to change a
+ // flag on rcs_message would also work
+
+ rawQuery.append(" ORDER BY ");
+
+ int sortingProperty = queryParameters.getSortingProperty();
+ if (sortingProperty == RcsMessageQueryParameters.SORT_BY_TIMESTAMP) {
+ rawQuery.append(ORIGINATION_TIMESTAMP_COLUMN);
+ } else {
+ rawQuery.append(MESSAGE_ID_COLUMN);
+ }
+
+ rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
+
+ RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
+ String rawQueryAsString = rawQuery.toString();
+ Cursor cursor = db.rawQuery(rawQueryAsString, null);
+
+ // If the query was paginated, build the next query
+ int limit = queryParameters.getLimit();
+ if (limit > 0) {
+ RcsProviderUtil.createContinuationTokenBundle(cursor,
+ new RcsQueryContinuationToken(MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE,
+ rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
+ }
+
+ return cursor;
+ }
+
+ Cursor queryUnifiedMessageWithId(Uri uri) {
+ return queryUnifiedMessageWithSelection(getMessageIdSelection(uri), null);
+ }
+
+ Cursor queryUnifiedMessageWithIdInThread(Uri uri) {
+ return queryUnifiedMessageWithSelection(getMessageIdSelectionInThreadUri(uri), null);
+ }
+
+ Cursor queryUnifiedMessageWithSelection(String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ return db.query(UNIFIED_MESSAGE_VIEW, null, selection, selectionArgs, null, null, null,
+ null);
+ }
+
+ Cursor queryIncomingMessageWithId(Uri uri) {
+ return queryIncomingMessageWithSelection(getMessageIdSelection(uri), null);
+ }
+
+ Cursor queryIncomingMessageWithSelection(String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ return db.query(UNIFIED_INCOMING_MESSAGE_VIEW, null, selection, selectionArgs, null, null,
+ null, null);
+ }
+
+ Cursor queryOutgoingMessageWithId(Uri uri) {
+ return queryOutgoingMessageWithSelection(getMessageIdSelection(uri), null);
+ }
+
+ Cursor queryOutgoingMessageWithSelection(String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ return db.query(UNIFIED_OUTGOING_MESSAGE_VIEW, null, selection, selectionArgs, null, null,
+ null, null);
+ }
+
+ Cursor queryAllMessagesOnThread(Uri uri, String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+ String appendedSelection = appendThreadIdToSelection(uri, selection);
+ return db.query(UNIFIED_MESSAGE_VIEW, null, appendedSelection, null, null, null, null);
+ }
+
+ Uri insertMessageOnThread(Uri uri, ContentValues valuesParameter, boolean isIncoming,
+ boolean is1To1) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+ String threadId = RcsProviderThreadHelper.getThreadIdFromUri(uri);
+ ContentValues values = new ContentValues(valuesParameter);
+ values.put(RCS_THREAD_ID_COLUMN, Integer.parseInt(threadId));
+
+ db.beginTransaction();
+
+ ContentValues subMessageTableValues = new ContentValues();
+ if (isIncoming) {
+ subMessageTableValues = getIncomingMessageValues(values);
+ }
+
+ long rowId;
+ try {
+ rowId = db.insert(RCS_MESSAGE_TABLE, MESSAGE_ID_COLUMN, values);
+ if (rowId == INSERTION_FAILED) {
+ return null;
+ }
+
+ subMessageTableValues.put(MESSAGE_ID_COLUMN, rowId);
+ long tempId = db.insert(
+ isIncoming ? RCS_INCOMING_MESSAGE_TABLE : RCS_OUTGOING_MESSAGE_TABLE,
+ MESSAGE_ID_COLUMN, subMessageTableValues);
+ if (tempId == INSERTION_FAILED) {
+ return null;
+ }
+
+ // if the thread is outgoing, insert message deliveries
+ if (!isIncoming && !insertMessageDeliveriesForOutgoingMessageCreation(db, tempId,
+ threadId)) {
+ return null;
+ }
+
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+
+ values.remove(RCS_THREAD_ID_COLUMN);
+
+ String threadPart = is1To1 ? RCS_1_TO_1_THREAD_URI_PART : RCS_GROUP_THREAD_URI_PART;
+ String messagePart = isIncoming ? INCOMING_MESSAGE_URI_PART : OUTGOING_MESSAGE_URI_PART;
+
+ return CONTENT_AND_AUTHORITY.buildUpon().appendPath(threadPart).appendPath(threadId).
+ appendPath(messagePart).appendPath(Long.toString(rowId)).build();
+ }
+
+ // Tries to insert deliveries for outgoing message, returns false if it fails.
+ private boolean insertMessageDeliveriesForOutgoingMessageCreation(
+ SQLiteDatabase dbInTransaction, long messageId, String threadId) {
+ try (Cursor participantsInThreadCursor = dbInTransaction.query(
+ RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, null, RCS_THREAD_ID_COLUMN + "=?",
+ new String[]{threadId}, null, null, null)) {
+ if (participantsInThreadCursor == null) {
+ return false;
+ }
+
+ while (participantsInThreadCursor.moveToNext()) {
+ String participantId = participantsInThreadCursor.getString(
+ participantsInThreadCursor.getColumnIndex(
+ RCS_PARTICIPANT_ID_COLUMN));
+
+ long insertionRow = insertMessageDelivery(Long.toString(messageId), participantId,
+ new ContentValues());
+
+ if (insertionRow == INSERTION_FAILED) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ long insertMessageDelivery(Uri uri, ContentValues values) {
+ String messageId = getMessageIdFromUri(uri);
+ String participantId = getParticipantIdFromDeliveryUri(uri);
+ return insertMessageDelivery(messageId, participantId, values);
+ }
+
+ private long insertMessageDelivery(String messageId, String participantId,
+ ContentValues valuesParameter) {
+ ContentValues values = new ContentValues(valuesParameter);
+ values.put(MESSAGE_ID_COLUMN, messageId);
+ values.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.insert(RCS_MESSAGE_DELIVERY_TABLE, MESSAGE_ID_COLUMN, values);
+ }
+
+ int updateDelivery(Uri uri, ContentValues contentValues) {
+ String messageId = getMessageIdFromUri(uri);
+ String participantId = getParticipantIdFromDeliveryUri(uri);
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.update(RCS_MESSAGE_DELIVERY_TABLE, contentValues,
+ MESSAGE_ID_COLUMN + "=? AND " + RCS_PARTICIPANT_ID_COLUMN + "=?",
+ new String[]{messageId, participantId});
+ }
+
+ int deleteIncomingMessageWithId(Uri uri) {
+ return deleteIncomingMessageWithSelection(getMessageIdSelection(uri), null);
+ }
+
+ int deleteIncomingMessageWithSelection(String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.delete(RCS_INCOMING_MESSAGE_TABLE, selection, selectionArgs);
+ }
+
+ int deleteOutgoingMessageWithId(Uri uri) {
+ return deleteOutgoingMessageWithSelection(getMessageIdSelection(uri), null);
+ }
+
+ int deleteOutgoingMessageWithSelection(String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.delete(RCS_OUTGOING_MESSAGE_TABLE, selection, selectionArgs);
+ }
+
+ int updateIncomingMessage(Uri uri, ContentValues values) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+ ContentValues incomingMessageValues = getIncomingMessageValues(values);
+
+ int updateCountInIncoming = 0;
+ int updateCountInCommon = 0;
+ db.beginTransaction();
+ if (!incomingMessageValues.isEmpty()) {
+ updateCountInIncoming = db.update(RCS_INCOMING_MESSAGE_TABLE, incomingMessageValues,
+ getMessageIdSelection(uri), null);
+ }
+ if (!values.isEmpty()) {
+ updateCountInCommon = db.update(RCS_MESSAGE_TABLE, values, getMessageIdSelection(uri),
+ null);
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+
+ return Math.max(updateCountInIncoming, updateCountInCommon);
+ }
+
+ int updateOutgoingMessage(Uri uri, ContentValues values) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.update(RCS_MESSAGE_TABLE, values, getMessageIdSelection(uri), null);
+ }
+
+ Cursor queryOutgoingMessageDeliveries(Uri uri) {
+ String messageId = getMessageIdFromUri(uri);
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ return db.query(RCS_MESSAGE_DELIVERY_TABLE, null, MESSAGE_ID_COLUMN + "=?",
+ new String[]{messageId}, null, null, null);
+ }
+
+ Cursor queryFileTransfer(Uri uri) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ return db.query(RCS_FILE_TRANSFER_TABLE, null, FILE_TRANSFER_ID_COLUMN + "=?",
+ new String[]{getFileTransferIdFromUri(uri)}, null, null, null, null);
+ }
+
+ long insertFileTransferToMessage(Uri uri, ContentValues values) {
+ String messageId = getMessageIdFromUri(uri);
+ values.put(MESSAGE_ID_COLUMN, messageId);
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ long rowId = db.insert(RCS_FILE_TRANSFER_TABLE, MESSAGE_ID_COLUMN, values);
+ values.remove(MESSAGE_ID_COLUMN);
+
+ if (rowId == INSERTION_FAILED) {
+ rowId = TRANSACTION_FAILED;
+ }
+
+ return rowId;
+ }
+
+ int deleteFileTransfer(Uri uri) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.delete(RCS_FILE_TRANSFER_TABLE, FILE_TRANSFER_ID_COLUMN + "=?",
+ new String[]{getFileTransferIdFromUri(uri)});
+ }
+
+ int updateFileTransfer(Uri uri, ContentValues values) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.update(RCS_FILE_TRANSFER_TABLE, values,
+ FILE_TRANSFER_ID_COLUMN + "=?", new String[]{getFileTransferIdFromUri(uri)});
+ }
+
+ /**
+ * Removes the incoming message values out of all values and returns as a separate content
+ * values object.
+ */
+ private ContentValues getIncomingMessageValues(ContentValues allValues) {
+ ContentValues incomingMessageValues = new ContentValues();
+
+ if (allValues.containsKey(SENDER_PARTICIPANT_ID_COLUMN)) {
+ incomingMessageValues.put(SENDER_PARTICIPANT_ID_COLUMN,
+ allValues.getAsInteger(SENDER_PARTICIPANT_ID_COLUMN));
+ allValues.remove(SENDER_PARTICIPANT_ID_COLUMN);
+ }
+
+ if (allValues.containsKey(ARRIVAL_TIMESTAMP_COLUMN)) {
+ incomingMessageValues.put(
+ ARRIVAL_TIMESTAMP_COLUMN, allValues.getAsLong(ARRIVAL_TIMESTAMP_COLUMN));
+ allValues.remove(ARRIVAL_TIMESTAMP_COLUMN);
+ }
+
+ if (allValues.containsKey(RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN)) {
+ incomingMessageValues.put(
+ RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN,
+ allValues.getAsLong(RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN));
+ allValues.remove(RcsIncomingMessageColumns.SEEN_TIMESTAMP_COLUMN);
+ }
+
+ return incomingMessageValues;
+ }
+
+ private String appendThreadIdToSelection(Uri uri, String selection) {
+ String threadIdSelection = RCS_THREAD_ID_COLUMN + "=" + getThreadIdFromUri(uri);
+
+ if (TextUtils.isEmpty(selection)) {
+ return threadIdSelection;
+ }
+
+ return "(" + selection + ") AND " + threadIdSelection;
+ }
+
+ private String getMessageIdSelection(Uri uri) {
+ return MESSAGE_ID_COLUMN + "=" + getMessageIdFromUri(uri);
+ }
+
+ private String getMessageIdSelectionInThreadUri(Uri uri) {
+ return MESSAGE_ID_COLUMN + "=" + getMessageIdFromThreadUri(uri);
+ }
+
+ private String getMessageIdFromUri(Uri uri) {
+ return uri.getPathSegments().get(MESSAGE_ID_INDEX_IN_URI);
+ }
+
+ private String getFileTransferIdFromUri(Uri uri) {
+ // this works because messages and file transfer uri's have the same indices.
+ return getMessageIdFromUri(uri);
+ }
+
+ private String getParticipantIdFromDeliveryUri(Uri uri) {
+ // this works because messages in threads and participants in deliveries have the same
+ // indices.
+ return getMessageIdFromThreadUri(uri);
+ }
+
+ private String getMessageIdFromThreadUri(Uri uri) {
+ return uri.getPathSegments().get(MESSAGE_ID_INDEX_IN_THREAD_URI);
+ }
+}
diff --git a/src/com/android/providers/telephony/RcsProviderParticipantHelper.java b/src/com/android/providers/telephony/RcsProviderParticipantHelper.java
index d6ae4f7..dc107a8 100644
--- a/src/com/android/providers/telephony/RcsProviderParticipantHelper.java
+++ b/src/com/android/providers/telephony/RcsProviderParticipantHelper.java
@@ -15,12 +15,37 @@
*/
package com.android.providers.telephony;
-import static com.android.providers.telephony.RcsProviderThreadHelper.RCS_THREAD_ID_COLUMN;
-import static com.android.providers.telephony.RcsProviderThreadHelper.THREAD_TABLE;
+import static android.provider.Telephony.CanonicalAddressesColumns.ADDRESS;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_URI_PART;
+import static android.provider.Telephony.RcsColumns.RcsParticipantHelpers.RCS_PARTICIPANT_WITH_ADDRESS_VIEW;
+import static android.provider.Telephony.RcsColumns.RcsParticipantHelpers.RCS_PARTICIPANT_WITH_THREAD_VIEW;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+import static android.telephony.ims.RcsParticipantQueryParameters.PARTICIPANT_QUERY_PARAMETERS_KEY;
+
+import static android.telephony.ims.RcsQueryContinuationToken.PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static com.android.providers.telephony.RcsProvider.GROUP_THREAD_URI_PREFIX;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_THREAD_JUNCTION_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.TAG;
+import static com.android.providers.telephony.RcsProviderThreadHelper.getThreadIdFromUri;
+import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
import android.content.ContentValues;
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telephony.ims.RcsParticipantQueryParameters;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -29,57 +54,361 @@
*
* @hide
*/
-public class RcsProviderParticipantHelper {
- static final String ID_COLUMN = "_id";
- static final String PARTICIPANT_TABLE = "rcs_participant";
- static final String CANONICAL_ADDRESS_ID_COLUMN = "canonical_address_id";
- static final String RCS_ALIAS_COLUMN = "rcs_alias";
-
- static final String PARTICIPANT_THREAD_JUNCTION_TABLE = "rcs_thread_participant";
- static final String RCS_PARTICIPANT_ID_COLUMN = "rcs_participant_id";
+class RcsProviderParticipantHelper {
+ private static final int PARTICIPANT_ID_INDEX_IN_URI = 1;
+ private static final int PARTICIPANT_ID_INDEX_IN_THREAD_URI = 3;
@VisibleForTesting
public static void createParticipantTables(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + PARTICIPANT_TABLE + " (" +
- ID_COLUMN + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ Log.d(TAG, "Creating participant tables");
+
+ // create participant tables
+ db.execSQL("CREATE TABLE " + RCS_PARTICIPANT_TABLE + " (" +
+ RCS_PARTICIPANT_ID_COLUMN + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
CANONICAL_ADDRESS_ID_COLUMN + " INTEGER ," +
RCS_ALIAS_COLUMN + " TEXT, " +
"FOREIGN KEY(" + CANONICAL_ADDRESS_ID_COLUMN + ") "
- + "REFERENCES canonical_addresses(address)" +
+ + "REFERENCES canonical_addresses(_id)" +
");");
- db.execSQL("CREATE TABLE " + PARTICIPANT_THREAD_JUNCTION_TABLE + " (" +
+ db.execSQL("CREATE TABLE " + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + " (" +
RCS_THREAD_ID_COLUMN + " INTEGER, " +
RCS_PARTICIPANT_ID_COLUMN + " INTEGER, " +
"CONSTRAINT thread_participant PRIMARY KEY("
+ RCS_THREAD_ID_COLUMN + ", " + RCS_PARTICIPANT_ID_COLUMN + "), " +
"FOREIGN KEY(" + RCS_THREAD_ID_COLUMN
- + ") REFERENCES " + THREAD_TABLE + "(" + ID_COLUMN + "), " +
+ + ") REFERENCES " + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + "), " +
"FOREIGN KEY(" + RCS_PARTICIPANT_ID_COLUMN
- + ") REFERENCES " + PARTICIPANT_TABLE + "(" + ID_COLUMN + "))");
+ + ") REFERENCES " + RCS_PARTICIPANT_TABLE + "(" + RCS_PARTICIPANT_ID_COLUMN + "))");
+
+ // create views
+
+ // The following view joins rcs_participant table with canonical_addresses table to add the
+ // actual address of a participant in the result.
+ db.execSQL("CREATE VIEW " + RCS_PARTICIPANT_WITH_ADDRESS_VIEW + " AS SELECT "
+ + "rcs_participant.rcs_participant_id, rcs_participant.canonical_address_id, "
+ + "rcs_participant.rcs_alias, canonical_addresses.address FROM rcs_participant "
+ + "LEFT JOIN canonical_addresses ON "
+ + "rcs_participant.canonical_address_id=canonical_addresses._id");
+
+ // The following view is the rcs_participant_with_address_view above, plus the information
+ // on which threads this participant contributes to, to enable getting participants of a
+ // thread
+ db.execSQL("CREATE VIEW " + RCS_PARTICIPANT_WITH_THREAD_VIEW + " AS SELECT "
+ + "rcs_participant.rcs_participant_id, rcs_participant.canonical_address_id, "
+ + "rcs_participant.rcs_alias, canonical_addresses.address, rcs_thread_participant"
+ + ".rcs_thread_id FROM rcs_participant LEFT JOIN canonical_addresses ON "
+ + "rcs_participant.canonical_address_id=canonical_addresses._id LEFT JOIN "
+ + "rcs_thread_participant ON rcs_participant.rcs_participant_id="
+ + "rcs_thread_participant.rcs_participant_id");
+
+ // TODO - create indexes for faster querying
}
- static void buildParticipantsQuery(SQLiteQueryBuilder qb) {
- qb.setTables(PARTICIPANT_TABLE);
+ private final SQLiteOpenHelper mSqLiteOpenHelper;
+
+ RcsProviderParticipantHelper(SQLiteOpenHelper sqLiteOpenHelper) {
+ mSqLiteOpenHelper = sqLiteOpenHelper;
}
- static long insert(SQLiteDatabase db, ContentValues values) {
- long rowId = db.insert(PARTICIPANT_TABLE, ID_COLUMN, values);
- db.setTransactionSuccessful();
- return rowId;
+ Cursor queryParticipant(Bundle bundle) {
+ RcsParticipantQueryParameters queryParameters = null;
+ RcsQueryContinuationToken continuationToken = null;
+
+ if (bundle != null) {
+ queryParameters = bundle.getParcelable(PARTICIPANT_QUERY_PARAMETERS_KEY);
+ continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
+ }
+
+ if (continuationToken != null) {
+ return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
+ continuationToken);
+ }
+
+ if (queryParameters == null) {
+ queryParameters = new RcsParticipantQueryParameters.Builder().build();
+ }
+
+ return performInitialQuery(queryParameters);
}
- static int delete(SQLiteDatabase db, String selection,
- String[] selectionArgs) {
- int deletedRows = db.delete(PARTICIPANT_TABLE, selection, selectionArgs);
- db.setTransactionSuccessful();
- return deletedRows;
+ private Cursor performInitialQuery(RcsParticipantQueryParameters queryParameters) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+ StringBuilder rawQuery = buildInitialRawQuery(queryParameters);
+ RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
+ String rawQueryAsString = rawQuery.toString();
+ Cursor cursor = db.rawQuery(rawQueryAsString, null);
+
+ // If the query was paginated, build the next query
+ int limit = queryParameters.getLimit();
+ if (limit > 0) {
+ RcsProviderUtil.createContinuationTokenBundle(cursor,
+ new RcsQueryContinuationToken(PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE,
+ rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
+ }
+
+ return cursor;
}
- static int update(SQLiteDatabase db, ContentValues values,
- String selection, String[] selectionArgs) {
- int updatedRows = db.update(PARTICIPANT_TABLE, values, selection, selectionArgs);
- db.setTransactionSuccessful();
- return updatedRows;
+ private StringBuilder buildInitialRawQuery(RcsParticipantQueryParameters queryParameters) {
+ StringBuilder rawQuery = new StringBuilder("SELECT * FROM ");
+
+ boolean isThreadFiltered = queryParameters.getThreadId() > 0;
+
+ if (isThreadFiltered) {
+ rawQuery.append(RCS_PARTICIPANT_WITH_THREAD_VIEW);
+ } else {
+ rawQuery.append(RCS_PARTICIPANT_WITH_ADDRESS_VIEW);
+ }
+
+ boolean isAliasFiltered = !TextUtils.isEmpty(queryParameters.getAliasLike());
+ boolean isCanonicalAddressFiltered = !TextUtils.isEmpty(
+ queryParameters.getCanonicalAddressLike());
+
+ if (isAliasFiltered || isCanonicalAddressFiltered || isThreadFiltered) {
+ rawQuery.append(" WHERE ");
+ }
+
+ if (isAliasFiltered) {
+ rawQuery.append(RCS_ALIAS_COLUMN).append(" LIKE \"").append(
+ queryParameters.getAliasLike()).append("\"");
+ }
+
+ if (isCanonicalAddressFiltered) {
+ if (isAliasFiltered) {
+ rawQuery.append(" AND ");
+ }
+ rawQuery.append(ADDRESS).append(" LIKE \"").append(
+ queryParameters.getCanonicalAddressLike()).append("\"");
+ }
+
+ if (isThreadFiltered) {
+ if (isAliasFiltered || isCanonicalAddressFiltered) {
+ rawQuery.append(" AND ");
+ }
+ rawQuery.append(RCS_THREAD_ID_COLUMN).append("=").append(queryParameters.getThreadId());
+ }
+
+ rawQuery.append(" ORDER BY ");
+
+ int sortingProperty = queryParameters.getSortingProperty();
+ if (sortingProperty == RcsParticipantQueryParameters.SORT_BY_ALIAS) {
+ rawQuery.append(RCS_ALIAS_COLUMN);
+ } else if (sortingProperty == RcsParticipantQueryParameters.SORT_BY_CANONICAL_ADDRESS) {
+ rawQuery.append(ADDRESS);
+ } else {
+ rawQuery.append(RCS_PARTICIPANT_ID_COLUMN);
+ }
+ rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
+
+ return rawQuery;
+ }
+
+ Cursor queryParticipantWithId(Uri uri, String[] projection) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ return db.query(RCS_PARTICIPANT_WITH_ADDRESS_VIEW, projection,
+ getParticipantIdSelection(uri), null, null, null, null);
+ }
+
+ Cursor queryParticipantIn1To1Thread(Uri uri) {
+ String threadId = getThreadIdFromUri(uri);
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+ return db.rawQuery(
+ " SELECT * "
+ + "FROM rcs_participant "
+ + "WHERE rcs_participant.rcs_participant_id = ("
+ + " SELECT rcs_thread_participant.rcs_participant_id "
+ + " FROM rcs_thread_participant "
+ + " WHERE rcs_thread_participant.rcs_thread_id=" + threadId + ")", null);
+ }
+
+ Cursor queryParticipantsInGroupThread(Uri uri) {
+ String threadId = getThreadIdFromUri(uri);
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+ return db.rawQuery(" SELECT * "
+ + "FROM rcs_participant "
+ + "WHERE rcs_participant.rcs_participant_id = ("
+ + " SELECT rcs_participant_id "
+ + " FROM rcs_thread_participant "
+ + " WHERE rcs_thread_id= " + threadId + ")", null);
+ }
+
+ Cursor queryParticipantInGroupThreadWithId(Uri uri) {
+ String threadId = getThreadIdFromUri(uri);
+ String participantId = getParticipantIdFromUri(uri);
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+
+ return db.rawQuery(" SELECT * "
+ + "FROM rcs_participant "
+ + "WHERE rcs_participant.rcs_participant_id = ("
+ + " SELECT rcs_participant_id "
+ + " FROM rcs_thread_participant "
+ + " WHERE rcs_thread_id=? AND rcs_participant_id=?)",
+ new String[]{threadId, participantId});
+ }
+
+ long insertParticipant(ContentValues contentValues) {
+ if (!contentValues.containsKey(CANONICAL_ADDRESS_ID_COLUMN) || TextUtils.isEmpty(
+ contentValues.getAsString(CANONICAL_ADDRESS_ID_COLUMN))) {
+ Log.e(TAG,
+ "RcsProviderParticipantHelper: Inserting participants without canonical "
+ + "address is not supported");
+ return TRANSACTION_FAILED;
+ }
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.insert(RCS_PARTICIPANT_TABLE, RCS_PARTICIPANT_ID_COLUMN, contentValues);
+ }
+
+ long insertParticipantIntoP2pThread(Uri uri) {
+ String threadId = getThreadIdFromUri(uri);
+ String participantId = getParticipantIdFromUri(uri);
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+ Cursor cursor = db.query(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, null,
+ RCS_THREAD_ID_COLUMN + "=? AND " + RCS_PARTICIPANT_ID_COLUMN + "=?",
+ new String[]{threadId, participantId}, null, null, null);
+
+ int existingEntryCount = 0;
+ if (cursor != null) {
+ existingEntryCount = cursor.getCount();
+ cursor.close();
+ }
+
+ // if this 1 to 1 thread already has a participant, fail the transaction.
+ if (existingEntryCount > 0) {
+ return TRANSACTION_FAILED;
+ }
+
+ ContentValues contentValues = new ContentValues(2);
+ contentValues.put(RCS_THREAD_ID_COLUMN, threadId);
+ contentValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
+ long rowId = db.insert(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, RCS_PARTICIPANT_ID_COLUMN,
+ contentValues);
+
+ if (rowId == INSERTION_FAILED) {
+ return TRANSACTION_FAILED;
+ }
+ return Long.parseLong(participantId);
+ }
+
+ /**
+ * Inserts a participant into group thread. This function returns the participant ID instead of
+ * the row id in the junction table
+ */
+ long insertParticipantIntoGroupThread(ContentValues values) {
+ if (!values.containsKey(RCS_THREAD_ID_COLUMN) || !values.containsKey(
+ RCS_PARTICIPANT_ID_COLUMN)) {
+ Log.e(TAG, "RcsProviderParticipantHelper: Cannot insert participant into group.");
+ return TRANSACTION_FAILED;
+ }
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ long insertedRowId = db.insert(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE,
+ RCS_PARTICIPANT_ID_COLUMN,
+ values);
+
+ if (insertedRowId == INSERTION_FAILED) {
+ return TRANSACTION_FAILED;
+ }
+
+ return values.getAsLong(RCS_PARTICIPANT_ID_COLUMN);
+ }
+
+ /**
+ * Inserts a participant into group thread. This function returns the participant ID instead of
+ * the row id in the junction table
+ */
+ long insertParticipantIntoGroupThreadWithId(Uri uri) {
+ String threadId = getThreadIdFromUri(uri);
+ String participantId = getParticipantIdFromUri(uri);
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+ ContentValues contentValues = new ContentValues(2);
+ contentValues.put(RCS_THREAD_ID_COLUMN, threadId);
+ contentValues.put(RCS_PARTICIPANT_ID_COLUMN, participantId);
+
+ long insertedRowId = db.insert(
+ RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, RCS_PARTICIPANT_ID_COLUMN, contentValues);
+
+ if (insertedRowId == INSERTION_FAILED) {
+ return TRANSACTION_FAILED;
+ }
+
+ return Long.parseLong(participantId);
+ }
+
+ int deleteParticipantWithId(Uri uri) {
+ String participantId = uri.getPathSegments().get(PARTICIPANT_ID_INDEX_IN_URI);
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+
+ // See if this participant is involved in any threads
+ Cursor cursor = db.query(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE, null,
+ RCS_PARTICIPANT_ID_COLUMN + "=?", new String[]{participantId}, null, null, null);
+
+ int participatingThreadCount = 0;
+ if (cursor != null) {
+ participatingThreadCount = cursor.getCount();
+ cursor.close();
+ }
+
+ if (participatingThreadCount > 0) {
+ Log.e(TAG,
+ "RcsProviderParticipantHelper: Can't delete participant while it is still in "
+ + "RCS threads, uri:"
+ + uri);
+ return 0;
+ }
+
+ return db.delete(RCS_PARTICIPANT_TABLE, RCS_PARTICIPANT_ID_COLUMN + "=?",
+ new String[]{participantId});
+ }
+
+ int deleteParticipantFromGroupThread(Uri uri) {
+ String threadId = getThreadIdFromUri(uri);
+ String participantId = getParticipantIdFromUri(uri);
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ // TODO check to remove owner
+ return db.delete(RCS_PARTICIPANT_THREAD_JUNCTION_TABLE,
+ RCS_THREAD_ID_COLUMN + "=? AND " + RCS_PARTICIPANT_ID_COLUMN + "=?",
+ new String[]{threadId, participantId});
+ }
+
+ int updateParticipant(ContentValues contentValues, String selection, String[] selectionArgs) {
+ if (contentValues.containsKey(RCS_PARTICIPANT_ID_COLUMN)) {
+ Log.e(TAG, "RcsProviderParticipantHelper: Updating participant id is not supported");
+ return 0;
+ }
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.update(RCS_PARTICIPANT_TABLE, contentValues, selection, selectionArgs);
+ }
+
+ int updateParticipantWithId(ContentValues contentValues, Uri uri) {
+ return updateParticipant(contentValues, getParticipantIdSelection(uri), null);
+ }
+
+ private String getParticipantIdSelection(Uri uri) {
+ return RCS_PARTICIPANT_ID_COLUMN + "=" + uri.getPathSegments().get(
+ PARTICIPANT_ID_INDEX_IN_URI);
+ }
+
+ Uri getParticipantInThreadUri(ContentValues values, long rowId) {
+ if (values == null) {
+ return null;
+ }
+ Integer threadId = values.getAsInteger(RCS_THREAD_ID_COLUMN);
+ if (threadId == null) {
+ return null;
+ }
+
+ return GROUP_THREAD_URI_PREFIX.buildUpon().appendPath(
+ Integer.toString(threadId)).appendPath(RCS_PARTICIPANT_URI_PART).appendPath(
+ Long.toString(rowId)).build();
+ }
+
+ private String getParticipantIdFromUri(Uri uri) {
+ return uri.getPathSegments().get(PARTICIPANT_ID_INDEX_IN_THREAD_URI);
}
}
diff --git a/src/com/android/providers/telephony/RcsProviderThreadHelper.java b/src/com/android/providers/telephony/RcsProviderThreadHelper.java
index e643136..9b9947b 100644
--- a/src/com/android/providers/telephony/RcsProviderThreadHelper.java
+++ b/src/com/android/providers/telephony/RcsProviderThreadHelper.java
@@ -15,9 +15,40 @@
*/
package com.android.providers.telephony;
+import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.FALLBACK_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.CONFERENCE_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_ICON_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.OWNER_PARTICIPANT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TEXT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.STATUS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static android.telephony.ims.RcsQueryContinuationToken.THREAD_QUERY_CONTINUATION_TOKEN_TYPE;
+import static android.telephony.ims.RcsThreadQueryParameters.THREAD_QUERY_PARAMETERS_KEY;
+
+import static com.android.providers.telephony.RcsProvider.RCS_1_TO_1_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_GROUP_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_PARTICIPANT_THREAD_JUNCTION_TABLE;
+import static com.android.providers.telephony.RcsProvider.RCS_THREAD_TABLE;
+import static com.android.providers.telephony.RcsProvider.TAG;
+import static com.android.providers.telephony.RcsProvider.UNIFIED_RCS_THREAD_VIEW;
+import static com.android.providers.telephony.RcsProviderUtil.INSERTION_FAILED;
+
import android.content.ContentValues;
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.BaseColumns;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.telephony.ims.RcsThreadQueryParameters;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -27,64 +58,383 @@
* @hide
*/
class RcsProviderThreadHelper {
- static final String ID_COLUMN = "_id";
- static final String THREAD_TABLE = "rcs_thread";
- static final String OWNER_PARTICIPANT = "owner_participant";
-
- static final String RCS_1_TO_1_THREAD_TABLE = "rcs_1_to_1_thread";
- static final String RCS_THREAD_ID_COLUMN = "rcs_thread_id";
- static final String FALLBACK_THREAD_ID_COLUMN = "rcs_fallback_thread_id";
-
- static final String RCS_GROUP_THREAD_TABLE = "rcs_group_thread";
- static final String GROUP_NAME_COLUMN = "group_name";
- static final String ICON_COLUMN = "icon";
- static final String CONFERENCE_URI_COLUMN = "conference_uri";
+ private static final int THREAD_ID_INDEX_IN_URI = 1;
@VisibleForTesting
public static void createThreadTables(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + THREAD_TABLE + " (" +
- ID_COLUMN + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
- OWNER_PARTICIPANT + " INTEGER " +
- ");");
+ Log.d(TAG, "Creating thread tables");
+
+ // Add the thread tables
+ db.execSQL("CREATE TABLE " + RCS_THREAD_TABLE + " (" +
+ RCS_THREAD_ID_COLUMN + " INTEGER PRIMARY KEY AUTOINCREMENT);");
db.execSQL("CREATE TABLE " + RCS_1_TO_1_THREAD_TABLE + " (" +
RCS_THREAD_ID_COLUMN + " INTEGER PRIMARY KEY, " +
FALLBACK_THREAD_ID_COLUMN + " INTEGER, " +
"FOREIGN KEY(" + RCS_THREAD_ID_COLUMN
- + ") REFERENCES " + THREAD_TABLE + "(" + ID_COLUMN + ")," +
+ + ") REFERENCES " + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + ")," +
"FOREIGN KEY(" + FALLBACK_THREAD_ID_COLUMN
- + ") REFERENCES threads( " + ID_COLUMN + "))" );
+ + ") REFERENCES threads( " + BaseColumns._ID + "))");
db.execSQL("CREATE TABLE " + RCS_GROUP_THREAD_TABLE + " (" +
RCS_THREAD_ID_COLUMN + " INTEGER PRIMARY KEY, " +
+ OWNER_PARTICIPANT_COLUMN + " INTEGER, " +
GROUP_NAME_COLUMN + " TEXT, " +
- ICON_COLUMN + " TEXT, " +
+ GROUP_ICON_COLUMN + " TEXT, " +
CONFERENCE_URI_COLUMN + " TEXT, " +
"FOREIGN KEY(" + RCS_THREAD_ID_COLUMN
- + ") REFERENCES " + THREAD_TABLE + "(" + ID_COLUMN + "))" );
+ + ") REFERENCES " + RCS_THREAD_TABLE + "(" + RCS_THREAD_ID_COLUMN + "))");
+ // Add the views
+
+ // The following is a unified thread view. Since SQLite does not support right or full
+ // joins, we are using a union with null values for unused variables for each thread type.
+ // The thread_type column is an easy way to figure out whether the entry came from a 1 to 1
+ // thread or a group thread. The last message in each thread is appended to the table
+ // entries to figure out the latest threads and snippet text. We use COALESCE so that MAX()
+ // can take null values into account in order to have threads with no messages still
+ // represented here
+ //
+ // SELECT <1 to 1 thread and first message>
+ // FROM (
+ // SELECT *
+ // FROM rcs_1_to_1_thread LEFT JOIN rcs_message
+ // ON rcs_1_to_1_thread.rcs_thread_id=rcs_message.rcs_thread_id)
+ // GROUP BY rcs_thread_id
+ // HAVING MAX(COALESCE(origination_timestamp,1))
+ //
+ // UNION
+ // SELECT <group thread and first message>
+ // FROM (
+ // SELECT *
+ // FROM rcs_group_thread LEFT JOIN rcs_message
+ // ON rcs_group_thread.rcs_thread_id=rcs_message.rcs_thread_id)
+ // GROUP BY rcs_thread_id
+ // HAVING MAX(COALESCE(origination_timestamp,1))
+
+ db.execSQL("CREATE VIEW " + UNIFIED_RCS_THREAD_VIEW + " AS "
+ + "SELECT "
+ + RCS_THREAD_ID_COLUMN + ", "
+ + FALLBACK_THREAD_ID_COLUMN + ", "
+ + "null AS " + OWNER_PARTICIPANT_COLUMN + ", "
+ + "null AS " + GROUP_NAME_COLUMN + ", "
+ + "null AS " + GROUP_ICON_COLUMN + ", "
+ + "null AS " + CONFERENCE_URI_COLUMN + ", "
+ + "0 AS " + THREAD_TYPE_COLUMN + ", "
+ + ORIGINATION_TIMESTAMP_COLUMN + ", "
+ + MESSAGE_TEXT_COLUMN + ", "
+ + STATUS_COLUMN
+ + " FROM (SELECT * FROM "
+ + RCS_1_TO_1_THREAD_TABLE + " LEFT JOIN " + RCS_MESSAGE_TABLE
+ + " ON "
+ + RCS_1_TO_1_THREAD_TABLE + "." + RCS_THREAD_ID_COLUMN + "="
+ + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ")"
+ + " GROUP BY " + RCS_THREAD_ID_COLUMN
+ + " HAVING MAX(COALESCE("
+ + ORIGINATION_TIMESTAMP_COLUMN + ", 1))"
+ + " UNION SELECT "
+ + RCS_THREAD_ID_COLUMN + ", "
+ + "null AS " + FALLBACK_THREAD_ID_COLUMN + ", "
+ + OWNER_PARTICIPANT_COLUMN + ", "
+ + GROUP_NAME_COLUMN + ", "
+ + GROUP_ICON_COLUMN + ", "
+ + CONFERENCE_URI_COLUMN + ", "
+ + "1 AS " + THREAD_TYPE_COLUMN + ", "
+ + ORIGINATION_TIMESTAMP_COLUMN + ", "
+ + MESSAGE_TEXT_COLUMN + ", "
+ + STATUS_COLUMN
+ + " FROM (SELECT * FROM "
+ + RCS_GROUP_THREAD_TABLE + " LEFT JOIN " + RCS_MESSAGE_TABLE
+ + " ON "
+ + RCS_GROUP_THREAD_TABLE + "." + RCS_THREAD_ID_COLUMN + "="
+ + RCS_MESSAGE_TABLE + "." + RCS_THREAD_ID_COLUMN + ")"
+ + " GROUP BY " + RCS_THREAD_ID_COLUMN
+ + " HAVING MAX(COALESCE("
+ + ORIGINATION_TIMESTAMP_COLUMN + ", 1))");
+
+ // Add the triggers
+
+ // Delete the corresponding rcs_thread row upon deleting a row in rcs_1_to_1_thread
+ //
+ // CREATE TRIGGER deleteRcsThreadAfter1to1
+ // AFTER DELETE ON rcs_1_to_1_thread
+ // BEGIN
+ // DELETE FROM rcs_thread WHERE rcs_thread._id=OLD.rcs_thread_id;
+ // END
+ db.execSQL("CREATE TRIGGER deleteRcsThreadAfter1to1 AFTER DELETE ON "
+ + RCS_1_TO_1_THREAD_TABLE + " BEGIN DELETE FROM " + RCS_THREAD_TABLE + " WHERE "
+ + RCS_THREAD_TABLE + "." + RCS_THREAD_ID_COLUMN + "=OLD." + RCS_THREAD_ID_COLUMN
+ + "; END");
+
+ // Delete the corresponding rcs_thread row upon deleting a row in rcs_group_thread
+ //
+ // CREATE TRIGGER deleteRcsThreadAfter1to1
+ // AFTER DELETE ON rcs_1_to_1_thread
+ // BEGIN
+ // DELETE FROM rcs_thread WHERE rcs_thread._id=OLD.rcs_thread_id;
+ // END
+ db.execSQL("CREATE TRIGGER deleteRcsThreadAfterGroup AFTER DELETE ON "
+ + RCS_GROUP_THREAD_TABLE + " BEGIN DELETE FROM " + RCS_THREAD_TABLE + " WHERE "
+ + RCS_THREAD_TABLE + "." + RCS_THREAD_ID_COLUMN + "=OLD." + RCS_THREAD_ID_COLUMN
+ + "; END");
+
+ // Delete the junction table entries upon deleting a 1 to 1 thread
+ //
+ // CREATE TRIGGER delete1To1JunctionEntries
+ // AFTER
+ // DELETE ON rcs_1_to_1_thread
+ // BEGIN
+ // DELETE FROM
+ // rcs_thread_participant
+ // WHERE
+ // rcs_thread_participant.rcs_thread_id = OLD.rcs_thread_id;
+ // END
+ db.execSQL("CREATE TRIGGER delete1To1JunctionEntries AFTER DELETE ON "
+ + RCS_1_TO_1_THREAD_TABLE + " BEGIN DELETE FROM "
+ + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + " WHERE "
+ + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + "." + RCS_THREAD_ID_COLUMN + "=OLD."
+ + RCS_THREAD_ID_COLUMN + "; END");
+
+ // Delete the junction table entries upon deleting a group thread
+ //
+ // CREATE TRIGGER delete1To1JunctionEntries
+ // AFTER
+ // DELETE ON rcs_1_to_1_thread
+ // BEGIN
+ // DELETE FROM
+ // rcs_thread_participant
+ // WHERE
+ // rcs_thread_participant.rcs_thread_id = OLD.rcs_thread_id;
+ // END
+ db.execSQL("CREATE TRIGGER deleteGroupJunctionEntries AFTER DELETE ON "
+ + RCS_GROUP_THREAD_TABLE + " BEGIN DELETE FROM "
+ + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + " WHERE "
+ + RCS_PARTICIPANT_THREAD_JUNCTION_TABLE + "." + RCS_THREAD_ID_COLUMN + "=OLD."
+ +RCS_THREAD_ID_COLUMN + "; END");
+
+ // TODO - delete all messages in a thread after deleting a thread
+
+ // TODO - create indexes for faster querying
}
- static void buildThreadQuery(SQLiteQueryBuilder qb) {
- qb.setTables(THREAD_TABLE);
+ private final SQLiteOpenHelper mSqLiteOpenHelper;
+
+ RcsProviderThreadHelper(SQLiteOpenHelper sqLiteOpenHelper) {
+ mSqLiteOpenHelper = sqLiteOpenHelper;
}
- static long insert(SQLiteDatabase db, ContentValues values) {
- long rowId = db.insert(THREAD_TABLE, OWNER_PARTICIPANT, values);
- db.setTransactionSuccessful();
- return rowId;
+ Cursor queryUnifiedThread(Bundle bundle) {
+ RcsThreadQueryParameters queryParameters = null;
+ RcsQueryContinuationToken continuationToken = null;
+ if (bundle != null) {
+ queryParameters = bundle.getParcelable(
+ THREAD_QUERY_PARAMETERS_KEY);
+ continuationToken = bundle.getParcelable(QUERY_CONTINUATION_TOKEN);
+ }
+
+ if (continuationToken != null) {
+ return RcsProviderUtil.performContinuationQuery(mSqLiteOpenHelper.getReadableDatabase(),
+ continuationToken);
+ }
+
+ if (queryParameters == null) {
+ queryParameters = new RcsThreadQueryParameters.Builder().build();
+ }
+
+ return performInitialQuery(queryParameters);
}
- static int delete(SQLiteDatabase db, String selection, String[] selectionArgs) {
- int deletedRowCount = db.delete(THREAD_TABLE, selection, selectionArgs);
- db.setTransactionSuccessful();
- return deletedRowCount;
+ private Cursor performInitialQuery(RcsThreadQueryParameters queryParameters) {
+ if (queryParameters == null) {
+ // return everything for test purposes
+ queryParameters = new RcsThreadQueryParameters.Builder().build();
+ }
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ StringBuilder rawQuery = new StringBuilder("SELECT * FROM ").append(
+ UNIFIED_RCS_THREAD_VIEW);
+
+ if (queryParameters.getThreadType() == RcsThreadQueryParameters.THREAD_TYPE_1_TO_1) {
+ rawQuery.append(" WHERE ").append(THREAD_TYPE_COLUMN).append("=0");
+ } else if (queryParameters.getThreadType() == RcsThreadQueryParameters.THREAD_TYPE_GROUP) {
+ rawQuery.append(" WHERE ").append(THREAD_TYPE_COLUMN).append("=1");
+ }
+
+ rawQuery.append(" ORDER BY ");
+
+ if (queryParameters.getSortingProperty() == RcsThreadQueryParameters.SORT_BY_TIMESTAMP) {
+ rawQuery.append(ORIGINATION_TIMESTAMP_COLUMN);
+ } else {
+ rawQuery.append(RCS_THREAD_ID_COLUMN);
+ }
+
+ rawQuery.append(queryParameters.getSortDirection() ? " ASC " : " DESC ");
+ RcsProviderUtil.appendLimit(rawQuery, queryParameters.getLimit());
+
+ String rawQueryAsString = rawQuery.toString();
+ Cursor cursor = db.rawQuery(rawQueryAsString, null);
+
+ // If this is a paginated query, build the next query and return as a Cursor extra. Only do
+ // this if the current query returned a result.
+ int limit = queryParameters.getLimit();
+ if (limit > 0) {
+ RcsProviderUtil.createContinuationTokenBundle(cursor,
+ new RcsQueryContinuationToken(THREAD_QUERY_CONTINUATION_TOKEN_TYPE,
+ rawQueryAsString, limit, limit), QUERY_CONTINUATION_TOKEN);
+ }
+
+ return cursor;
}
- static int update(SQLiteDatabase db, ContentValues values, String selection,
- String[] selectionArgs) {
- int updatedRowCount = db.update(THREAD_TABLE, values, selection, selectionArgs);
- db.setTransactionSuccessful();
- return updatedRowCount;
+ Cursor queryUnifiedThreadUsingId(Uri uri, String[] projection) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ String threadId = getThreadIdFromUri(uri);
+
+ return db.query(UNIFIED_RCS_THREAD_VIEW, projection, RCS_THREAD_ID_COLUMN + "=?",
+ new String[]{threadId},
+ null, null, null);
+ }
+
+ Cursor query1to1Thread(String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ return db.query(RCS_1_TO_1_THREAD_TABLE, projection, selection, selectionArgs, null,
+ null, sortOrder);
+ }
+
+ Cursor query1To1ThreadUsingId(Uri uri, String[] projection) {
+ return query1to1Thread(projection, getThreadIdSelection(uri), null, null);
+ }
+
+ Cursor queryGroupThread(String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getReadableDatabase();
+ return db.query(RCS_GROUP_THREAD_TABLE, projection, selection, selectionArgs, null,
+ null, sortOrder);
+ }
+
+ Cursor queryGroupThreadUsingId(Uri uri, String[] projection) {
+ return queryGroupThread(projection, getThreadIdSelection(uri), null, null);
+ }
+
+ long insert1To1Thread(ContentValues contentValues) {
+ long returnValue = TRANSACTION_FAILED;
+ if (contentValues.containsKey(RCS_THREAD_ID_COLUMN)) {
+ Log.e(RcsProvider.TAG,
+ "RcsProviderThreadHelper: inserting threads with IDs is not supported");
+ return returnValue;
+ }
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ try {
+ db.beginTransaction();
+
+ // Insert into the common rcs_threads table
+ long rowId = insertIntoCommonRcsThreads(db);
+
+ // Add the rowId in rcs_threads table as a foreign key in rcs_1_to_1_table
+ contentValues.put(RCS_THREAD_ID_COLUMN, rowId);
+ db.insert(RCS_1_TO_1_THREAD_TABLE, RCS_THREAD_ID_COLUMN, contentValues);
+ contentValues.remove(RCS_THREAD_ID_COLUMN);
+
+ db.setTransactionSuccessful();
+ returnValue = rowId;
+ } finally {
+ db.endTransaction();
+ }
+ return returnValue;
+ }
+
+ long insertGroupThread(ContentValues contentValues) {
+ long returnValue = TRANSACTION_FAILED;
+ if (contentValues.containsKey(RCS_THREAD_ID_COLUMN)) {
+ Log.e(RcsProvider.TAG,
+ "RcsProviderThreadHelper: inserting threads with IDs is not supported");
+ return returnValue;
+ }
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ try {
+ db.beginTransaction();
+
+ // Insert into the common rcs_threads table
+ long rowId = insertIntoCommonRcsThreads(db);
+ if (rowId == INSERTION_FAILED) {
+ return returnValue;
+ }
+
+ // Add the rowId in rcs_threads table as a foreign key in rcs_group_table
+ contentValues.put(RCS_THREAD_ID_COLUMN, rowId);
+ db.insert(RCS_GROUP_THREAD_TABLE, RCS_THREAD_ID_COLUMN, contentValues);
+ contentValues.remove(RCS_THREAD_ID_COLUMN);
+
+ db.setTransactionSuccessful();
+ returnValue = rowId;
+ } finally {
+ db.endTransaction();
+ }
+ return returnValue;
+ }
+
+ private long insertIntoCommonRcsThreads(SQLiteDatabase db) {
+ return db.insert(RCS_THREAD_TABLE, RCS_THREAD_ID_COLUMN, new ContentValues());
+ }
+
+ int delete1To1Thread(String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.delete(RCS_1_TO_1_THREAD_TABLE, selection, selectionArgs);
+ }
+
+ int delete1To1ThreadWithId(Uri uri) {
+ return delete1To1Thread(getThreadIdSelection(uri), null);
+ }
+
+ int deleteGroupThread(String selection, String[] selectionArgs) {
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.delete(RCS_GROUP_THREAD_TABLE, selection, selectionArgs);
+ }
+
+ int deleteGroupThreadWithId(Uri uri) {
+ return deleteGroupThread(getThreadIdSelection(uri), null);
+ }
+
+ int update1To1Thread(ContentValues values, String selection, String[] selectionArgs) {
+ if (values.containsKey(RCS_THREAD_ID_COLUMN)) {
+ Log.e(TAG,
+ "RcsProviderThreadHelper: updating thread id for 1 to 1 threads is not "
+ + "allowed");
+ return 0;
+ }
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.update(RCS_1_TO_1_THREAD_TABLE, values, selection, selectionArgs);
+ }
+
+ int update1To1ThreadWithId(ContentValues values, Uri uri) {
+ return update1To1Thread(values, getThreadIdSelection(uri), null);
+ }
+
+ int updateGroupThread(ContentValues values, String selection, String[] selectionArgs) {
+ if (values.containsKey(RCS_THREAD_ID_COLUMN)) {
+ Log.e(TAG,
+ "RcsProviderThreadHelper: updating thread id for group threads is not "
+ + "allowed");
+ return 0;
+ }
+
+ SQLiteDatabase db = mSqLiteOpenHelper.getWritableDatabase();
+ return db.update(RCS_GROUP_THREAD_TABLE, values, selection, selectionArgs);
+ }
+
+ int updateGroupThreadWithId(ContentValues values, Uri uri) {
+ return updateGroupThread(values, getThreadIdSelection(uri), null);
+ }
+
+ private String getThreadIdSelection(Uri uri) {
+ return RCS_THREAD_ID_COLUMN + "=" + getThreadIdFromUri(uri);
+ }
+
+ static String getThreadIdFromUri(Uri uri) {
+ return uri.getPathSegments().get(THREAD_ID_INDEX_IN_URI);
}
}
diff --git a/src/com/android/providers/telephony/RcsProviderUtil.java b/src/com/android/providers/telephony/RcsProviderUtil.java
new file mode 100644
index 0000000..38f7be1
--- /dev/null
+++ b/src/com/android/providers/telephony/RcsProviderUtil.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.TRANSACTION_FAILED;
+
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static com.android.providers.telephony.RcsProvider.TAG;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.text.TextUtils;
+import android.util.Log;
+
+class RcsProviderUtil {
+ /**
+ * The value returned when database insertion fails,
+ * @see SQLiteDatabase#insert(String, String, ContentValues)
+ */
+ static final int INSERTION_FAILED = -1;
+
+ static void appendLimit(StringBuilder stringBuilder, int limit) {
+ if (limit > 0) {
+ stringBuilder.append(" LIMIT ").append(limit);
+ }
+ }
+
+ static void createContinuationTokenBundle(Cursor cursor, Parcelable continuationToken,
+ String tokenKey) {
+ if (cursor.getCount() > 0) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(tokenKey, continuationToken);
+ cursor.setExtras(bundle);
+ }
+ }
+
+ /**
+ * This method gets a token with raw query, performs the query, increments the token's offset
+ * and returns it as a cursor extra
+ */
+ static Cursor performContinuationQuery(SQLiteDatabase db,
+ RcsQueryContinuationToken continuationToken) {
+ String rawQuery = continuationToken.getRawQuery();
+ int offset = continuationToken.getOffset();
+
+ if (offset <= 0 || TextUtils.isEmpty(rawQuery)) {
+ Log.e(TAG, "RcsProviderUtil: Invalid continuation token");
+ return null;
+ }
+
+ String continuationQuery = rawQuery + " OFFSET " + offset;
+
+ Cursor cursor = db.rawQuery(continuationQuery, null);
+ if (cursor.getCount() > 0) {
+ continuationToken.incrementOffset();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+ cursor.setExtras(bundle);
+ }
+
+ return cursor;
+ }
+
+ static Uri buildUriWithRowIdAppended(Uri prefix, long rowId) {
+ if (rowId == TRANSACTION_FAILED) {
+ return null;
+ }
+ return Uri.withAppendedPath(prefix, Long.toString(rowId));
+ }
+
+ static Uri returnUriAsIsIfSuccessful(Uri uri, long rowId) {
+ if (rowId == TRANSACTION_FAILED) {
+ return null;
+ }
+ return uri;
+ }
+}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderDeleteTest.java b/tests/src/com/android/providers/telephony/RcsProviderDeleteTest.java
new file mode 100644
index 0000000..61e9713
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderDeleteTest.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.FALLBACK_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.OWNER_PARTICIPANT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.NEW_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
+
+import static com.android.providers.telephony.RcsProvider.RCS_MESSAGE_TABLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsProviderDeleteTest {
+ private MockContentResolver mContentResolver;
+ private RcsProviderTestable mRcsProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mRcsProvider = new RcsProviderTestable();
+ RcsProviderTestable.MockContextWithProvider
+ context = new RcsProviderTestable.MockContextWithProvider(mRcsProvider);
+ mContentResolver = context.getContentResolver();
+
+ // insert a participant
+ // first into the MmsSmsProvider
+ mRcsProvider.getWritableDatabase().execSQL(
+ "INSERT INTO canonical_addresses VALUES (1, \"+15551234567\")");
+
+ // then into the RcsProvider
+ ContentValues participantValues = new ContentValues();
+ participantValues.put(RCS_ALIAS_COLUMN, "Bob");
+ participantValues.put(CANONICAL_ADDRESS_ID_COLUMN, 1);
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/participant"),
+ participantValues)).isEqualTo(Uri.parse("content://rcs/participant/1"));
+
+ // insert one 1 to 1 thread
+ ContentValues p2pContentValues = new ContentValues();
+ Uri p2pThreadUri = Uri.parse("content://rcs/p2p_thread");
+ p2pContentValues.put(FALLBACK_THREAD_ID_COLUMN, 1);
+ assertThat(mContentResolver.insert(p2pThreadUri, p2pContentValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1"));
+
+ // insert one group thread
+ ContentValues groupContentValues = new ContentValues();
+ groupContentValues.put(OWNER_PARTICIPANT_COLUMN, 1);
+ groupContentValues.put(GROUP_NAME_COLUMN, "name");
+ Uri groupThreadUri = Uri.parse("content://rcs/group_thread");
+ assertThat(mContentResolver.insert(groupThreadUri, groupContentValues)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/2"));
+
+ // add the participant into both threads
+ Uri addParticipantTo1To1Thread = Uri.parse("content://rcs/p2p_thread/1/participant/1");
+ assertThat(mContentResolver.insert(addParticipantTo1To1Thread, null)).isEqualTo(
+ addParticipantTo1To1Thread);
+
+ Uri addParticipantToGroupThread = Uri.parse("content://rcs/group_thread/2/participant/1");
+ assertThat(mContentResolver.insert(addParticipantToGroupThread, null)).isEqualTo(
+ addParticipantToGroupThread);
+
+ // add incoming and outgoing messages to both threads
+ ContentValues messageValues = new ContentValues();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"));
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1/outgoing_message/2"));
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/incoming_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/2/incoming_message/3"));
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/outgoing_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/2/outgoing_message/4"));
+
+ // add a file transfer to a message
+ ContentValues fileTransferValues = new ContentValues();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/message/3/file_transfer"),
+ fileTransferValues)).isEqualTo(Uri.parse("content://rcs/file_transfer/1"));
+
+ // insert an alias change event
+ ContentValues eventValues = new ContentValues();
+ eventValues.put(NEW_ALIAS_COLUMN, "new alias");
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/participant/1/alias_change_event"),
+ eventValues)).isEqualTo(Uri.parse(
+ "content://rcs/participant/1/alias_change_event/1"));
+
+ // create a group name change event
+ eventValues.clear();
+ eventValues.put(NEW_NAME_COLUMN, "new name");
+ assertThat(mContentResolver.insert(
+ Uri.parse("content://rcs/group_thread/2/name_changed_event"),
+ eventValues)).isEqualTo(Uri.parse(
+ "content://rcs/group_thread/2/name_changed_event/1"));
+ }
+
+ @After
+ public void tearDown() {
+ mRcsProvider.tearDown();
+ }
+
+ @Test
+ public void testDelete1To1ThreadWithId() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1"), null,
+ null)).isEqualTo(1);
+ assertDeletionViaQuery("content://rcs/p2p_thread/1");
+ }
+
+ @Test
+ public void testDelete1To1ThreadWithSelection() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/p2p_thread"),
+ "rcs_fallback_thread_id=1", null)).isEqualTo(1);
+ assertDeletionViaQuery("content://rcs/p2p_thread/1");
+ }
+
+ @Test
+ public void testDeleteGroupThreadWithId() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread/2"), null,
+ null)).isEqualTo(1);
+ assertDeletionViaQuery("content://rcs/group_thread/2");
+ }
+
+ @Test
+ public void testDeleteGroupThreadWithSelection() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread"),
+ "group_name=\"name\"", null)).isEqualTo(1);
+ assertDeletionViaQuery("content://rcs/group_thread/1");
+ }
+
+ @Test
+ public void testDeleteParticipantWithIdWhileParticipatingInAThread() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/participant/1"), null,
+ null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testDeleteParticipantAfterLeavingThreads() {
+ // leave the first thread
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread/2/participant/1"),
+ null, null)).isEqualTo(1);
+
+ // try deleting the participant. It should fail
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/participant/1"), null,
+ null)).isEqualTo(0);
+
+ // delete the p2p thread
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1"), null,
+ null)).isEqualTo(1);
+
+ // try deleting the participant. It should succeed
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/participant/1"), null,
+ null)).isEqualTo(1);
+ assertDeletionViaQuery("content://rcs/participant/1");
+ }
+
+ @Test
+ public void testDeleteParticipantWithSelectionFails() {
+ assertThat(
+ mContentResolver.delete(Uri.parse("content://rcs/participant"), "rcs_alias=\"Bob\"",
+ null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testDeleteParticipantFrom1To1ThreadFails() {
+ assertThat(
+ mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1/participant/1"), null,
+ null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testDeleteParticipantFromGroupThread() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread/2/participant/1"),
+ null, null)).isEqualTo(1);
+ assertDeletionViaQuery("content://rcs/group_thread/2/participant");
+ }
+
+ @Test
+ public void testDeleteParticipantFromGroupThreadWithSelectionFails() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/group_thread/2/participant"),
+ "rcs_alias=?", new String[]{"Bob"})).isEqualTo(0);
+ }
+
+ @Test
+ public void testDeleteMessagesUsingUnifiedMessageViewFails() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/message/1"), null,
+ null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testDeleteMessagesUsingThreadUrisFails() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1/message/1"), null,
+ null)).isEqualTo(0);
+ assertThat(
+ mContentResolver.delete(Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"),
+ null, null)).isEqualTo(0);
+ }
+
+ @Test
+ @Ignore // TODO: fix and un-ignore
+ public void testDeleteMessage() {
+ // verify there exists 4 messages
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, null,
+ null);
+ assertThat(cursor.getCount()).isEqualTo(4);
+ cursor.close();
+
+ // delete 2 of them
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/incoming_message/1"), null,
+ null)).isEqualTo(1);
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/outgoing_message/4"), null,
+ null)).isEqualTo(1);
+
+ // verify that only 2 messages are left
+ cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(2);
+ cursor.close();
+
+ // verify that entries in common table is deleted and only messages with id's 2 and 3 remain
+ SQLiteDatabase db = mRcsProvider.getWritableDatabase();
+ cursor = db.query(RCS_MESSAGE_TABLE, null, null, null, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(2);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(0)).isEqualTo(2);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(0)).isEqualTo(3);
+ cursor.close();
+ }
+
+ @Test
+ public void testDeleteFileTransfer() {
+ assertThat(mContentResolver.delete(Uri.parse("content://rcs/file_transfer/1"), null,
+ null)).isEqualTo(1);
+ assertDeletionViaQuery("content://rcs/file_transfer/1");
+ }
+
+ @Test
+ public void testDeleteParticipantEvent() {
+ assertThat(mContentResolver.delete(Uri.parse(
+ "content://rcs/participant/1/alias_change_event/1"), null, null)).isEqualTo(1);
+
+ // try deleting again and verify nothing is deleted
+ // TODO - convert to query once querying is in place
+ assertThat(mContentResolver.delete(Uri.parse(
+ "content://rcs/participant/1/alias_change_event/1"), null, null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testDeleteGroupThreadEvent() {
+ assertThat(mContentResolver.delete(Uri.parse(
+ "content://rcs/group_thread/2/name_changed_event/1"), null, null)).isEqualTo(1);
+
+ // try deleting again and verify nothing is deleted
+ // TODO - convert to query once querying is in place
+ assertThat(mContentResolver.delete(Uri.parse(
+ "content://rcs/group_thread/2/name_changed_event/1"), null, null)).isEqualTo(0);
+ }
+
+ private void assertDeletionViaQuery(String queryUri) {
+ Cursor cursor = mContentResolver.query(Uri.parse(queryUri), null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(0);
+ cursor.close();
+ }
+}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderInsertTest.java b/tests/src/com/android/providers/telephony/RcsProviderInsertTest.java
new file mode 100644
index 0000000..b893341
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderInsertTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.FALLBACK_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.CONFERENCE_URI_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_ICON_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.GLOBAL_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantEventColumns.NEW_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsProviderInsertTest {
+ private MockContentResolver mContentResolver;
+ private RcsProviderTestable mRcsProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mRcsProvider = new RcsProviderTestable();
+ RcsProviderTestable.MockContextWithProvider
+ context = new RcsProviderTestable.MockContextWithProvider(mRcsProvider);
+ mContentResolver = context.getContentResolver();
+ }
+
+ @After
+ public void tearDown() {
+ mRcsProvider.tearDown();
+ }
+
+ @Test
+ public void testInsertUnifiedThreadFails() {
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/thread"), null)).isNull();
+ }
+
+ @Test
+ public void testInsert1To1Thread() {
+ ContentValues values = new ContentValues(1);
+ values.put(FALLBACK_THREAD_ID_COLUMN, 445);
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/p2p_thread"), values)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1"));
+ }
+
+ @Test
+ public void testInsertGroupThread() {
+ ContentValues contentValues = new ContentValues(3);
+ contentValues.put(CONFERENCE_URI_COLUMN, "conference uri");
+ contentValues.put(GROUP_NAME_COLUMN, "group name");
+ contentValues.put(GROUP_ICON_COLUMN, "groupIcon");
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/group_thread"),
+ contentValues)).isEqualTo(Uri.parse("content://rcs/group_thread/1"));
+ }
+
+ @Test
+ public void testInsertParticipant() {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 6);
+ contentValues.put(RCS_ALIAS_COLUMN, "Alias");
+
+ Uri uri = mContentResolver.insert(Uri.parse("content://rcs/participant"), contentValues);
+ assertThat(uri).isEqualTo(Uri.parse("content://rcs/participant/1"));
+ }
+
+ @Test
+ public void testInsertParticipantInto1To1Thread() {
+ // create a participant
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 15);
+ contentValues.put(RCS_ALIAS_COLUMN, "Alias");
+ mContentResolver.insert(Uri.parse("content://rcs/participant"), contentValues);
+
+ // create a thread
+ ContentValues values = new ContentValues(1);
+ values.put(FALLBACK_THREAD_ID_COLUMN, 699);
+ mContentResolver.insert(Uri.parse("content://rcs/p2p_thread"), values);
+
+ // add participant to the thread
+ Uri uri = Uri.parse("content://rcs/p2p_thread/1/participant/1");
+ assertThat(mContentResolver.insert(uri, null)).isEqualTo(uri);
+
+ // assert that adding again fails
+ assertThat(mContentResolver.insert(uri, null)).isNull();
+ }
+
+ @Test
+ public void testInsertParticipantIntoGroupThread() {
+ // create a participant
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 23);
+ mContentResolver.insert(Uri.parse("content://rcs/participant"), contentValues);
+
+ // create a thread
+ ContentValues values = new ContentValues(1);
+ values.put(GROUP_NAME_COLUMN, "Group");
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread"), values);
+
+ // add participant to the thread
+ Uri uri = Uri.parse("content://rcs/group_thread/1/participant/1");
+ assertThat(mContentResolver.insert(uri, null)).isEqualTo(uri);
+
+ // assert that adding again fails
+ assertThat(mContentResolver.insert(uri, null)).isNull();
+ }
+
+ @Test
+ public void testInsertMessageFails() {
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(GLOBAL_ID_COLUMN, "global RCS id");
+
+ // try inserting messages without threads
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/message"),
+ contentValues)).isNull();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/message/6"),
+ contentValues)).isNull();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/incoming_message"),
+ contentValues)).isNull();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/incoming_message/12"),
+ contentValues)).isNull();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message"),
+ contentValues)).isNull();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message/18"),
+ contentValues)).isNull();
+
+ // try inserting into unified thread view
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/thread/5/incoming_message"),
+ contentValues)).isNull();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/thread/5/outgoing_message"),
+ contentValues)).isNull();
+ }
+
+ @Test
+ @Ignore // TODO: fix and un-ignore
+ public void testInsertMessageIntoThread() {
+ // create two threads
+ ContentValues values = new ContentValues();
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/p2p_thread"), values)).isNotNull();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/group_thread"),
+ values)).isNotNull();
+
+ // add messages to threads
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+ values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"));
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+ values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/1/outgoing_message/2"));
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/incoming_message"),
+ values)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/2/incoming_message/3"));
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/outgoing_message"),
+ values)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/2/outgoing_message/4"));
+
+ // assert that they are accessible in messages table
+ Cursor messageCursor = mContentResolver.query(Uri.parse("content://rcs/message"), null,
+ null, null, null);
+ assertThat(messageCursor.getCount()).isEqualTo(4);
+ }
+
+ @Test
+ public void testInsertMessageDelivery() {
+ // create a thread
+ ContentValues values = new ContentValues();
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/p2p_thread"), values)).isNotNull();
+
+ // add an outgoing message to the thread
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+ values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/1/outgoing_message/1"));
+
+ // add a delivery to the outgoing message
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message/1/delivery/1"),
+ values)).isEqualTo(Uri.parse("content://rcs/outgoing_message/1/delivery/1"));
+ }
+
+ @Test
+ public void testInsertFileTransfer() {
+ // create a thread
+ ContentValues values = new ContentValues();
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/p2p_thread"), values)).isNotNull();
+
+ // add an outgoing message to the thread
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+ values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/1/outgoing_message/1"));
+
+ // add a file transfer to the message
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/message/1/file_transfer"),
+ values)).isEqualTo(Uri.parse("content://rcs/file_transfer/1"));
+ }
+
+ @Test
+ public void testInsertParticipantEvent() {
+ // create a participant
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 23);
+ mContentResolver.insert(Uri.parse("content://rcs/participant"), contentValues);
+
+ // insert an alias change event
+ ContentValues eventValues = new ContentValues();
+ eventValues.put(NEW_ALIAS_COLUMN, "new alias");
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/participant/1/alias_change_event"),
+ eventValues)).isEqualTo(Uri.parse(
+ "content://rcs/participant/1/alias_change_event/1"));
+ }
+
+ @Test
+ public void testInsertGroupThreadEvent() {
+ // create a group thread
+ ContentValues contentValues = new ContentValues();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/group_thread"),
+ contentValues)).isEqualTo(Uri.parse("content://rcs/group_thread/1"));
+
+ // create a group name change event
+ ContentValues eventValues = new ContentValues();
+ eventValues.put(NEW_NAME_COLUMN, "new name");
+ assertThat(mContentResolver.insert(
+ Uri.parse("content://rcs/group_thread/1/name_changed_event"),
+ eventValues)).isEqualTo(Uri.parse(
+ "content://rcs/group_thread/1/name_changed_event/1"));
+ }
+}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderQueryTest.java b/tests/src/com/android/providers/telephony/RcsProviderQueryTest.java
new file mode 100644
index 0000000..e18ebd5
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderQueryTest.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.FILE_SIZE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.SESSION_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TEXT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.MESSAGE_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsThreadColumns.RCS_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.DESTINATION_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.EVENT_TYPE_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.NEW_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsThreadEventColumns.SOURCE_PARTICIPANT_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_COLUMN;
+import static android.telephony.ims.RcsEventQueryParameters.EVENT_QUERY_PARAMETERS_KEY;
+import static android.telephony.ims.RcsEventQueryParameters.GROUP_THREAD_NAME_CHANGED_EVENT;
+import static android.telephony.ims.RcsMessageQueryParameters.MESSAGE_QUERY_PARAMETERS_KEY;
+import static android.telephony.ims.RcsParticipantQueryParameters.PARTICIPANT_QUERY_PARAMETERS_KEY;
+import static android.telephony.ims.RcsQueryContinuationToken.QUERY_CONTINUATION_TOKEN;
+import static android.telephony.ims.RcsThreadQueryParameters.THREAD_QUERY_PARAMETERS_KEY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.RcsEventQueryParameters;
+import android.telephony.ims.RcsGroupThread;
+import android.telephony.ims.RcsMessageQueryParameters;
+import android.telephony.ims.RcsParticipantQueryParameters;
+import android.telephony.ims.RcsQueryContinuationToken;
+import android.telephony.ims.RcsThreadQueryParameters;
+import android.test.mock.MockContentResolver;
+
+import com.android.providers.telephony.RcsProviderTestable.MockContextWithProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsProviderQueryTest {
+ private MockContentResolver mContentResolver;
+ private RcsProviderTestable mRcsProvider;
+
+ private static final String GROUP_NAME = "group name";
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mRcsProvider = new RcsProviderTestable();
+ MockContextWithProvider context = new MockContextWithProvider(mRcsProvider);
+ mContentResolver = context.getContentResolver();
+
+ // insert two participants
+ Uri participantUri = Uri.parse("content://rcs/participant");
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 99);
+ contentValues.put(RCS_ALIAS_COLUMN, "Some alias");
+ mContentResolver.insert(participantUri, contentValues);
+
+ contentValues.clear();
+ contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 100);
+ contentValues.put(RCS_ALIAS_COLUMN, "Some other alias");
+ mContentResolver.insert(participantUri, contentValues);
+
+ // insert two 1 to 1 threads
+ ContentValues p2pContentValues = new ContentValues(0);
+ Uri threadsUri = Uri.parse("content://rcs/p2p_thread");
+ assertThat(mContentResolver.insert(threadsUri, p2pContentValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1"));
+ assertThat(mContentResolver.insert(threadsUri, p2pContentValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/2"));
+
+ // insert one group thread
+ ContentValues groupContentValues = new ContentValues(1);
+ groupContentValues.put(GROUP_NAME_COLUMN, GROUP_NAME);
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/group_thread"),
+ groupContentValues)).isEqualTo(Uri.parse("content://rcs/group_thread/3"));
+
+ // put participants into the group
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread/3/participant/1"), null);
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread/3/participant/2"), null);
+
+ // insert two messages into first thread, leave the second one empty, insert one into group
+ // thread
+ ContentValues messageValues = new ContentValues();
+
+ messageValues.put(ORIGINATION_TIMESTAMP_COLUMN, 300);
+ messageValues.put(MESSAGE_TEXT_COLUMN, "Old message");
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"));
+
+ messageValues.clear();
+ messageValues.put(ORIGINATION_TIMESTAMP_COLUMN, 400);
+ messageValues.put(MESSAGE_TEXT_COLUMN, "New message");
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1/outgoing_message/2"));
+
+ messageValues.clear();
+ messageValues.put(ORIGINATION_TIMESTAMP_COLUMN, 200);
+ messageValues.put(MESSAGE_TEXT_COLUMN, "Group message");
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread/3/incoming_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/3/incoming_message/3"));
+
+ // Add two events to the group thread
+ ContentValues eventValues = new ContentValues();
+ eventValues.put(NEW_NAME_COLUMN, "New group name");
+ assertThat(mContentResolver.insert(
+ Uri.parse("content://rcs/group_thread/3/name_changed_event"),
+ eventValues)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/3/name_changed_event/1"));
+
+ eventValues.clear();
+ eventValues.put(SOURCE_PARTICIPANT_ID_COLUMN, 1);
+ eventValues.put(DESTINATION_PARTICIPANT_ID_COLUMN, 2);
+ assertThat(mContentResolver.insert(
+ Uri.parse("content://rcs/group_thread/3/participant_joined_event"),
+ eventValues)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/3/participant_joined_event/2"));
+ }
+
+ @After
+ public void tearDown() {
+ mRcsProvider.tearDown();
+ }
+
+ @Test
+ public void testCanQueryUnifiedThreads() {
+ RcsThreadQueryParameters queryParameters = new RcsThreadQueryParameters.Builder().build();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(THREAD_QUERY_PARAMETERS_KEY, queryParameters);
+
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/thread"),
+ null, bundle, null);
+ assertThat(cursor.getCount()).isEqualTo(3);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(1);
+ assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(
+ GROUP_NAME);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+ 200);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+ "Group message");
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+ assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(0);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(null);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+ assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+ 400);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+ "New message");
+ }
+
+ @Test
+ public void testCanQueryUnifiedThreadsWithLimitAndSorting() {
+ RcsThreadQueryParameters queryParameters = new RcsThreadQueryParameters.Builder()
+ .setThreadType(RcsThreadQueryParameters.THREAD_TYPE_1_TO_1).setResultLimit(1)
+ .setSortProperty(RcsThreadQueryParameters.SORT_BY_TIMESTAMP).setSortDirection(true)
+ .build();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(THREAD_QUERY_PARAMETERS_KEY, queryParameters);
+
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/thread"),
+ null, bundle, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+ assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(0);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(null);
+ }
+
+ @Test
+ public void testCanContinueThreadQuery() {
+ // Limit results to 1.
+ RcsThreadQueryParameters queryParameters =
+ new RcsThreadQueryParameters.Builder().setResultLimit(1).build();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(THREAD_QUERY_PARAMETERS_KEY, queryParameters);
+
+ // Perform an initial query, verify first thread is returned
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/thread"), null, bundle,
+ null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(1);
+ assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(
+ GROUP_NAME);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+ 200);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+ "Group message");
+
+ // Put the continuation token in the bundle to do a follow up query
+ RcsQueryContinuationToken continuationToken = cursor.getExtras().getParcelable(
+ QUERY_CONTINUATION_TOKEN);
+ bundle.clear();
+ bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+ cursor.close();
+
+ cursor = mContentResolver.query(Uri.parse("content://rcs/thread"), null, bundle,
+ null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+ assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(0);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(null);
+ cursor.close();
+
+ // Put the continuation token in the bundle to do a follow up query again, verify third
+ // thread is returned
+ continuationToken = cursor.getExtras().getParcelable(QUERY_CONTINUATION_TOKEN);
+ bundle.clear();
+ bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+ cursor = mContentResolver.query(Uri.parse("content://rcs/thread"), null, bundle,
+ null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(THREAD_TYPE_COLUMN))).isEqualTo(0);
+ assertThat(cursor.getString(cursor.getColumnIndex(GROUP_NAME_COLUMN))).isEqualTo(null);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+ 400);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+ "New message");
+ cursor.close();
+ }
+
+ @Test
+ public void testQuery1To1Threads() {
+ // verify two threads are returned in the query
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/p2p_thread"),
+ new String[]{RCS_THREAD_ID_COLUMN}, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void testQueryGroupThreads() {
+ // verify one thread is returned in the query
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/group_thread"),
+ new String[]{GROUP_NAME_COLUMN}, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getString(0)).isEqualTo(GROUP_NAME);
+ }
+
+ @Test
+ public void testQueryParticipant() {
+ RcsParticipantQueryParameters queryParameters = new RcsParticipantQueryParameters.Builder()
+ .build();
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(PARTICIPANT_QUERY_PARAMETERS_KEY, queryParameters);
+
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/participant"), null, bundle,
+ null);
+ assertThat(cursor.getCount()).isEqualTo(2);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN))).isEqualTo(2);
+ assertThat(cursor.getInt(cursor.getColumnIndex(CANONICAL_ADDRESS_ID_COLUMN))).isEqualTo(
+ 100);
+ assertThat(cursor.getString(cursor.getColumnIndex(RCS_ALIAS_COLUMN))).isEqualTo(
+ "Some other alias");
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN))).isEqualTo(1);
+ assertThat(cursor.getInt(cursor.getColumnIndex(CANONICAL_ADDRESS_ID_COLUMN))).isEqualTo(99);
+ assertThat(cursor.getString(cursor.getColumnIndex(RCS_ALIAS_COLUMN))).isEqualTo(
+ "Some alias");
+ }
+
+ @Test
+ public void testQueryParticipantWithContinuation() {
+ Uri participantUri = Uri.parse("content://rcs/participant");
+
+ // Perform the initial query
+ RcsParticipantQueryParameters queryParameters =
+ new RcsParticipantQueryParameters.Builder().setAliasLike("%ali%").setSortProperty(
+ RcsParticipantQueryParameters.SORT_BY_ALIAS).setSortDirection(true)
+ .setResultLimit(1).build();
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(PARTICIPANT_QUERY_PARAMETERS_KEY, queryParameters);
+
+ Cursor cursor = mContentResolver.query(participantUri, null, bundle, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(0)).isEqualTo(1);
+ assertThat(cursor.getInt(1)).isEqualTo(99);
+ assertThat(cursor.getString(2)).isEqualTo("Some alias");
+
+ // Perform the continuation query
+ RcsQueryContinuationToken continuationToken = cursor.getExtras().getParcelable(
+ QUERY_CONTINUATION_TOKEN);
+ bundle.clear();
+ bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+
+ cursor = mContentResolver.query(participantUri, null, bundle, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(0)).isEqualTo(2);
+ assertThat(cursor.getInt(1)).isEqualTo(100);
+ assertThat(cursor.getString(2)).isEqualTo("Some other alias");
+
+ // Perform the continuation query to verify no entries left
+ continuationToken = cursor.getExtras().getParcelable(QUERY_CONTINUATION_TOKEN);
+ bundle.clear();
+ bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+
+ cursor = mContentResolver.query(participantUri, null, bundle, null);
+ assertThat(cursor.getCount()).isEqualTo(0);
+ continuationToken = cursor.getExtras().getParcelable(QUERY_CONTINUATION_TOKEN);
+ assertThat(continuationToken).isNull();
+ }
+
+ @Test
+ public void testQueryGroupParticipants() {
+ // TODO - implement
+ }
+
+ @Test
+ @Ignore // TODO: fix and un-ignore
+ public void testQueryEvents() {
+ RcsEventQueryParameters queryParameters = new RcsEventQueryParameters.Builder().build();
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(EVENT_QUERY_PARAMETERS_KEY, queryParameters);
+
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/event"), null, bundle,
+ null);
+ assertThat(cursor.getCount()).isEqualTo(2);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(EVENT_TYPE_COLUMN))).isEqualTo(
+ PARTICIPANT_JOINED_EVENT_TYPE);
+ assertThat(cursor.getInt(cursor.getColumnIndex(SOURCE_PARTICIPANT_ID_COLUMN))).isEqualTo(
+ 1);
+ assertThat(cursor.getInt(cursor.getColumnIndex(DESTINATION_PARTICIPANT_ID_COLUMN))).isEqualTo(
+ 2);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(EVENT_TYPE_COLUMN))).isEqualTo(
+ GROUP_THREAD_NAME_CHANGED_EVENT);
+ assertThat(cursor.getString(cursor.getColumnIndex(NEW_NAME_COLUMN))).isEqualTo(
+ "New group name");
+ }
+
+ @Test
+ @Ignore // TODO: fix and un-ignore
+ public void testQueryEventsWithContinuation() {
+ RcsEventQueryParameters queryParameters =
+ new RcsEventQueryParameters.Builder().setResultLimit(1).setSortDirection(true)
+ .build();
+
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(EVENT_QUERY_PARAMETERS_KEY, queryParameters);
+
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/event"), null, bundle,
+ null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(EVENT_TYPE_COLUMN))).isEqualTo(
+ GROUP_THREAD_NAME_CHANGED_EVENT);
+ assertThat(cursor.getString(cursor.getColumnIndex(NEW_NAME_COLUMN))).isEqualTo(
+ "New group name");
+ }
+
+ @Test
+ @Ignore // TODO: fix and un-ignore
+ public void testQueryEventsWithTypeLimitation() {
+ RcsEventQueryParameters queryParameters =
+ new RcsEventQueryParameters.Builder().setEventType(
+ GROUP_THREAD_NAME_CHANGED_EVENT).build();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(EVENT_QUERY_PARAMETERS_KEY, queryParameters);
+
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/event"), null, bundle,
+ null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(EVENT_TYPE_COLUMN))).isEqualTo(
+ GROUP_THREAD_NAME_CHANGED_EVENT);
+ assertThat(cursor.getString(cursor.getColumnIndex(NEW_NAME_COLUMN))).isEqualTo(
+ "New group name");
+ }
+
+ @Test
+ @Ignore // TODO: fix and un-ignore
+ public void testQueryMessages() {
+ RcsMessageQueryParameters queryParameters = new RcsMessageQueryParameters.Builder().build();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(MESSAGE_QUERY_PARAMETERS_KEY, queryParameters);
+
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, bundle,
+ null);
+
+ assertThat(cursor.getCount()).isEqualTo(3);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(3);
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(3);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(2);
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(1);
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(1);
+ }
+
+ @Test
+ @Ignore // TODO: fix and un-ignore
+ public void testQueryMessagesWithContinuation() {
+ RcsMessageQueryParameters queryParameters =
+ new RcsMessageQueryParameters.Builder().setMessageLike("%o%message").setResultLimit(
+ 1).setSortProperty(RcsMessageQueryParameters.SORT_BY_TIMESTAMP)
+ .setSortDirection(true).build();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(MESSAGE_QUERY_PARAMETERS_KEY, queryParameters);
+
+ // Perform the initial query
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, bundle,
+ null);
+
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(3);
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(3);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+ 200);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TYPE_COLUMN))).isEqualTo(
+ "Group message");
+
+ // Perform the continuation query
+ RcsQueryContinuationToken continuationToken = cursor.getExtras().getParcelable(
+ QUERY_CONTINUATION_TOKEN);
+ assertThat(continuationToken).isNotNull();
+ bundle.clear();
+ bundle.putParcelable(QUERY_CONTINUATION_TOKEN, continuationToken);
+
+ cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, bundle, null);
+
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(1);
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(1);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+ 300);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+ "Old message");
+ }
+
+ @Test
+ @Ignore // TODO: fix and un-ignore
+ public void testQueryMessagesWithThreadFilter() {
+ RcsMessageQueryParameters queryParameters =
+ new RcsMessageQueryParameters.Builder().setThread(new RcsGroupThread(3))
+ .build();
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(MESSAGE_QUERY_PARAMETERS_KEY, queryParameters);
+
+ // Perform the initial query
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/message"), null, bundle,
+ null);
+
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(cursor.getColumnIndex(MESSAGE_ID_COLUMN))).isEqualTo(3);
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_THREAD_ID_COLUMN))).isEqualTo(3);
+ assertThat(cursor.getInt(cursor.getColumnIndex(ORIGINATION_TIMESTAMP_COLUMN))).isEqualTo(
+ 200);
+ assertThat(cursor.getString(cursor.getColumnIndex(MESSAGE_TEXT_COLUMN))).isEqualTo(
+ "Group message");
+
+ }
+
+ @Test
+ public void testQueryParticipantOf1To1Thread() {
+ // insert the participant into a 1 to 1 thread
+ Uri insertUri = Uri.parse("content://rcs/p2p_thread/1/participant/1");
+ assertThat(mContentResolver.insert(insertUri, null)).isNotNull();
+
+ // query the participant back
+ Uri queryUri = Uri.parse("content://rcs/p2p_thread/1/participant");
+ Cursor cursor = mContentResolver.query(queryUri, null, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+
+ assertThat(cursor.getInt(1)).isEqualTo(99);
+ assertThat(cursor.getString(2)).isEqualTo("Some alias");
+ }
+
+ @Test
+ public void testQueryParticipantOfGroupThread() {
+ // query all the participants in this thread
+ Uri queryUri = Uri.parse("content://rcs/group_thread/3/participant");
+ Cursor cursor = mContentResolver.query(queryUri, null, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN))).isEqualTo(1);
+ assertThat(cursor.getInt(cursor.getColumnIndex(CANONICAL_ADDRESS_ID_COLUMN))).isEqualTo(99);
+ assertThat(cursor.getString(cursor.getColumnIndex(RCS_ALIAS_COLUMN))).isEqualTo(
+ "Some alias");
+ }
+
+ @Test
+ public void testQueryParticipantOfGroupThreadWithId() {
+ Cursor cursor = mContentResolver.query(
+ Uri.parse("content://rcs/group_thread/3/participant/1"), null, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+
+ assertThat(cursor.getInt(cursor.getColumnIndex(RCS_PARTICIPANT_ID_COLUMN))).isEqualTo(1);
+ assertThat(cursor.getInt(cursor.getColumnIndex(CANONICAL_ADDRESS_ID_COLUMN))).isEqualTo(99);
+ assertThat(cursor.getString(cursor.getColumnIndex(RCS_ALIAS_COLUMN))).isEqualTo(
+ "Some alias");
+ }
+
+ @Test
+ public void testQueryFileTransfer() {
+ ContentValues values = new ContentValues();
+ // add an incoming message to the thread 2
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/2/incoming_message"),
+ values)).isEqualTo(Uri.parse("content://rcs/p2p_thread/2/incoming_message/4"));
+
+ // add a file transfer
+ values.put(SESSION_ID_COLUMN, "session_id");
+ values.put(FILE_SIZE_COLUMN, 1234567890);
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/message/4/file_transfer"),
+ values)).isEqualTo(Uri.parse("content://rcs/file_transfer/1"));
+
+ // query the file transfer back
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/file_transfer/1"), null,
+ null, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(0)).isEqualTo(1);
+ assertThat(cursor.getInt(1)).isEqualTo(4);
+ assertThat(cursor.getString(2)).isEqualTo("session_id");
+ assertThat(cursor.getLong(5)).isEqualTo(1234567890);
+ }
+}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderTest.java b/tests/src/com/android/providers/telephony/RcsProviderTest.java
deleted file mode 100644
index 4074e77..0000000
--- a/tests/src/com/android/providers/telephony/RcsProviderTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.providers.telephony;
-
-import static com.android.providers.telephony.RcsProviderParticipantHelper.CANONICAL_ADDRESS_ID_COLUMN;
-import static com.android.providers.telephony.RcsProviderParticipantHelper.RCS_ALIAS_COLUMN;
-import static com.android.providers.telephony.RcsProviderThreadHelper.OWNER_PARTICIPANT;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.AppOpsManager;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.ProviderInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.telephony.TelephonyManager;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockContext;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-public class RcsProviderTest {
- private MockContentResolver mContentResolver;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- RcsProvider rcsProvider = new RcsProviderTestable();
- MockContextWithProvider context = new MockContextWithProvider(rcsProvider);
- mContentResolver = context.getContentResolver();
- }
-
- // TODO(sahinc): This test isn't that useful for now as it only checks the return value. Revisit
- // once we have more of the implementation in place.
- @Test
- public void testInsertThread() {
- ContentValues contentValues = new ContentValues();
- contentValues.put(OWNER_PARTICIPANT, 5);
-
- Uri uri = mContentResolver.insert(Uri.parse("content://rcs/thread"), contentValues);
- assertThat(uri).isEqualTo(Uri.parse("content://rcs/thread/1"));
- }
-
- @Test
- public void testUpdateThread() {
- ContentValues contentValues = new ContentValues();
- contentValues.put(OWNER_PARTICIPANT, 5);
- mContentResolver.insert(Uri.parse("content://rcs/thread"), contentValues);
-
- contentValues.put(OWNER_PARTICIPANT, 12);
- int updateCount = mContentResolver.update(Uri.parse("content://rcs/thread"),
- contentValues, "owner_participant=5", null);
-
- assertThat(updateCount).isEqualTo(1);
- }
-
- @Test
- public void testCanQueryAllThreads() {
- // insert two threads
- ContentValues contentValues = new ContentValues();
- Uri threadsUri = Uri.parse("content://rcs/thread");
- contentValues.put(OWNER_PARTICIPANT, 7);
- mContentResolver.insert(threadsUri, contentValues);
-
- contentValues.put(OWNER_PARTICIPANT, 13);
- mContentResolver.insert(threadsUri, contentValues);
-
- //verify two threads are inserted
- Cursor cursor = mContentResolver.query(threadsUri, null, null, null, null);
- assertThat(cursor.getCount()).isEqualTo(2);
- }
-
- @Test
- public void testInsertParticipant() {
- ContentValues contentValues = new ContentValues();
- contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 6);
- contentValues.put(RCS_ALIAS_COLUMN, "Alias");
-
- Uri uri = mContentResolver.insert(Uri.parse("content://rcs/participant"), contentValues);
- assertThat(uri).isEqualTo(Uri.parse("content://rcs/participant/1"));
- }
-
- @Test
- public void testUpdateParticipant() {
- // insert a participant
- Uri participantUri = Uri.parse("content://rcs/participant");
- ContentValues contentValues = new ContentValues();
- contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 11);
- contentValues.put(RCS_ALIAS_COLUMN, "Alias 1");
-
- mContentResolver.insert(participantUri, contentValues);
-
- // update the participant
- contentValues.clear();
- contentValues.put(RCS_ALIAS_COLUMN, "Alias 2");
-
- int updatedRowCount = mContentResolver.update(participantUri, contentValues, "rcs_alias=?",
- new String[]{"Alias 1"});
- assertThat(updatedRowCount).isEqualTo(1);
-
- // verify participant is actually updated
- Cursor cursor = mContentResolver.query(participantUri, new String[]{RCS_ALIAS_COLUMN},
- "rcs_alias=?", new String[]{"Alias 2"}, null);
- cursor.moveToNext();
- assertThat(cursor.getString(0)).isEqualTo("Alias 2");
- }
-
- @Test
- public void testQueryParticipant() {
- // insert a participant
- Uri participantUri = Uri.parse("content://rcs/participant");
- ContentValues contentValues = new ContentValues();
- contentValues.put(CANONICAL_ADDRESS_ID_COLUMN, 99);
- contentValues.put(RCS_ALIAS_COLUMN, "Some alias");
-
- mContentResolver.insert(participantUri, contentValues);
-
- // Query the participant back
- Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/participant"),
- new String[]{RCS_ALIAS_COLUMN}, null, null, null);
- cursor.moveToNext();
- assertThat(cursor.getString(0)).isEqualTo("Some alias");
- }
-
- class MockContextWithProvider extends MockContext {
- private final MockContentResolver mResolver;
-
- MockContextWithProvider(RcsProvider rcsProvider) {
- mResolver = new MockContentResolver();
-
- // Add authority="rcs" to given smsProvider
- ProviderInfo providerInfo = new ProviderInfo();
- providerInfo.authority = RcsProvider.AUTHORITY;
- rcsProvider.attachInfoForTesting(this, providerInfo);
- mResolver.addProvider(RcsProvider.AUTHORITY, rcsProvider);
- }
-
- @Override
- public MockContentResolver getContentResolver() {
- return mResolver;
- }
-
- @Override
- public Object getSystemService(String name) {
- switch (name) {
- case Context.APP_OPS_SERVICE:
- return Mockito.mock(AppOpsManager.class);
- case Context.TELEPHONY_SERVICE:
- return Mockito.mock(TelephonyManager.class);
- default:
- return null;
- }
- }
- }
-}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderTestable.java b/tests/src/com/android/providers/telephony/RcsProviderTestable.java
index 397d656..569fcdb 100644
--- a/tests/src/com/android/providers/telephony/RcsProviderTestable.java
+++ b/tests/src/com/android/providers/telephony/RcsProviderTestable.java
@@ -15,21 +15,44 @@
*/
package com.android.providers.telephony;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
+import android.test.mock.MockContext;
+
+import org.mockito.Mockito;
/**
* A subclass of RcsProvider used for testing on an in-memory database
*/
public class RcsProviderTestable extends RcsProvider {
+ private MockContextWithProvider mockContextWithProvider;
@Override
public boolean onCreate() {
+ mockContextWithProvider = new MockContextWithProvider(this);
mDbOpenHelper = new InMemoryRcsDatabase();
+ mParticipantHelper = new RcsProviderParticipantHelper(mDbOpenHelper);
+ mThreadHelper = new RcsProviderThreadHelper(mDbOpenHelper);
+ mMessageHelper = new RcsProviderMessageHelper(mDbOpenHelper);
+ mEventHelper = new RcsProviderEventHelper(mDbOpenHelper);
return true;
}
- static class InMemoryRcsDatabase extends SQLiteOpenHelper {
+ protected void tearDown() {
+ mDbOpenHelper.close();
+ }
+
+ public SQLiteDatabase getWritableDatabase() {
+ return mDbOpenHelper.getWritableDatabase();
+ }
+
+ class InMemoryRcsDatabase extends SQLiteOpenHelper {
InMemoryRcsDatabase() {
super(null, // no context is needed for in-memory db
null, // db file name is null for in-memory db
@@ -39,8 +62,16 @@
@Override
public void onCreate(SQLiteDatabase db) {
+ MmsSmsDatabaseHelper mmsSmsDatabaseHelper = new MmsSmsDatabaseHelper(
+ mockContextWithProvider, null);
+ mmsSmsDatabaseHelper.createMmsTables(db);
+ mmsSmsDatabaseHelper.createSmsTables(db);
+ mmsSmsDatabaseHelper.createCommonTables(db);
+
RcsProviderThreadHelper.createThreadTables(db);
RcsProviderParticipantHelper.createParticipantTables(db);
+ RcsProviderMessageHelper.createRcsMessageTables(db);
+ RcsProviderEventHelper.createRcsEventTables(db);
}
@Override
@@ -48,4 +79,45 @@
// no-op
}
}
+
+ static class MockContextWithProvider extends MockContext {
+ private final MockContentResolver mResolver;
+
+ MockContextWithProvider(RcsProvider rcsProvider) {
+ mResolver = new MockContentResolver();
+
+ // Add authority="rcs" to given smsProvider
+ ProviderInfo providerInfo = new ProviderInfo();
+ providerInfo.authority = RcsProvider.AUTHORITY;
+ rcsProvider.attachInfoForTesting(this, providerInfo);
+ mResolver.addProvider(RcsProvider.AUTHORITY, rcsProvider);
+ }
+
+ @Override
+ public MockContentResolver getContentResolver() {
+ return mResolver;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return Mockito.mock(PackageManager.class);
+ }
+
+ @Override
+ public Object getSystemService(String name) {
+ switch (name) {
+ case Context.APP_OPS_SERVICE:
+ return Mockito.mock(AppOpsManager.class);
+ case Context.TELEPHONY_SERVICE:
+ return Mockito.mock(TelephonyManager.class);
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isCredentialProtectedStorage() {
+ return false;
+ }
+ }
}
diff --git a/tests/src/com/android/providers/telephony/RcsProviderUpdateTest.java b/tests/src/com/android/providers/telephony/RcsProviderUpdateTest.java
new file mode 100644
index 0000000..28954c9
--- /dev/null
+++ b/tests/src/com/android/providers/telephony/RcsProviderUpdateTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.providers.telephony;
+
+import static android.provider.Telephony.RcsColumns.Rcs1To1ThreadColumns.FALLBACK_THREAD_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.HEIGHT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsFileTransferColumns.WIDTH_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.GROUP_NAME_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsGroupThreadColumns.OWNER_PARTICIPANT_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsIncomingMessageColumns.ARRIVAL_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageColumns.ORIGINATION_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns.DELIVERED_TIMESTAMP_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.CANONICAL_ADDRESS_ID_COLUMN;
+import static android.provider.Telephony.RcsColumns.RcsParticipantColumns.RCS_ALIAS_COLUMN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Telephony.RcsColumns.RcsMessageDeliveryColumns;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.mock.MockContentResolver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsProviderUpdateTest {
+ private MockContentResolver mContentResolver;
+ private RcsProviderTestable mRcsProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mRcsProvider = new RcsProviderTestable();
+ RcsProviderTestable.MockContextWithProvider
+ context = new RcsProviderTestable.MockContextWithProvider(mRcsProvider);
+ mContentResolver = context.getContentResolver();
+
+ // insert a participant
+ // first into the MmsSmsProvider
+ mRcsProvider.getWritableDatabase().execSQL(
+ "INSERT INTO canonical_addresses VALUES (1, \"+15551234567\")");
+
+ // then into the RcsProvider
+ ContentValues participantValues = new ContentValues();
+ participantValues.put(RCS_ALIAS_COLUMN, "Bob");
+ participantValues.put(CANONICAL_ADDRESS_ID_COLUMN, 1);
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/participant"),
+ participantValues)).isEqualTo(Uri.parse("content://rcs/participant/1"));
+
+ // insert fallback threads
+ mRcsProvider.getWritableDatabase().execSQL("INSERT INTO threads(_id) VALUES (1)");
+ mRcsProvider.getWritableDatabase().execSQL("INSERT INTO threads(_id) VALUES (2)");
+
+ // insert one 1 to 1 thread
+ ContentValues p2pContentValues = new ContentValues();
+ Uri p2pThreadUri = Uri.parse("content://rcs/p2p_thread");
+ p2pContentValues.put(FALLBACK_THREAD_ID_COLUMN, 1);
+ assertThat(mContentResolver.insert(p2pThreadUri, p2pContentValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1"));
+
+ // insert one group thread
+ ContentValues groupContentValues = new ContentValues();
+ groupContentValues.put(OWNER_PARTICIPANT_COLUMN, 1);
+ groupContentValues.put(GROUP_NAME_COLUMN, "Name");
+ Uri groupThreadUri = Uri.parse("content://rcs/group_thread");
+ assertThat(mContentResolver.insert(groupThreadUri, groupContentValues)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/2"));
+
+ // Add participant to both threads
+ Uri p2pInsertionUri = Uri.parse("content://rcs/p2p_thread/1/participant/1");
+ assertThat(mContentResolver.insert(p2pInsertionUri, null)).isEqualTo(p2pInsertionUri);
+
+ Uri groupInsertionUri = Uri.parse("content://rcs/group_thread/2/participant/1");
+ assertThat(mContentResolver.insert(groupInsertionUri, null)).isEqualTo(groupInsertionUri);
+
+ // add incoming and outgoing messages to both threads
+ ContentValues messageValues = new ContentValues();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"));
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/p2p_thread/1/outgoing_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/p2p_thread/1/outgoing_message/2"));
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/incoming_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/2/incoming_message/3"));
+ assertThat(
+ mContentResolver.insert(Uri.parse("content://rcs/group_thread/2/outgoing_message"),
+ messageValues)).isEqualTo(
+ Uri.parse("content://rcs/group_thread/2/outgoing_message/4"));
+
+ // add message delivery to the outgoing messages
+ ContentValues deliveryValues = new ContentValues();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message/2/delivery/1"),
+ deliveryValues)).isEqualTo(
+ Uri.parse("content://rcs/outgoing_message/2/delivery/1"));
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/outgoing_message/4/delivery/1"),
+ deliveryValues)).isEqualTo(
+ Uri.parse("content://rcs/outgoing_message/4/delivery/1"));
+
+ // add a file transfer to an incoming message
+ ContentValues fileTransferValues = new ContentValues();
+ assertThat(mContentResolver.insert(Uri.parse("content://rcs/message/3/file_transfer"),
+ fileTransferValues)).isEqualTo(Uri.parse("content://rcs/file_transfer/1"));
+ }
+
+ @After
+ public void tearDown() {
+ mRcsProvider.tearDown();
+ }
+
+ @Test
+ public void testUpdate1To1ThreadWithSelection() {
+ // update the fallback thread id
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(FALLBACK_THREAD_ID_COLUMN, 2);
+ Uri p2pThreadUri = Uri.parse("content://rcs/p2p_thread");
+
+ assertThat(mContentResolver.update(p2pThreadUri, contentValues, "rcs_fallback_thread_id=1",
+ null)).isEqualTo(1);
+
+ // verify the thread is actually updated
+ Cursor cursor = mContentResolver.query(p2pThreadUri,
+ new String[]{FALLBACK_THREAD_ID_COLUMN}, "rcs_fallback_thread_id=2", null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(0)).isEqualTo(2);
+ }
+
+ @Test
+ public void testUpdate1To1ThreadWithId() {
+ // update the fallback thread id
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(FALLBACK_THREAD_ID_COLUMN, 2);
+ Uri p2pThreadUri = Uri.parse("content://rcs/p2p_thread/1");
+ assertThat(mContentResolver.update(p2pThreadUri, contentValues, null, null)).isEqualTo(1);
+
+ // verify the thread is actually updated
+ Cursor cursor = mContentResolver.query(p2pThreadUri,
+ new String[]{FALLBACK_THREAD_ID_COLUMN}, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getInt(0)).isEqualTo(2);
+ }
+
+ @Test
+ public void testUpdateGroupThreadWithSelection() {
+ // update the group name
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(GROUP_NAME_COLUMN, "New name");
+ Uri groupThreadUri = Uri.parse("content://rcs/group_thread");
+ assertThat(mContentResolver.update(groupThreadUri, contentValues, "group_name=\"Name\"",
+ null)).isEqualTo(1);
+
+ // verify the thread is actually updated
+ Cursor cursor = mContentResolver.query(groupThreadUri, new String[]{GROUP_NAME_COLUMN},
+ "group_name=\"New name\"", null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+
+ cursor.moveToNext();
+ assertThat(cursor.getString(0)).isEqualTo("New name");
+ }
+
+ @Test
+ public void testUpdateGroupThreadWithId() {
+ // update the group name
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(GROUP_NAME_COLUMN, "New name");
+ Uri groupThreadUri = Uri.parse("content://rcs/group_thread/2");
+ assertThat(mContentResolver.update(groupThreadUri, contentValues, null, null)).isEqualTo(1);
+
+ // verify the thread is actually updated
+ Cursor cursor = mContentResolver.query(groupThreadUri, new String[]{GROUP_NAME_COLUMN},
+ null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getString(0)).isEqualTo("New name");
+ }
+
+ @Test
+ public void testUpdateParticipantWithId() {
+ // change the participant name from Bob to Bobby
+ ContentValues contentValues = new ContentValues(1);
+ contentValues.put(RCS_ALIAS_COLUMN, "Bobby");
+
+ Uri participantUri = Uri.parse("content://rcs/participant/1");
+
+ assertThat(mContentResolver.update(participantUri, contentValues, null, null)).isEqualTo(1);
+
+ // verify participant is actually updated
+ Cursor cursor = mContentResolver.query(participantUri, new String[]{RCS_ALIAS_COLUMN}, null,
+ null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getString(0)).isEqualTo("Bobby");
+ }
+
+ @Test
+ public void testUpdate1To1ThreadParticipantFails() {
+ assertThat(
+ mContentResolver.update(Uri.parse("content://rcs/p2p_thread/1/participant/1"), null,
+ null, null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testUpdateGroupParticipantFails() {
+ assertThat(mContentResolver.update(Uri.parse("content://rcs/group_thread/2/participant/1"),
+ null, null, null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testUpdateUnifiedMessageViewFails() {
+ ContentValues updateValues = new ContentValues();
+ updateValues.put(ORIGINATION_TIMESTAMP_COLUMN, 1234567890);
+
+ assertThat(mContentResolver.update(Uri.parse("content://rcs/message"), updateValues, null,
+ null)).isEqualTo(0);
+ assertThat(mContentResolver.update(Uri.parse("content://rcs/message/1"), updateValues, null,
+ null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testUpdateMessageOnThreadFails() {
+ ContentValues updateValues = new ContentValues();
+ updateValues.put(ORIGINATION_TIMESTAMP_COLUMN, 1234567890);
+
+ assertThat(mContentResolver.update(Uri.parse("content://rcs/p2p_thread/1/incoming_message"),
+ updateValues, null, null)).isEqualTo(0);
+ assertThat(
+ mContentResolver.update(Uri.parse("content://rcs/p2p_thread/1/incoming_message/1"),
+ updateValues, null, null)).isEqualTo(0);
+ assertThat(
+ mContentResolver.update(Uri.parse("content://rcs/group_thread/2/outgoing_message"),
+ updateValues, null, null)).isEqualTo(0);
+ assertThat(mContentResolver.update(
+ Uri.parse("content://rcs/groupp_thread/2/outgoing_message/1"), updateValues, null,
+ null)).isEqualTo(0);
+ }
+
+ @Test
+ public void testUpdateMessage() {
+ // update the message
+ ContentValues updateValues = new ContentValues(1);
+ updateValues.put(ORIGINATION_TIMESTAMP_COLUMN, 1234567890);
+ assertThat(
+ mContentResolver.update(Uri.parse("content://rcs/outgoing_message/2"), updateValues,
+ null, null)).isEqualTo(1);
+
+ // verify the value is actually updated
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/outgoing_message/2"), null,
+ null, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getLong(5)).isEqualTo(1234567890);
+ cursor.close();
+ }
+
+ @Test
+ @Ignore // TODO: fix and un-ignore
+ public void testUpdateIncomingMessageSpecificColumn() {
+ // update the message
+ ContentValues updateValues = new ContentValues(1);
+ updateValues.put(ARRIVAL_TIMESTAMP_COLUMN, 987654321);
+ assertThat(
+ mContentResolver.update(Uri.parse("content://rcs/incoming_message/3"), updateValues,
+ null, null)).isEqualTo(1);
+
+ // verify the value is actually updated
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/incoming_message/3"), null,
+ null, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getLong(7)).isEqualTo(987654321);
+ cursor.close();
+ }
+
+ @Test
+ public void testUpdateMessageDelivery() {
+ ContentValues updateValues = new ContentValues();
+ updateValues.put(DELIVERED_TIMESTAMP_COLUMN, 12345);
+ updateValues.put(RcsMessageDeliveryColumns.SEEN_TIMESTAMP_COLUMN, 54321);
+
+ assertThat(mContentResolver.update(Uri.parse("content://rcs/outgoing_message/2/delivery/1"),
+ updateValues, null, null)).isEqualTo(1);
+
+ // verify the value is actually updated
+ Cursor cursor = mContentResolver.query(
+ Uri.parse("content://rcs/outgoing_message/2/delivery"), null, null, null, null,
+ null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(0)).isEqualTo(2);
+ assertThat(cursor.getInt(1)).isEqualTo(1);
+ assertThat(cursor.getLong(2)).isEqualTo(12345);
+ assertThat(cursor.getLong(3)).isEqualTo(54321);
+ }
+
+ @Test
+ public void testUpdateFileTransfer() {
+ ContentValues updateValues = new ContentValues();
+ updateValues.put(WIDTH_COLUMN, 640);
+ updateValues.put(HEIGHT_COLUMN, 480);
+
+ assertThat(mContentResolver.update(Uri.parse("content://rcs/file_transfer/1"), updateValues,
+ null, null)).isEqualTo(1);
+
+ // verify that the values are actually updated
+ Cursor cursor = mContentResolver.query(Uri.parse("content://rcs/file_transfer/1"), null,
+ null, null, null, null);
+ assertThat(cursor.getCount()).isEqualTo(1);
+ cursor.moveToNext();
+ assertThat(cursor.getInt(8)).isEqualTo(640);
+ assertThat(cursor.getInt(9)).isEqualTo(480);
+ }
+}