Cleaning up Presence API
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 951764b..4ceca6f 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -28,6 +28,7 @@
 import com.android.providers.contacts.OpenHelper.PackagesColumns;
 import com.android.providers.contacts.OpenHelper.PhoneColumns;
 import com.android.providers.contacts.OpenHelper.PhoneLookupColumns;
+import com.android.providers.contacts.OpenHelper.PresenceColumns;
 import com.android.providers.contacts.OpenHelper.RawContactsColumns;
 import com.android.providers.contacts.OpenHelper.Tables;
 import com.google.android.collect.Lists;
@@ -583,11 +584,12 @@
 
         columns = new HashMap<String, String>();
         columns.put(Presence._ID, Presence._ID);
-        columns.put(Presence.RAW_CONTACT_ID, Presence.RAW_CONTACT_ID);
+        columns.put(PresenceColumns.RAW_CONTACT_ID, PresenceColumns.RAW_CONTACT_ID);
         columns.put(Presence.DATA_ID, Presence.DATA_ID);
         columns.put(Presence.IM_ACCOUNT, Presence.IM_ACCOUNT);
         columns.put(Presence.IM_HANDLE, Presence.IM_HANDLE);
-        columns.put(Presence.IM_PROTOCOL, Presence.IM_PROTOCOL);
+        columns.put(Presence.PROTOCOL, Presence.PROTOCOL);
+        columns.put(Presence.CUSTOM_PROTOCOL, Presence.CUSTOM_PROTOCOL);
         columns.put(Presence.PRESENCE_STATUS, Presence.PRESENCE_STATUS);
         columns.put(Presence.PRESENCE_CUSTOM_STATUS, Presence.PRESENCE_CUSTOM_STATUS);
         sPresenceProjectionMap = columns;
@@ -1540,23 +1542,51 @@
      */
     public long insertPresence(ContentValues values) {
         final String handle = values.getAsString(Presence.IM_HANDLE);
-        final String protocol = values.getAsString(Presence.IM_PROTOCOL);
-        if (TextUtils.isEmpty(handle) || TextUtils.isEmpty(protocol)) {
-            throw new IllegalArgumentException("IM_PROTOCOL and IM_HANDLE are required");
+        if (TextUtils.isEmpty(handle) || !values.containsKey(Presence.PROTOCOL)) {
+            throw new IllegalArgumentException("PROTOCOL and IM_HANDLE are required");
+        }
+
+        final long protocol = values.getAsLong(Presence.PROTOCOL);
+        String customProtocol = null;
+
+        if (protocol == Im.PROTOCOL_CUSTOM) {
+            customProtocol = values.getAsString(Presence.CUSTOM_PROTOCOL);
+            if (TextUtils.isEmpty(customProtocol)) {
+                throw new IllegalArgumentException(
+                        "CUSTOM_PROTOCOL is required when PROTOCOL=PROTOCOL_CUSTOM");
+            }
         }
 
         // TODO: generalize to allow other providers to match against email
-        boolean matchEmail = Im.PROTOCOL_GOOGLE_TALK == Integer.parseInt(protocol);
+        boolean matchEmail = Im.PROTOCOL_GOOGLE_TALK == protocol;
 
         StringBuilder selection = new StringBuilder();
         String[] selectionArgs;
         if (matchEmail) {
-            selection.append("(" + Clauses.WHERE_IM_MATCHES + ") OR ("
-                    + Clauses.WHERE_EMAIL_MATCHES + ")");
-            selectionArgs = new String[] { protocol, handle, handle };
+            selection.append(
+                    "((" + MimetypesColumns.MIMETYPE + "='" + Im.CONTENT_ITEM_TYPE + "'"
+                    + " AND " + Im.PROTOCOL + "=?"
+                    + " AND " + Im.DATA + "=?");
+            if (customProtocol != null) {
+                selection.append(" AND " + Im.CUSTOM_PROTOCOL + "=");
+                DatabaseUtils.appendEscapedSQLString(selection, customProtocol);
+            }
+            selection.append(") OR ("
+                    + MimetypesColumns.MIMETYPE + "='" + Email.CONTENT_ITEM_TYPE + "'"
+                    + " AND " + Email.DATA + "=?"
+                    + "))");
+            selectionArgs = new String[] { String.valueOf(protocol), handle, handle };
         } else {
-            selection.append(Clauses.WHERE_IM_MATCHES);
-            selectionArgs = new String[] { protocol, handle };
+            selection.append(
+                    MimetypesColumns.MIMETYPE + "='" + Im.CONTENT_ITEM_TYPE + "'"
+                    + " AND " + Im.PROTOCOL + "=?"
+                    + " AND " + Im.DATA + "=?");
+            if (customProtocol != null) {
+                selection.append(" AND " + Im.CUSTOM_PROTOCOL + "=");
+                DatabaseUtils.appendEscapedSQLString(selection, customProtocol);
+            }
+
+            selectionArgs = new String[] { String.valueOf(protocol), handle };
         }
 
         if (values.containsKey(Presence.DATA_ID)) {
@@ -1564,12 +1594,6 @@
                     .append(values.getAsLong(Presence.DATA_ID));
         }
 
-        // TODO remove this capability
-        if (values.containsKey(Presence.RAW_CONTACT_ID)) {
-            selection.append(" AND " + DataColumns.CONCRETE_RAW_CONTACT_ID + "=")
-                    .append(values.getAsLong(Presence.RAW_CONTACT_ID));
-        }
-
         selection.append(" AND ").append(getContactsRestrictions());
 
         long dataId = -1;
@@ -1595,7 +1619,7 @@
         }
 
         values.put(Presence.DATA_ID, dataId);
-        values.put(Presence.RAW_CONTACT_ID, rawContactId);
+        values.put(PresenceColumns.RAW_CONTACT_ID, rawContactId);
 
         // Insert the presence update
         long presenceId = mDb.replace(Tables.PRESENCE, null, values);
diff --git a/src/com/android/providers/contacts/OpenHelper.java b/src/com/android/providers/contacts/OpenHelper.java
index e2ff79b..006a978 100644
--- a/src/com/android/providers/contacts/OpenHelper.java
+++ b/src/com/android/providers/contacts/OpenHelper.java
@@ -195,12 +195,6 @@
     }
 
     public interface Clauses {
-        public static final String WHERE_IM_MATCHES = MimetypesColumns.MIMETYPE + "='"
-                + Im.CONTENT_ITEM_TYPE + "' AND " + Im.PROTOCOL + "=? AND " + Im.DATA + "=?";
-
-        public static final String WHERE_EMAIL_MATCHES = MimetypesColumns.MIMETYPE + "='"
-                + Email.CONTENT_ITEM_TYPE + "' AND " + Email.DATA + "=?";
-
         public static final String MIMETYPE_IS_GROUP_MEMBERSHIP = MimetypesColumns.CONCRETE_MIMETYPE
                 + "='" + GroupMembership.CONTENT_ITEM_TYPE + "'";
 
@@ -382,6 +376,10 @@
         public static final String CLUSTER = "cluster";
     }
 
