Implement RcsParticipant (provider)

This change adds the tables, APIs and provider for RcsParticipants. It
is now possible to add/update/query participants.

Test: Added unit tests

Bug: 109759350
Change-Id: Ibe9af508aaf6c3747f3121a3a50247d08bdf25eb
diff --git a/src/com/android/providers/telephony/MmsSmsProvider.java b/src/com/android/providers/telephony/MmsSmsProvider.java
index 1653cd9..e2e9a1c 100644
--- a/src/com/android/providers/telephony/MmsSmsProvider.java
+++ b/src/com/android/providers/telephony/MmsSmsProvider.java
@@ -1272,10 +1272,15 @@
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
-        if (URI_MATCHER.match(uri) == URI_PENDING_MSG) {
-            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        int matchIndex = URI_MATCHER.match(uri);
+
+        if (matchIndex == URI_PENDING_MSG) {
             long rowId = db.insert(TABLE_PENDING_MSG, null, values);
-            return Uri.parse(uri + "/" + rowId);
+            return uri.buildUpon().appendPath(Long.toString(rowId)).build();
+        } else if (matchIndex == URI_CANONICAL_ADDRESS) {
+            long rowId = db.insert(TABLE_CANONICAL_ADDRESSES, null, values);
+            return uri.buildUpon().appendPath(Long.toString(rowId)).build();
         }
         throw new UnsupportedOperationException(NO_DELETES_INSERTS_OR_UPDATES + uri);
     }
