summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java117
-rw-r--r--tests/src/com/android/providers/contacts/ContactsProvider2Test.java78
2 files changed, 125 insertions, 70 deletions
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 9497d24..9e39b94 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -110,6 +110,7 @@ import android.provider.ContactsContract.CommonDataKinds.Note;
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;
@@ -664,6 +665,36 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
.add(TIMES_USED_SORT_COLUMN, "SUM(" + DataUsageStatColumns.CONCRETE_TIMES_USED + ")")
.build();
+ /**
+ * Used for Strequent Uri with {@link ContactsContract#STREQUENT_PHONE_ONLY}, which allows
+ * users to obtain part of Data columns. Right now Starred part just returns NULL for
+ * those data columns (frequent part should return real ones in data table).
+ **/
+ private static final ProjectionMap sStrequentPhoneOnlyStarredProjectionMap
+ = ProjectionMap.builder()
+ .addAll(sContactsProjectionMap)
+ .add(TIMES_USED_SORT_COLUMN, String.valueOf(Long.MAX_VALUE))
+ .add(Phone.NUMBER, "NULL")
+ .add(Phone.TYPE, "NULL")
+ .add(Phone.LABEL, "NULL")
+ .build();
+
+ /**
+ * Used for Strequent Uri with {@link ContactsContract#STREQUENT_PHONE_ONLY}, which allows
+ * users to obtain part of Data columns. We hard-code {@link Contacts#IS_USER_PROFILE} to NULL,
+ * because sContactsProjectionMap specifies a field that doesn't exist in the view behind the
+ * query that uses this projection map.
+ **/
+ private static final ProjectionMap sStrequentPhoneOnlyFrequentProjectionMap
+ = ProjectionMap.builder()
+ .addAll(sContactsProjectionMap)
+ .add(TIMES_USED_SORT_COLUMN, DataUsageStatColumns.CONCRETE_TIMES_USED)
+ .add(Phone.NUMBER)
+ .add(Phone.TYPE)
+ .add(Phone.LABEL)
+ .add(Contacts.IS_USER_PROFILE, "NULL")
+ .build();
+
/** Contains just the contacts vCard columns */
private static final ProjectionMap sContactsVCardProjectionMap = ProjectionMap.builder()
.add(Contacts._ID)
@@ -4487,9 +4518,10 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
}
// Build the first query for starred
- setTablesAndProjectionMapForContacts(qb, uri, projection,
- false /* for frequent */, phoneOnly);
- qb.setProjectionMap(sStrequentStarredProjectionMap);
+ setTablesAndProjectionMapForContacts(qb, uri, projection, false);
+ qb.setProjectionMap(phoneOnly ?
+ sStrequentPhoneOnlyStarredProjectionMap
+ : sStrequentStarredProjectionMap);
qb.appendWhere(DbQueryUtils.concatenateClauses(
selection, Contacts.IS_USER_PROFILE + "=0"));
qb.setStrict(true);
@@ -4498,17 +4530,51 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
// Reset the builder.
qb = new SQLiteQueryBuilder();
-
- // Build the second query for frequent
- setTablesAndProjectionMapForContacts(qb, uri, projection,
- true /* for frequent */, phoneOnly);
- qb.setProjectionMap(sStrequentFrequentProjectionMap);
- qb.appendWhere(DbQueryUtils.concatenateClauses(
- selection, Contacts.IS_USER_PROFILE + "=0"));
qb.setStrict(true);
- final String frequentQuery = qb.buildQuery(subProjection,
- "(" + Contacts.STARRED + " =0 OR " + Contacts.STARRED + " IS NULL)",
- Contacts._ID, null, null, null);
+
+ // Build the second query for frequent part.
+ final String frequentQuery;
+ if (phoneOnly) {
+ final StringBuilder tableBuilder = new StringBuilder();
+ // In phone only mode, we need to look at view_data instead of
+ // contacts/raw_contacts to obtain actual phone numbers. One problem is that
+ // view_data is much larger than view_contacts, so our query might become much
+ // slower.
+ //
+ // To avoid the possible slow down, we start from data usage table and join
+ // view_data to the table, assuming data usage table is quite smaller than
+ // data rows (almost always it should be), and we don't want any phone
+ // numbers not used by the user. This way sqlite is able to drop a number of
+ // rows in view_data in the early stage of data lookup.
+ tableBuilder.append(Tables.DATA_USAGE_STAT
+ + " INNER JOIN " + Views.DATA + " " + Tables.DATA
+ + " ON (" + DataUsageStatColumns.CONCRETE_DATA_ID + "="
+ + DataColumns.CONCRETE_ID + " AND "
+ + DataUsageStatColumns.CONCRETE_USAGE_TYPE + "="
+ + DataUsageStatColumns.USAGE_TYPE_INT_CALL + ")");
+ appendContactPresenceJoin(tableBuilder, projection, RawContacts.CONTACT_ID);
+ appendContactStatusUpdateJoin(tableBuilder, projection,
+ ContactsColumns.LAST_STATUS_UPDATE_ID);
+
+ qb.setTables(tableBuilder.toString());
+ qb.setProjectionMap(sStrequentPhoneOnlyFrequentProjectionMap);
+ qb.appendWhere(DbQueryUtils.concatenateClauses(
+ selection,
+ Contacts.STARRED + "=0 OR " + Contacts.STARRED + " IS NULL",
+ MimetypesColumns.MIMETYPE + " IN ("
+ + "'" + Phone.CONTENT_ITEM_TYPE + "', "
+ + "'" + SipAddress.CONTENT_ITEM_TYPE + "')"));
+ frequentQuery = qb.buildQuery(subProjection, null, null, null, null, null);
+ } else {
+ setTablesAndProjectionMapForContacts(qb, uri, projection, true);
+ qb.setProjectionMap(sStrequentFrequentProjectionMap);
+ qb.appendWhere(DbQueryUtils.concatenateClauses(
+ selection,
+ "(" + Contacts.STARRED + " =0 OR " + Contacts.STARRED + " IS NULL)",
+ Contacts.IS_USER_PROFILE + "=0"));
+ frequentQuery = qb.buildQuery(subProjection,
+ null, Contacts._ID, null, null, null);
+ }
// Put them together
final String unionQuery =
@@ -5619,38 +5685,27 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun
private void setTablesAndProjectionMapForContacts(SQLiteQueryBuilder qb, Uri uri,
String[] projection) {
- setTablesAndProjectionMapForContacts(qb, uri, projection, false, false);
+ setTablesAndProjectionMapForContacts(qb, uri, projection, false);
}
/**
- * @param forStrequentFrequent Should be used only in strequent handling.
- * true when this is for frequently contacted listing (not starred)
- * @param strequentPhoneCallOnly Should be used only in strequent handling.
- * true when this is for phone-only results. See also
- * {@link ContactsContract#STREQUENT_PHONE_ONLY}.
+ * @param includeDataUsageStat true when the table should include DataUsageStat table.
+ * Note that this uses INNER JOIN instead of LEFT OUTER JOIN, so some of data in Contacts
+ * may be dropped.
*/
private void setTablesAndProjectionMapForContacts(SQLiteQueryBuilder qb, Uri uri,
- String[] projection, boolean forStrequentFrequent, boolean strequentPhoneCallOnly) {
+ String[] projection, boolean includeDataUsageStat) {
StringBuilder sb = new StringBuilder();
sb.append(Views.CONTACTS);
// Just for frequently contacted contacts in Strequent Uri handling.
- if (forStrequentFrequent) {
- final String strequentPhoneCallOnlyClause =
- (strequentPhoneCallOnly ? DbQueryUtils.concatenateClauses(
- MimetypesColumns.MIMETYPE + "=\'" + Phone.CONTENT_ITEM_TYPE + "'",
- DataUsageStatColumns.CONCRETE_USAGE_TYPE + "=" +
- DataUsageStatColumns.USAGE_TYPE_INT_CALL)
- : "");
- // Use INNER JOIN for maximum performance, ommiting unnecessary rows as much as
- // possible.
+ if (includeDataUsageStat) {
sb.append(" INNER JOIN " +
Views.DATA_USAGE_STAT + " AS " + Tables.DATA_USAGE_STAT +
" ON (" +
DbQueryUtils.concatenateClauses(
DataUsageStatColumns.CONCRETE_TIMES_USED + " > 0",
- RawContacts.CONTACT_ID + "=" + Views.CONTACTS + "." + Contacts._ID,
- strequentPhoneCallOnlyClause) +
+ RawContacts.CONTACT_ID + "=" + Views.CONTACTS + "." + Contacts._ID) +
")");
}
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index df83373..44250f7 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -1202,10 +1202,13 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
/** Tests {@link DataUsageFeedback} correctly promotes a data row instead of a raw contact. */
public void testEmailFilterSortOrderWithFeedback() {
long rawContactId1 = createRawContact();
- insertEmail(rawContactId1, "address1@email.com");
+ String address1 = "address1@email.com";
+ insertEmail(rawContactId1, address1);
long rawContactId2 = createRawContact();
- insertEmail(rawContactId2, "address2@email.com");
- long dataId = ContentUris.parseId(insertEmail(rawContactId2, "address3@email.com"));
+ String address2 = "address2@email.com";
+ insertEmail(rawContactId2, address2);
+ String address3 = "address3@email.com";
+ ContentUris.parseId(insertEmail(rawContactId2, address3));
ContentValues v1 = new ContentValues();
v1.put(Email.ADDRESS, "address1@email.com");
@@ -1232,13 +1235,7 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
assertStoredValuesOrderly(filterUri3, new ContentValues[] { v1, v2, v3 });
assertStoredValuesOrderly(filterUri4, new ContentValues[] { v1, v2, v3 });
- // Send feedback for address3 in the second account.
- Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
- .appendPath(String.valueOf(dataId))
- .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
- DataUsageFeedback.USAGE_TYPE_LONG_TEXT)
- .build();
- assertNotSame(0, mResolver.update(feedbackUri, new ContentValues(), null, null));
+ sendFeedback(address3, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, v3);
// account3@email.com should be the first. account2@email.com should also be promoted as
// it has same contact id.
@@ -1454,8 +1451,9 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
createContact(values1, "Noah", "Tever", "18004664411",
email1, StatusUpdates.OFFLINE, timesContacted1, 0, 0,
StatusUpdates.CAPABILITY_HAS_CAMERA | StatusUpdates.CAPABILITY_HAS_VIDEO);
+ final String phoneNumber2 = "18004664412";
ContentValues values2 = new ContentValues();
- createContact(values2, "Sam", "Times", "18004664412",
+ createContact(values2, "Sam", "Times", phoneNumber2,
"b@acme.com", StatusUpdates.INVISIBLE, 3, 0, 0,
StatusUpdates.CAPABILITY_HAS_CAMERA);
ContentValues values3 = new ContentValues();
@@ -1473,20 +1471,8 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
// usage feedback should be used for "frequently contacted" listing.
assertStoredValues(Contacts.CONTENT_STREQUENT_URI, values4);
- final long dataIdPhone3 = getStoredLongValue(Phone.CONTENT_URI,
- Phone.NUMBER + "=?", new String[] { phoneNumber3 },
- Data._ID);
-
// Send feedback for the 3rd phone number, pretending we called that person via phone.
- Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
- .appendPath(String.valueOf(dataIdPhone3))
- .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
- DataUsageFeedback.USAGE_TYPE_CALL)
- .build();
- assertNotSame(0, mResolver.update(feedbackUri, new ContentValues(), null, null));
-
- // After the feedback, times contacted should be incremented
- values3.put(RawContacts.TIMES_CONTACTED, timesContacted3 + 1);
+ sendFeedback(phoneNumber3, DataUsageFeedback.USAGE_TYPE_CALL, values3);
// After the feedback, 3rd contact should be shown after starred one.
assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI,
@@ -1497,31 +1483,26 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
Email.ADDRESS + "=?", new String[] { email1},
Email._ID);
- // Send feedback for the 1st email, pretending we sent the person an email twice.
- // (we don't define the order for 1st and 3rd contacts with same times contacted)
- feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
- .appendPath(String.valueOf(dataIdEmail1))
- .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
- DataUsageFeedback.USAGE_TYPE_LONG_TEXT)
- .build();
- assertNotSame(0, mResolver.update(feedbackUri, new ContentValues(), null, null));
+ sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
// Twice.
- assertNotSame(0, mResolver.update(feedbackUri, new ContentValues(), null, null));
-
- // After the feedback, times contacted should be incremented
- values1.put(RawContacts.TIMES_CONTACTED, timesContacted1 + 2);
+ sendFeedback(email1, DataUsageFeedback.USAGE_TYPE_LONG_TEXT, values1);
// After the feedback, 1st and 3rd contacts should be shown after starred one.
assertStoredValuesOrderly(Contacts.CONTENT_STREQUENT_URI,
- new ContentValues[] { values4, values1, values3 });
+ new ContentValues[] { values4, values1, values3 });
// With phone-only parameter, the 1st contact shouldn't be returned, since it is only
// about email, not phone-call.
Uri phoneOnlyStrequentUri = Contacts.CONTENT_STREQUENT_URI.buildUpon()
.appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true")
.build();
- assertStoredValuesOrderly(phoneOnlyStrequentUri,
- new ContentValues[] { values4, values3 });
+ assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] { values4, values3 });
+
+ // Send feedback for the 2rd phone number, pretending we send the person a SMS message.
+ sendFeedback(phoneNumber2, DataUsageFeedback.USAGE_TYPE_SHORT_TEXT, values1);
+
+ // SMS feedback shouldn't affect phone-only results.
+ assertStoredValuesOrderly(phoneOnlyStrequentUri, new ContentValues[] { values4, values3 });
Uri filterUri = Uri.withAppendedPath(Contacts.CONTENT_STREQUENT_FILTER_URI, "fay");
assertStoredValues(filterUri, values4);
@@ -5672,5 +5653,24 @@ public class ContactsProvider2Test extends BaseContactsProvider2Test {
values.put(Data.SYNC3, "sync3");
values.put(Data.SYNC4, "sync4");
}
+
+ /**
+ * @param data1 email address or phone number
+ * @param usageType One of {@link DataUsageFeedback#USAGE_TYPE}
+ * @param values ContentValues for this feedback. Useful for incrementing
+ * {Contacts#TIMES_CONTACTED} in the ContentValue. Can be null.
+ */
+ private void sendFeedback(String data1, String usageType, ContentValues values) {
+ final long dataId = getStoredLongValue(Data.CONTENT_URI,
+ Data.DATA1 + "=?", new String[] { data1 }, Data._ID);
+ final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
+ .appendPath(String.valueOf(dataId))
+ .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, usageType)
+ .build();
+ assertNotSame(0, mResolver.update(feedbackUri, new ContentValues(), null, null));
+ if (values != null && values.containsKey(Contacts.TIMES_CONTACTED)) {
+ values.put(Contacts.TIMES_CONTACTED, values.getAsInteger(Contacts.TIMES_CONTACTED) + 1);
+ }
+ }
}