+    public interface PresenceColumns {
+        String RAW_CONTACT_ID = "presence_raw_contact_id";
+    }
+
     public interface AggregatedPresenceColumns {
         String CONTACT_ID = "presence_contact_id";
     }
@@ -489,19 +487,20 @@
         db.execSQL("ATTACH DATABASE ':memory:' AS " + DATABASE_PRESENCE + ";");
         db.execSQL("CREATE TABLE IF NOT EXISTS " + DATABASE_PRESENCE + "." + Tables.PRESENCE + " ("+
                 Presence._ID + " INTEGER PRIMARY KEY," +
-                Presence.RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)," +
+                PresenceColumns.RAW_CONTACT_ID + " INTEGER REFERENCES raw_contacts(_id)," +
                 Presence.DATA_ID + " INTEGER REFERENCES data(_id)," +
-                Presence.IM_PROTOCOL + " TEXT," +
+                Presence.PROTOCOL + " INTEGER NOT NULL," +
+                Presence.CUSTOM_PROTOCOL + " TEXT," +
                 Presence.IM_HANDLE + " TEXT," +
                 Presence.IM_ACCOUNT + " TEXT," +
                 Presence.PRESENCE_STATUS + " INTEGER," +
                 Presence.PRESENCE_CUSTOM_STATUS + " TEXT," +