diff --git a/src/com/android/providers/telephony/RcsProvider.java b/src/com/android/providers/telephony/RcsProvider.java
index c0fffd0..af9212e 100644
--- a/src/com/android/providers/telephony/RcsProvider.java
+++ b/src/com/android/providers/telephony/RcsProvider.java
@@ -35,8 +35,12 @@
 public class RcsProvider extends ContentProvider {
     static final String TAG = "RcsProvider";
     static final String AUTHORITY = "rcs";
-    private static final UriMatcher URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
 
+    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 UriMatcher URL_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
     private static final int THREAD = 1;
     private static final int PARTICIPANT = 2;
 
@@ -88,13 +92,20 @@
         SQLiteDatabase writableDatabase = mDbOpenHelper.getWritableDatabase();
         writableDatabase.beginTransaction();
 
+        Uri returnUri = null;
+        long rowId;
+
         try {
             switch (match) {
                 case THREAD:
-                    RcsProviderThreadHelper.insert(writableDatabase, values);
+                    rowId = RcsProviderThreadHelper.insert(writableDatabase, values);
+                    returnUri = THREAD_URI_PREFIX.buildUpon().appendPath(
+                            Long.toString(rowId)).build();
                     break;
                 case PARTICIPANT:
-                    RcsProviderParticipantHelper.insert(writableDatabase, values);
+                    rowId = RcsProviderParticipantHelper.insert(writableDatabase, values);
+                    returnUri = PARTICIPANT_URI_PREFIX.buildUpon().appendPath(
+                            Long.toString(rowId)).build();
                     break;
                 default:
                     Log.e(TAG, "Invalid insert: " + uri);
@@ -103,7 +114,7 @@
             writableDatabase.endTransaction();
         }
 
-        return null;
+        return returnUri;
     }
 
     @Override
diff --git a/src/com/android/providers/telephony/RcsProviderParticipantHelper.java b/src/com/android/providers/telephony/RcsProviderParticipantHelper.java
index 913b8eb..d6ae4f7 100644
--- a/src/com/android/providers/telephony/RcsProviderParticipantHelper.java
+++ b/src/com/android/providers/telephony/RcsProviderParticipantHelper.java
@@ -15,15 +15,14 @@
  */
 package com.android.providers.telephony;
 
-import static com.android.providers.telephony.RcsProvider.TAG;
+import static com.android.providers.telephony.RcsProviderThreadHelper.RCS_THREAD_ID_COLUMN;
+import static com.android.providers.telephony.RcsProviderThreadHelper.THREAD_TABLE;
+
 import android.content.ContentValues;
-import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteQueryBuilder;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.ims.RcsMessageStoreController;
 
 /**
  * Constants and helpers related to participants for {@link RcsProvider} to keep the code clean.
@@ -36,10 +35,8 @@
     static final String CANONICAL_ADDRESS_ID_COLUMN = "canonical_address_id";
     static final String RCS_ALIAS_COLUMN = "rcs_alias";
 
-    static final String CANONICAL_ADDRESSES_TABLE = "canonical_addresses";
-    static final String ADDRESS_COLUMN = "address";
-
-    private static final int NO_EXISTING_ADDRESS = Integer.MIN_VALUE;
+    static final String PARTICIPANT_THREAD_JUNCTION_TABLE = "rcs_thread_participant";
+    static final String RCS_PARTICIPANT_ID_COLUMN = "rcs_participant_id";
 
     @VisibleForTesting
     public static void createParticipantTables(SQLiteDatabase db) {
@@ -50,14 +47,26 @@
                 "FOREIGN KEY(" + CANONICAL_ADDRESS_ID_COLUMN + ") "
                 + "REFERENCES canonical_addresses(address)" +
                 ");");
+
+        db.execSQL("CREATE TABLE " + 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 + "), " +
+                "FOREIGN KEY(" + RCS_PARTICIPANT_ID_COLUMN
+                + ") REFERENCES " + PARTICIPANT_TABLE + "(" + ID_COLUMN + "))");
     }
 
     static void buildParticipantsQuery(SQLiteQueryBuilder qb) {
         qb.setTables(PARTICIPANT_TABLE);
     }
 
-    static void insert(SQLiteDatabase db, ContentValues values) {
-        // TODO - implement
+    static long insert(SQLiteDatabase db, ContentValues values) {
+        long rowId = db.insert(PARTICIPANT_TABLE, ID_COLUMN, values);
+        db.setTransactionSuccessful();
+        return rowId;
     }
 
     static int delete(SQLiteDatabase db, String selection,
@@ -73,33 +82,4 @@
         db.setTransactionSuccessful();
         return updatedRows;
     }
-
-    private static int getCanonicalAddressId(SQLiteDatabase db, ContentValues values) {
-        String address = values.getAsString(RcsMessageStoreController.PARTICIPANT_ADDRESS_KEY);
-
-        // see if the address already exists
-        // TODO(sahinc) - refine this to match phone number formats in canonical addresses, or find
-        // TODO(sahinc) - another solution.
-        Cursor existingCanonicalAddressAsCursor = db.query(CANONICAL_ADDRESSES_TABLE,
-                new String[]{ID_COLUMN}, ADDRESS_COLUMN + "=" + address,
-                null, null, null, null);
-        long canonicalAddressId = NO_EXISTING_ADDRESS;
-
-        if (existingCanonicalAddressAsCursor != null
-                && existingCanonicalAddressAsCursor.moveToFirst()) {
-            canonicalAddressId = existingCanonicalAddressAsCursor.getLong(0);
-            existingCanonicalAddressAsCursor.close();
-        }
-
-        // If there is no existing canonical address, add one.
-        if (canonicalAddressId == NO_EXISTING_ADDRESS) {
-            canonicalAddressId = db.insert(CANONICAL_ADDRESSES_TABLE, ADDRESS_COLUMN, values);
-        }
-
-        if (canonicalAddressId == NO_EXISTING_ADDRESS) {
-            Log.e(TAG, "Could not create an entry in canonical addresses");
-        }
-
-        return (int) canonicalAddressId;
-    }
 }
diff --git a/src/com/android/providers/telephony/RcsProviderThreadHelper.java b/src/com/android/providers/telephony/RcsProviderThreadHelper.java
index f993b2e..e643136 100644
--- a/src/com/android/providers/telephony/RcsProviderThreadHelper.java
+++ b/src/com/android/providers/telephony/RcsProviderThreadHelper.java
@@ -31,21 +31,48 @@
     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";
+
     @VisibleForTesting
     public static void createThreadTables(SQLiteDatabase db) {
         db.execSQL("CREATE TABLE " + THREAD_TABLE + " (" +
                 ID_COLUMN + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                 OWNER_PARTICIPANT + " INTEGER " +
                 ");");
+
+        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 + ")," +
+                "FOREIGN KEY(" + FALLBACK_THREAD_ID_COLUMN
+                    + ") REFERENCES threads( " + ID_COLUMN + "))" );
+
+        db.execSQL("CREATE TABLE " + RCS_GROUP_THREAD_TABLE + " (" +
+                RCS_THREAD_ID_COLUMN + " INTEGER PRIMARY KEY, " +
+                GROUP_NAME_COLUMN + " TEXT, " +
+                ICON_COLUMN + " TEXT, " +
+                CONFERENCE_URI_COLUMN + " TEXT, " +
+                "FOREIGN KEY(" + RCS_THREAD_ID_COLUMN
+                    + ") REFERENCES " + THREAD_TABLE + "(" + ID_COLUMN + "))" );
+
     }
 
     static void buildThreadQuery(SQLiteQueryBuilder qb) {
         qb.setTables(THREAD_TABLE);
     }
 
-    static void insert(SQLiteDatabase db, ContentValues values) {
-        db.insert(THREAD_TABLE, OWNER_PARTICIPANT, values);
+    static long insert(SQLiteDatabase db, ContentValues values) {
+        long rowId = db.insert(THREAD_TABLE, OWNER_PARTICIPANT, values);
         db.setTransactionSuccessful();
+        return rowId;
     }
 
     static int delete(SQLiteDatabase db, String selection, String[] selectionArgs) {
diff --git a/tests/src/com/android/providers/telephony/RcsProviderTest.java b/tests/src/com/android/providers/telephony/RcsProviderTest.java
index 2417f51..4074e77 100644
--- a/tests/src/com/android/providers/telephony/RcsProviderTest.java
+++ b/tests/src/com/android/providers/telephony/RcsProviderTest.java
@@ -15,6 +15,10 @@
  */
 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;
@@ -48,19 +52,19 @@
     @Test
     public void testInsertThread() {
         ContentValues contentValues = new ContentValues();
-        contentValues.put(RcsProviderThreadHelper.OWNER_PARTICIPANT, 5);
+        contentValues.put(OWNER_PARTICIPANT, 5);
 
         Uri uri = mContentResolver.insert(Uri.parse("content://rcs/thread"), contentValues);
-        assertThat(uri).isNull();
+        assertThat(uri).isEqualTo(Uri.parse("content://rcs/thread/1"));
     }
 
     @Test
     public void testUpdateThread() {
         ContentValues contentValues = new ContentValues();
-        contentValues.put(RcsProviderThreadHelper.OWNER_PARTICIPANT, 5);
+        contentValues.put(OWNER_PARTICIPANT, 5);
         mContentResolver.insert(Uri.parse("content://rcs/thread"), contentValues);
 
-        contentValues.put(RcsProviderThreadHelper.OWNER_PARTICIPANT, 12);
+        contentValues.put(OWNER_PARTICIPANT, 12);
         int updateCount = mContentResolver.update(Uri.parse("content://rcs/thread"),
                 contentValues, "owner_participant=5", null);
 
@@ -72,10 +76,10 @@
         // insert two threads
         ContentValues contentValues = new ContentValues();
         Uri threadsUri = Uri.parse("content://rcs/thread");
-        contentValues.put(RcsProviderThreadHelper.OWNER_PARTICIPANT, 7);
+        contentValues.put(OWNER_PARTICIPANT, 7);
         mContentResolver.insert(threadsUri, contentValues);
 
-        contentValues.put(RcsProviderThreadHelper.OWNER_PARTICIPANT, 13);
+        contentValues.put(OWNER_PARTICIPANT, 13);
         mContentResolver.insert(threadsUri, contentValues);
 
         //verify two threads are inserted
@@ -83,6 +87,58 @@
         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;
 
diff --git a/tests/src/com/android/providers/telephony/RcsProviderTestable.java b/tests/src/com/android/providers/telephony/RcsProviderTestable.java
index 64ae3ef..397d656 100644
--- a/tests/src/com/android/providers/telephony/RcsProviderTestable.java
+++ b/tests/src/com/android/providers/telephony/RcsProviderTestable.java
@@ -31,7 +31,7 @@
 
     static class InMemoryRcsDatabase extends SQLiteOpenHelper {
         InMemoryRcsDatabase() {
-            super(null,      // no context is needed for in-memory db
+            super(null,        // no context is needed for in-memory db
                     null,      // db file name is null for in-memory db
                     null,      // CursorFactory is null by default
                     1);        // db version is no-op for tests
@@ -40,6 +40,7 @@
         @Override
         public void onCreate(SQLiteDatabase db) {
             RcsProviderThreadHelper.createThreadTables(db);
+            RcsProviderParticipantHelper.createParticipantTables(db);
         }
 
         @Override