Merge "Remove hasColumn() and use isInProjection() instead."
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 3b34b6f..46e4061 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -300,6 +300,9 @@
private static final int EMAILS_FILTER = 3008;
private static final int POSTALS = 3009;
private static final int POSTALS_ID = 3010;
+ private static final int CALLABLES = 3011;
+ private static final int CALLABLES_ID = 3012;
+ private static final int CALLABLES_FILTER = 3013;
private static final int PHONE_LOOKUP = 4000;
@@ -1156,6 +1159,10 @@
matcher.addURI(ContactsContract.AUTHORITY, "data/postals/#", POSTALS_ID);
/** "*" is in CSV form with data ids ("123,456,789") */
matcher.addURI(ContactsContract.AUTHORITY, "data/usagefeedback/*", DATA_USAGE_FEEDBACK_ID);
+ matcher.addURI(ContactsContract.AUTHORITY, "data/callables/", CALLABLES);
+ matcher.addURI(ContactsContract.AUTHORITY, "data/callables/#", CALLABLES_ID);
+ matcher.addURI(ContactsContract.AUTHORITY, "data/callables/filter", CALLABLES_FILTER);
+ matcher.addURI(ContactsContract.AUTHORITY, "data/callables/filter/*", CALLABLES_FILTER);
matcher.addURI(ContactsContract.AUTHORITY, "groups", GROUPS);
matcher.addURI(ContactsContract.AUTHORITY, "groups/#", GROUPS_ID);
@@ -3580,6 +3587,7 @@
case DATA_ID:
case PHONES_ID:
case EMAILS_ID:
+ case CALLABLES_ID:
case POSTALS_ID:
case PROFILE_DATA_ID: {
long dataId = ContentUris.parseId(uri);
@@ -3929,6 +3937,7 @@
case DATA_ID:
case PHONES_ID:
case EMAILS_ID:
+ case CALLABLES_ID:
case POSTALS_ID: {
count = updateData(uri, values, selection, selectionArgs, callerIsSyncAdapter);
if (count > 0) {
@@ -5422,10 +5431,19 @@
return cursor;
}
- case PHONES: {
+ case PHONES:
+ case CALLABLES: {
+ final String mimeTypeIsPhoneExpression =
+ DataColumns.MIMETYPE_ID + "=" + mDbHelper.get().getMimeTypeIdForPhone();
+ final String mimeTypeIsSipExpression =
+ DataColumns.MIMETYPE_ID + "=" + mDbHelper.get().getMimeTypeIdForSip();
setTablesAndProjectionMapForData(qb, uri, projection, false);
- qb.appendWhere(" AND " + DataColumns.MIMETYPE_ID + "=" +
- mDbHelper.get().getMimeTypeIdForPhone());
+ if (match == CALLABLES) {
+ qb.appendWhere(" AND ((" + mimeTypeIsPhoneExpression +
+ ") OR (" + mimeTypeIsSipExpression + "))");
+ } else {
+ qb.appendWhere(" AND " + mimeTypeIsPhoneExpression);
+ }
final boolean removeDuplicates = readBooleanQueryParameter(
uri, ContactsContract.REMOVE_DUPLICATE_ENTRIES, false);
@@ -5445,31 +5463,50 @@
break;
}
- case PHONES_ID: {
+ case PHONES_ID:
+ case CALLABLES_ID: {
+ final String mimeTypeIsPhoneExpression =
+ DataColumns.MIMETYPE_ID + "=" + mDbHelper.get().getMimeTypeIdForPhone();
+ final String mimeTypeIsSipExpression =
+ DataColumns.MIMETYPE_ID + "=" + mDbHelper.get().getMimeTypeIdForSip();
setTablesAndProjectionMapForData(qb, uri, projection, false);
selectionArgs = insertSelectionArg(selectionArgs, uri.getLastPathSegment());
- qb.appendWhere(" AND " + DataColumns.MIMETYPE_ID + " = "
- + mDbHelper.get().getMimeTypeIdForPhone());
+ if (match == CALLABLES_ID) {
+ qb.appendWhere(" AND ((" + mimeTypeIsPhoneExpression +
+ ") OR (" + mimeTypeIsSipExpression + "))");
+ } else {
+ qb.appendWhere(" AND " + mimeTypeIsPhoneExpression);
+ }
qb.appendWhere(" AND " + Data._ID + "=?");
break;
}
- case PHONES_FILTER: {
+ case PHONES_FILTER:
+ case CALLABLES_FILTER: {
+ final String mimeTypeIsPhoneExpression =
+ DataColumns.MIMETYPE_ID + "=" + mDbHelper.get().getMimeTypeIdForPhone();
+ final String mimeTypeIsSipExpression =
+ DataColumns.MIMETYPE_ID + "=" + mDbHelper.get().getMimeTypeIdForSip();
+
String typeParam = uri.getQueryParameter(DataUsageFeedback.USAGE_TYPE);
Integer typeInt = sDataUsageTypeMap.get(typeParam);
if (typeInt == null) {
typeInt = DataUsageStatColumns.USAGE_TYPE_INT_CALL;
}
setTablesAndProjectionMapForData(qb, uri, projection, true, typeInt);
- qb.appendWhere(" AND " + DataColumns.MIMETYPE_ID + " = "
- + mDbHelper.get().getMimeTypeIdForPhone());
+ if (match == CALLABLES_FILTER) {
+ qb.appendWhere(" AND ((" + mimeTypeIsPhoneExpression +
+ ") OR (" + mimeTypeIsSipExpression + "))");
+ } else {
+ qb.appendWhere(" AND " + mimeTypeIsPhoneExpression);
+ }
+
if (uri.getPathSegments().size() > 2) {
String filterParam = uri.getLastPathSegment();
StringBuilder sb = new StringBuilder();
sb.append(" AND (");
boolean hasCondition = false;
- boolean orNeeded = false;
final String ftsMatchQuery = SearchIndexManager.getFtsMatchQuery(
filterParam, FtsQueryBuilder.UNSCOPED_NORMALIZING);
if (ftsMatchQuery.length() > 0) {
@@ -5482,13 +5519,12 @@
" WHERE " + SearchIndexColumns.NAME + " MATCH '");
sb.append(ftsMatchQuery);
sb.append("')");
- orNeeded = true;
hasCondition = true;
}
String number = PhoneNumberUtils.normalizeNumber(filterParam);
if (!TextUtils.isEmpty(number)) {
- if (orNeeded) {
+ if (hasCondition) {
sb.append(" OR ");
}
sb.append(Data._ID +
@@ -5500,6 +5536,23 @@
hasCondition = true;
}
+ if (!TextUtils.isEmpty(filterParam) && match == CALLABLES_FILTER) {
+ // If the request is via Callable uri, Sip addresses matching the filter
+ // parameter should be returned.
+ if (hasCondition) {
+ sb.append(" OR ");
+ }
+ sb.append("(");
+ sb.append(mimeTypeIsSipExpression);
+ sb.append(" AND ((" + Data.DATA1 + " LIKE ");
+ DatabaseUtils.appendEscapedSQLString(sb, filterParam + '%');
+ sb.append(") OR (" + Data.DATA1 + " LIKE ");
+ // Users may want SIP URIs starting from "sip:"
+ DatabaseUtils.appendEscapedSQLString(sb, "sip:"+ filterParam + '%');
+ sb.append(")))");
+ hasCondition = true;
+ }
+
if (!hasCondition) {
// If it is neither a phone number nor a name, the query should return
// an empty cursor. Let's ensure that.
@@ -5508,9 +5561,21 @@
sb.append(")");
qb.appendWhere(sb);
}
- groupBy = "(CASE WHEN " + PhoneColumns.NORMALIZED_NUMBER
+ if (match == CALLABLES_FILTER) {
+ // If the row is for a phone number that has a normalized form, we should use
+ // the normalized one as PHONES_FILTER does, while we shouldn't do that
+ // if the row is for a sip address.
+ String isPhoneAndHasNormalized = "("
+ + mimeTypeIsPhoneExpression + " AND "
+ + PhoneColumns.NORMALIZED_NUMBER + " IS NOT NULL)";
+ groupBy = "(CASE WHEN " + isPhoneAndHasNormalized
+ + " THEN " + PhoneColumns.NORMALIZED_NUMBER
+ + " ELSE " + Data.DATA1 + " END), " + RawContacts.CONTACT_ID;
+ } else {
+ groupBy = "(CASE WHEN " + PhoneColumns.NORMALIZED_NUMBER
+ " IS NOT NULL THEN " + PhoneColumns.NORMALIZED_NUMBER
+ " ELSE " + Phone.NUMBER + " END), " + RawContacts.CONTACT_ID;
+ }
if (sortOrder == null) {
final String accountPromotionSortOrder = getAccountPromotionSortOrder(uri);
if (!TextUtils.isEmpty(accountPromotionSortOrder)) {
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index 6080f7e..87196bf 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -41,6 +41,7 @@
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Contacts;
@@ -336,6 +337,23 @@
return resultUri;
}
+ protected Uri insertSipAddress(long rawContactId, String sipAddress) {
+ return insertSipAddress(rawContactId, sipAddress, false);
+ }
+
+ protected Uri insertSipAddress(long rawContactId, String sipAddress, boolean primary) {
+ ContentValues values = new ContentValues();
+ values.put(Data.RAW_CONTACT_ID, rawContactId);
+ values.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE);
+ values.put(SipAddress.SIP_ADDRESS, sipAddress);
+ if (primary) {
+ values.put(Data.IS_PRIMARY, 1);
+ }
+
+ Uri resultUri = mResolver.insert(Data.CONTENT_URI, values);
+ return resultUri;
+ }
+
protected Uri insertNickname(long rawContactId, String nickname) {
ContentValues values = new ContentValues();
values.put(Data.RAW_CONTACT_ID, rawContactId);
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index eae168f..599f1e7 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -36,12 +36,14 @@
import android.os.AsyncTask;
import android.provider.ContactsContract;
import android.provider.ContactsContract.AggregationExceptions;
+import android.provider.ContactsContract.CommonDataKinds.Callable;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.SipAddress;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.ContactCounts;
@@ -917,15 +919,33 @@
}
public void testPhonesFilterQuery() {
- long rawContactId1 = createRawContactWithName("Hot", "Tamale", ACCOUNT_1);
+ testPhonesFilterQueryInter(Phone.CONTENT_FILTER_URI);
+ }
+
+ /**
+ * A convenient method for {@link #testPhonesFilterQuery()} and
+ * {@link #testCallablesFilterQuery()}.
+ *
+ * This confirms if both URIs return identical results for phone-only contacts and
+ * appropriately different results for contacts with sip addresses.
+ *
+ * @param baseFilterUri Either {@link Phone#CONTENT_FILTER_URI} or
+ * {@link Callable#CONTENT_FILTER_URI}.
+ */
+ private void testPhonesFilterQueryInter(Uri baseFilterUri) {
+ assertTrue("Unsupported Uri (" + baseFilterUri + ")",
+ Phone.CONTENT_FILTER_URI.equals(baseFilterUri)
+ || Callable.CONTENT_FILTER_URI.equals(baseFilterUri));
+
+ final long rawContactId1 = createRawContactWithName("Hot", "Tamale", ACCOUNT_1);
insertPhoneNumber(rawContactId1, "1-800-466-4411");
- long rawContactId2 = createRawContactWithName("Chilled", "Guacamole", ACCOUNT_2);
+ final long rawContactId2 = createRawContactWithName("Chilled", "Guacamole", ACCOUNT_2);
insertPhoneNumber(rawContactId2, "1-800-466-5432");
insertPhoneNumber(rawContactId2, "0@example.com", false, Phone.TYPE_PAGER);
insertPhoneNumber(rawContactId2, "1@example.com", false, Phone.TYPE_PAGER);
- Uri filterUri1 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "tamale");
+ final Uri filterUri1 = Uri.withAppendedPath(baseFilterUri, "tamale");
ContentValues values = new ContentValues();
values.put(Contacts.DISPLAY_NAME, "Hot Tamale");
values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
@@ -934,16 +954,16 @@
values.putNull(Phone.LABEL);
assertStoredValuesWithProjection(filterUri1, values);
- Uri filterUri2 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "1-800-GOOG-411");
+ final Uri filterUri2 = Uri.withAppendedPath(baseFilterUri, "1-800-GOOG-411");
assertStoredValues(filterUri2, values);
- Uri filterUri3 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "18004664");
+ final Uri filterUri3 = Uri.withAppendedPath(baseFilterUri, "18004664");
assertStoredValues(filterUri3, values);
- Uri filterUri4 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "encilada");
+ final Uri filterUri4 = Uri.withAppendedPath(baseFilterUri, "encilada");
assertEquals(0, getCount(filterUri4, null, null));
- Uri filterUri5 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "*");
+ final Uri filterUri5 = Uri.withAppendedPath(baseFilterUri, "*");
assertEquals(0, getCount(filterUri5, null, null));
ContentValues values1 = new ContentValues();
@@ -967,7 +987,42 @@
values3.put(Phone.TYPE, Phone.TYPE_PAGER);
values3.putNull(Phone.LABEL);
- Uri filterUri6 = Uri.withAppendedPath(Phone.CONTENT_FILTER_URI, "Chilled");
+ final Uri filterUri6 = Uri.withAppendedPath(baseFilterUri, "Chilled");
+ assertStoredValues(filterUri6, new ContentValues[] {values1, values2, values3} );
+
+ // Insert a SIP address. From here, Phone URI and Callable URI may return different results
+ // than each other.
+ insertSipAddress(rawContactId1, "sip_hot_tamale@example.com");
+ insertSipAddress(rawContactId1, "sip:sip_hot@example.com");
+
+ final Uri filterUri7 = Uri.withAppendedPath(baseFilterUri, "sip_hot");
+ final Uri filterUri8 = Uri.withAppendedPath(baseFilterUri, "sip_hot_tamale");
+ if (Callable.CONTENT_FILTER_URI.equals(baseFilterUri)) {
+ ContentValues values4 = new ContentValues();
+ values4.put(Contacts.DISPLAY_NAME, "Hot Tamale");
+ values4.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE);
+ values4.put(SipAddress.SIP_ADDRESS, "sip_hot_tamale@example.com");
+
+ ContentValues values5 = new ContentValues();
+ values5.put(Contacts.DISPLAY_NAME, "Hot Tamale");
+ values5.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE);
+ values5.put(SipAddress.SIP_ADDRESS, "sip:sip_hot@example.com");
+ assertStoredValues(filterUri1, new ContentValues[] {values, values4, values5});
+
+ assertStoredValues(filterUri7, new ContentValues[] {values4, values5});
+ assertStoredValues(filterUri8, values4);
+ } else {
+ // Sip address should not affect Phone URI.
+ assertStoredValuesWithProjection(filterUri1, values);
+ assertEquals(0, getCount(filterUri7, null, null));
+ }
+
+ // Sanity test. Run tests for "Chilled Guacamole" again and see nothing changes
+ // after the Sip address being inserted.
+ assertStoredValues(filterUri2, values);
+ assertStoredValues(filterUri3, values);
+ assertEquals(0, getCount(filterUri4, null, null));
+ assertEquals(0, getCount(filterUri5, null, null));
assertStoredValues(filterUri6, new ContentValues[] {values1, values2, values3} );
}
@@ -1100,6 +1155,43 @@
assertNetworkNotified(true);
}
+ /** Tests if {@link Callable#CONTENT_URI} returns both phones and sip addresses. */
+ public void testCallablesQuery() {
+ long rawContactId1 = createRawContactWithName("Meghan", "Knox");
+ long phoneId1 = ContentUris.parseId(insertPhoneNumber(rawContactId1, "18004664411"));
+ long contactId1 = queryContactId(rawContactId1);
+
+ long rawContactId2 = createRawContactWithName("John", "Doe");
+ long sipAddressId2 = ContentUris.parseId(
+ insertSipAddress(rawContactId2, "sip@example.com"));
+ long contactId2 = queryContactId(rawContactId2);
+
+ ContentValues values1 = new ContentValues();
+ values1.put(Data._ID, phoneId1);
+ values1.put(Data.RAW_CONTACT_ID, rawContactId1);
+ values1.put(RawContacts.CONTACT_ID, contactId1);
+ values1.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+ values1.put(Phone.NUMBER, "18004664411");
+ values1.put(Phone.TYPE, Phone.TYPE_HOME);
+ values1.putNull(Phone.LABEL);
+ values1.put(Contacts.DISPLAY_NAME, "Meghan Knox");
+
+ ContentValues values2 = new ContentValues();
+ values2.put(Data._ID, sipAddressId2);
+ values2.put(Data.RAW_CONTACT_ID, rawContactId2);
+ values2.put(RawContacts.CONTACT_ID, contactId2);
+ values2.put(Data.MIMETYPE, SipAddress.CONTENT_ITEM_TYPE);
+ values2.put(SipAddress.SIP_ADDRESS, "sip@example.com");
+ values2.put(Contacts.DISPLAY_NAME, "John Doe");
+
+ assertEquals(2, getCount(Callable.CONTENT_URI, null, null));
+ assertStoredValues(Callable.CONTENT_URI, new ContentValues[] { values1, values2 });
+ }
+
+ public void testCallablesFilterQuery() {
+ testPhonesFilterQueryInter(Callable.CONTENT_FILTER_URI);
+ }
+
public void testEmailsQuery() {
ContentValues values = new ContentValues();
values.put(RawContacts.CUSTOM_RINGTONE, "d");