-                "UNIQUE(" + Presence.IM_PROTOCOL + ", " + Presence.IM_HANDLE + ", "
-                        + Presence.IM_ACCOUNT + ")" +
+                "UNIQUE(" + Presence.PROTOCOL + ", " + Presence.CUSTOM_PROTOCOL
+                    + ", " + Presence.IM_HANDLE + ", " + Presence.IM_ACCOUNT + ")" +
         ");");
 
         db.execSQL("CREATE INDEX IF NOT EXISTS " + DATABASE_PRESENCE + ".presenceIndex" + " ON "
-                + Tables.PRESENCE + " (" + Presence.RAW_CONTACT_ID + ");");
+                + Tables.PRESENCE + " (" + PresenceColumns.RAW_CONTACT_ID + ");");
 
         db.execSQL("CREATE TABLE IF NOT EXISTS "
                         + DATABASE_PRESENCE + "." + Tables.AGGREGATED_PRESENCE + " ("+
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 55e288c..02564bf 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -249,9 +249,11 @@
         return mResolver.insert(Data.CONTENT_URI, values);
     }
 
-    protected Uri insertPresence(int protocol, String handle, int presence, String status) {
+    protected Uri insertPresence(int protocol, String customProtocol, String handle, int presence,
+            String status) {
         ContentValues values = new ContentValues();
-        values.put(Presence.IM_PROTOCOL, protocol);
+        values.put(Presence.PROTOCOL, protocol);
+        values.put(Presence.CUSTOM_PROTOCOL, customProtocol);
         values.put(Presence.IM_HANDLE, handle);
         values.put(Presence.PRESENCE_STATUS, presence);
         values.put(Presence.PRESENCE_CUSTOM_STATUS, status);
@@ -260,11 +262,13 @@
         return resultUri;
     }
 
-    protected Uri insertImHandle(long rawContactId, int protocol, String handle) {
+    protected Uri insertImHandle(long rawContactId, int protocol, String customProtocol,
+            String handle) {
         ContentValues values = new ContentValues();
         values.put(Data.RAW_CONTACT_ID, rawContactId);
         values.put(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE);
         values.put(Im.PROTOCOL, protocol);
+        values.put(Im.CUSTOM_PROTOCOL, customProtocol);
         values.put(Im.DATA, handle);
         values.put(Im.TYPE, Im.TYPE_HOME);
 
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index a55b182..7a8ece0 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -16,13 +16,13 @@
 package com.android.providers.contacts;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.providers.contacts.OpenHelper.PresenceColumns;
 
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Entity;
 import android.content.EntityIterator;
 import android.database.Cursor;
-import android.database.DatabaseUtils;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
@@ -362,8 +362,10 @@
         insertEmail(rawContactId, "goog411@acme.com");
         insertEmail(rawContactId, "goog412@acme.com");
 
-        insertPresence(Im.PROTOCOL_GOOGLE_TALK, "goog411@acme.com", Presence.INVISIBLE, "Bad");
-        insertPresence(Im.PROTOCOL_GOOGLE_TALK, "goog412@acme.com", Presence.AVAILABLE, "Good");
+        insertPresence(Im.PROTOCOL_GOOGLE_TALK, null, "goog411@acme.com",
+                Presence.INVISIBLE, "Bad");
+        insertPresence(Im.PROTOCOL_GOOGLE_TALK, null, "goog412@acme.com",
+                Presence.AVAILABLE, "Good");
         long contactId = queryContactId(rawContactId);
 
         Uri uri = Uri.withAppendedPath(ContactsContract.AUTHORITY_URI, "data_with_presence");
@@ -511,16 +513,58 @@
         assertSendToVoicemailAndRingtone(queryContactId(rawContactId2), false, "bar");
     }
 
+    public void testInsertPresence() {
+        long rawContactId = createRawContact();
+        insertImHandle(rawContactId, Im.PROTOCOL_AIM, null, "aim");
+        insertImHandle(rawContactId, Im.PROTOCOL_CUSTOM, "my_im_proto", "my_im");
+        insertEmail(rawContactId, "acme123@acme.com");
+
+        // Match on IM (standard)
+        insertPresence(Im.PROTOCOL_AIM, null, "aim", Presence.AVAILABLE, "Available");
+
+        // Match on IM (custom)
+        insertPresence(Im.PROTOCOL_CUSTOM, "my_im_proto", "my_im", Presence.IDLE, "Idle");
+
+        // Match on Email
+        insertPresence(Im.PROTOCOL_GOOGLE_TALK, null, "acme123@acme.com", Presence.AWAY, "Away");
+
+        // No match
+        insertPresence(Im.PROTOCOL_ICQ, null, "12345", Presence.DO_NOT_DISTURB, "Go away");
+
+        Cursor c = mResolver.query(Presence.CONTENT_URI, new String[] {
+                Presence.DATA_ID, Presence.PROTOCOL, Presence.CUSTOM_PROTOCOL,
+                Presence.PRESENCE_STATUS, Presence.PRESENCE_CUSTOM_STATUS},
+                PresenceColumns.RAW_CONTACT_ID + "=" + rawContactId, null, Presence.DATA_ID);
+        assertTrue(c.moveToNext());
+        assertPresence(c, Im.PROTOCOL_AIM, null, Presence.AVAILABLE, "Available");
+        assertTrue(c.moveToNext());
+        assertPresence(c, Im.PROTOCOL_CUSTOM, "my_im_proto", Presence.IDLE, "Idle");
+        assertTrue(c.moveToNext());
+        assertPresence(c, Im.PROTOCOL_GOOGLE_TALK, null, Presence.AWAY, "Away");
+        assertFalse(c.moveToNext());
+        c.close();
+    }
+
+    private void assertPresence(Cursor c, int protocol, String customProtocol, int status,
+            String customStatus) {
+        ContentValues values = new ContentValues();
+        values.put(Presence.PROTOCOL, protocol);
+        values.put(Presence.CUSTOM_PROTOCOL, customProtocol);
+        values.put(Presence.PRESENCE_STATUS, status);
+        values.put(Presence.PRESENCE_CUSTOM_STATUS, customStatus);
+        assertCursorValues(c, values);
+    }
+
     public void testSinglePresenceRowPerContact() {
         int protocol1 = Im.PROTOCOL_GOOGLE_TALK;
         String handle1 = "test@gmail.com";
 
         long rawContactId1 = createRawContact();
-        insertImHandle(rawContactId1, protocol1, handle1);
+        insertImHandle(rawContactId1, protocol1, null, handle1);
 
-        insertPresence(protocol1, handle1, Presence.AVAILABLE, "Green");
-        insertPresence(protocol1, handle1, Presence.AWAY, "Yellow");
-        insertPresence(protocol1, handle1, Presence.INVISIBLE, "Red");
+        insertPresence(protocol1, null, handle1, Presence.AVAILABLE, "Green");
+        insertPresence(protocol1, null, handle1, Presence.AWAY, "Yellow");
+        insertPresence(protocol1, null, handle1, Presence.INVISIBLE, "Red");
 
         Cursor c = queryContactSummary(queryContactId(rawContactId1),
                 new String[] {Presence.PRESENCE_STATUS, Presence.PRESENCE_CUSTOM_STATUS});
@@ -747,12 +791,12 @@
         long rawContactId = createRawContact();
         Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
 
-        insertImHandle(rawContactId, Im.PROTOCOL_GOOGLE_TALK, "deleteme@android.com");
-        insertPresence(Im.PROTOCOL_GOOGLE_TALK, "deleteme@android.com", Presence.AVAILABLE, null);
+        insertImHandle(rawContactId, Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com");
+        insertPresence(Im.PROTOCOL_GOOGLE_TALK, null, "deleteme@android.com", Presence.AVAILABLE, null);
         assertEquals(1, getCount(Uri.withAppendedPath(uri, RawContacts.Data.CONTENT_DIRECTORY),
                 null, null));
-        assertEquals(1, getCount(Presence.CONTENT_URI, Presence.RAW_CONTACT_ID + "=" + rawContactId,
-                null));
+        assertEquals(1, getCount(Presence.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "="
+                + rawContactId, null));
 
         mResolver.delete(uri, null, null);
 
@@ -764,8 +808,8 @@
         assertEquals(0, getCount(uri, null, null));
         assertEquals(0, getCount(Uri.withAppendedPath(uri, RawContacts.Data.CONTENT_DIRECTORY),
                 null, null));
-        assertEquals(0, getCount(Presence.CONTENT_URI, Presence.RAW_CONTACT_ID + "=" + rawContactId,
-                null));
+        assertEquals(0, getCount(Presence.CONTENT_URI, PresenceColumns.RAW_CONTACT_ID + "="
+                + rawContactId, null));
     }
 
     public void testMarkAsDirtyParameter() {
@@ -881,7 +925,7 @@
         insertPhoneNumber(rawContactId, phoneNumber);
         insertEmail(rawContactId, email);
 
-        insertPresence(Im.PROTOCOL_GOOGLE_TALK, email, presenceStatus, "hacking");
+        insertPresence(Im.PROTOCOL_GOOGLE_TALK, null, email, presenceStatus, "hacking");
 
         if (groupId != 0) {
             insertGroupMembership(rawContactId, groupId);