diff options
author | Daisuke Miyakawa <dmiyakawa@google.com> | 2011-06-24 18:52:01 -0700 |
---|---|---|
committer | Daisuke Miyakawa <dmiyakawa@google.com> | 2011-06-28 12:13:43 -0700 |
commit | 2f830d3bb66f780937203e9738e046841a070e73 (patch) | |
tree | 655183ad09235d4321332de3e2aa588949462013 /src/com | |
parent | 42e8dddb940a502e97151a08f16b87aed7b30de5 (diff) | |
download | packages_providers_ContactsProvider-2f830d3bb66f780937203e9738e046841a070e73.zip packages_providers_ContactsProvider-2f830d3bb66f780937203e9738e046841a070e73.tar.gz packages_providers_ContactsProvider-2f830d3bb66f780937203e9738e046841a070e73.tar.bz2 |
Use new data usage stat for strequent contacts
Must be with Ie193bb91ee49b18f4a546a1f52be780bb514301d
- use phone-only query parameter in strequent mode
- introduce data_usage_stat View for combining the table
with some of data/raw_contacts columns, which are needed
for strequent uris.
- modify strequent impl for supporting phone-only search
- modify a test for strequent uri handling
Checked performance. We need UNION ALL there and some nasty
hacks with two sub queries.
Bug: 4371572
Change-Id: I8c81747d8a8ae47ce551067fc4dbe2c48f4f48ae
Diffstat (limited to 'src/com')
-rw-r--r-- | src/com/android/providers/contacts/ContactsDatabaseHelper.java | 44 | ||||
-rw-r--r-- | src/com/android/providers/contacts/ContactsProvider2.java | 139 |
2 files changed, 144 insertions, 39 deletions
diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java index b14f422..2e2af94 100644 --- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java +++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java @@ -97,7 +97,7 @@ import java.util.Locale; * 600-699 Ice Cream Sandwich * </pre> */ - static final int DATABASE_VERSION = 602; + static final int DATABASE_VERSION = 603; private static final String DATABASE_NAME = "contacts2.db"; private static final String DATABASE_PRESENCE = "presence_db"; @@ -211,6 +211,10 @@ import java.util.Locale; public static final String RAW_ENTITIES_RESTRICTED = "view_raw_entities_restricted"; public static final String GROUPS_ALL = "view_groups"; + + public static final String DATA_USAGE_STAT_ALL = "view_data_usage_stat"; + public static final String DATA_USAGE_STAT_RESTRICTED = + "view_data_usage_stat_restricted"; } public interface Clauses { @@ -1323,6 +1327,8 @@ import java.util.Locale; db.execSQL("DROP VIEW IF EXISTS " + Views.RAW_ENTITIES_RESTRICTED + ";"); db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES + ";"); db.execSQL("DROP VIEW IF EXISTS " + Views.ENTITIES_RESTRICTED + ";"); + db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_STAT_ALL + ";"); + db.execSQL("DROP VIEW IF EXISTS " + Views.DATA_USAGE_STAT_RESTRICTED + ";"); String dataColumns = Data.IS_PRIMARY + ", " @@ -1585,6 +1591,28 @@ import java.util.Locale; + entitiesSelect); db.execSQL("CREATE VIEW " + Views.ENTITIES_RESTRICTED + " AS " + entitiesSelect + " WHERE " + RawContactsColumns.CONCRETE_IS_RESTRICTED + "=0"); + + String dataUsageStatSelect = "SELECT " + + DataUsageStatColumns.CONCRETE_ID + " AS " + DataUsageStatColumns._ID + ", " + + DataUsageStatColumns.DATA_ID + ", " + + RawContactsColumns.CONCRETE_CONTACT_ID + " AS " + RawContacts.CONTACT_ID + ", " + + MimetypesColumns.CONCRETE_MIMETYPE + " AS " + Data.MIMETYPE + ", " + + DataUsageStatColumns.USAGE_TYPE_INT + ", " + + DataUsageStatColumns.TIMES_USED + ", " + + DataUsageStatColumns.LAST_TIME_USED + + " FROM " + Tables.DATA_USAGE_STAT + + " JOIN " + Tables.DATA + " ON (" + + DataColumns.CONCRETE_ID + "=" + DataUsageStatColumns.CONCRETE_DATA_ID + ")" + + " JOIN " + Tables.RAW_CONTACTS + " ON (" + + RawContactsColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_RAW_CONTACT_ID + + " )" + + " JOIN " + Tables.MIMETYPES + " ON (" + + MimetypesColumns.CONCRETE_ID + "=" + DataColumns.CONCRETE_MIMETYPE_ID + ")"; + + db.execSQL("CREATE VIEW " + Views.DATA_USAGE_STAT_ALL + " AS " + dataUsageStatSelect); + db.execSQL("CREATE VIEW " + Views.DATA_USAGE_STAT_RESTRICTED + " AS " + + dataUsageStatSelect + " WHERE " + + RawContactsColumns.CONCRETE_IS_RESTRICTED + "=0"); } private static String buildPhotoUriAlias(String contactIdColumn, String alias) { @@ -1936,6 +1964,11 @@ import java.util.Locale; oldVersion = 602; } + if (oldVersion < 603) { + upgradeViewsAndTriggers = true; + oldVersion = 603; + } + if (upgradeViewsAndTriggers) { createContactsViews(db); createGroupsView(db); @@ -3766,6 +3799,15 @@ import java.util.Locale; Views.ENTITIES : Views.ENTITIES_RESTRICTED; } + public String getDataUsageStatView() { + return getDataUsageStatView(false); + } + + public String getDataUsageStatView(boolean requireRestrictedView) { + return (hasAccessToRestrictedData() && !requireRestrictedView) ? + Views.DATA_USAGE_STAT_ALL : Views.DATA_USAGE_STAT_RESTRICTED; + } + /** * Test if any of the columns appear in the given projection. */ diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index 610fbd6..05edce5 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -29,6 +29,7 @@ import com.android.providers.contacts.ContactsDatabaseHelper.ContactsStatusUpdat import com.android.providers.contacts.ContactsDatabaseHelper.DataColumns; import com.android.providers.contacts.ContactsDatabaseHelper.DataUsageStatColumns; import com.android.providers.contacts.ContactsDatabaseHelper.GroupsColumns; +import com.android.providers.contacts.ContactsDatabaseHelper.MimetypesColumns; import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupColumns; import com.android.providers.contacts.ContactsDatabaseHelper.NameLookupType; import com.android.providers.contacts.ContactsDatabaseHelper.PhoneColumns; @@ -39,6 +40,7 @@ import com.android.providers.contacts.ContactsDatabaseHelper.SearchIndexColumns; import com.android.providers.contacts.ContactsDatabaseHelper.SettingsColumns; import com.android.providers.contacts.ContactsDatabaseHelper.StatusUpdatesColumns; import com.android.providers.contacts.ContactsDatabaseHelper.Tables; +import com.android.providers.contacts.util.DbQueryUtils; import com.android.vcard.VCardComposer; import com.android.vcard.VCardConfig; import com.google.android.collect.Lists; @@ -184,10 +186,14 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - private static final String TIMES_CONTACTED_SORT_COLUMN = "times_contacted_sort"; + /** + * Used to insert a column into strequent results, which enables SQL to sort the list using + * the total times contacted. See also {@link #sStrequentFrequentProjectionMap}. + */ + private static final String TIMES_USED_SORT_COLUMN = "times_used_sort"; private static final String STREQUENT_ORDER_BY = Contacts.STARRED + " DESC, " - + TIMES_CONTACTED_SORT_COLUMN + " DESC, " + + TIMES_USED_SORT_COLUMN + " DESC, " + Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; private static final String STREQUENT_LIMIT = "(SELECT COUNT(1) FROM " + Tables.CONTACTS + " WHERE " @@ -601,12 +607,12 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun /** Used for pushing starred contacts to the top of a times contacted list **/ private static final ProjectionMap sStrequentStarredProjectionMap = ProjectionMap.builder() .addAll(sContactsProjectionMap) - .add(TIMES_CONTACTED_SORT_COLUMN, String.valueOf(Long.MAX_VALUE)) + .add(TIMES_USED_SORT_COLUMN, String.valueOf(Long.MAX_VALUE)) .build(); private static final ProjectionMap sStrequentFrequentProjectionMap = ProjectionMap.builder() .addAll(sContactsProjectionMap) - .add(TIMES_CONTACTED_SORT_COLUMN, Contacts.TIMES_CONTACTED) + .add(TIMES_USED_SORT_COLUMN, "SUM(" + DataUsageStatColumns.CONCRETE_TIMES_USED + ")") .build(); /** Contains just the contacts vCard columns */ @@ -3680,58 +3686,81 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun case CONTACTS_STREQUENT_FILTER: case CONTACTS_STREQUENT: { - String filterSql = null; - if (match == CONTACTS_STREQUENT_FILTER - && uri.getPathSegments().size() > 3) { + // Basically the resultant SQL should look like this: + // (SQL for listing starred items) + // UNION ALL + // (SQL for listing frequently contacted items) + // ORDER BY ... + + final boolean phoneOnly = readBooleanQueryParameter( + uri, ContactsContract.STREQUENT_PHONE_ONLY, false); + if (match == CONTACTS_STREQUENT_FILTER && uri.getPathSegments().size() > 3) { String filterParam = uri.getLastPathSegment(); StringBuilder sb = new StringBuilder(); sb.append(Contacts._ID + " IN "); appendContactFilterAsNestedQuery(sb, filterParam); - filterSql = sb.toString(); + selection = DbQueryUtils.concatenateClauses(selection, sb.toString()); } - setTablesAndProjectionMapForContacts(qb, uri, projection); - - String[] starredProjection = null; - String[] frequentProjection = null; + String[] subProjection = null; if (projection != null) { - starredProjection = - appendProjectionArg(projection, TIMES_CONTACTED_SORT_COLUMN); - frequentProjection = - appendProjectionArg(projection, TIMES_CONTACTED_SORT_COLUMN); + subProjection = appendProjectionArg(projection, TIMES_USED_SORT_COLUMN); } - qb.setProjectionMap(sStrequentStarredProjectionMap); // Build the first query for starred - if (filterSql != null) { - qb.appendWhere(filterSql + " AND "); - } - qb.appendWhere(Contacts.IS_USER_PROFILE + "=0"); - final String starredQuery = qb.buildQuery(starredProjection, + setTablesAndProjectionMapForContacts(qb, uri, projection, + false /* for frequent */, phoneOnly); + qb.setProjectionMap(sStrequentStarredProjectionMap); + qb.appendWhere(DbQueryUtils.concatenateClauses( + selection, Contacts.IS_USER_PROFILE + "=0")); + qb.setStrict(true); + final String starredQuery = qb.buildQuery(subProjection, Contacts.STARRED + "=1", Contacts._ID, null, null, null); - // Build the second query for frequent + // Reset the builder. qb = new SQLiteQueryBuilder(); - setTablesAndProjectionMapForContacts(qb, uri, projection); + + // Build the second query for frequent + setTablesAndProjectionMapForContacts(qb, uri, projection, + true /* for frequent */, phoneOnly); qb.setProjectionMap(sStrequentFrequentProjectionMap); - if (filterSql != null) { - qb.appendWhere(filterSql + " AND "); - } - qb.appendWhere(Contacts.IS_USER_PROFILE + "=0"); - final String frequentQuery = qb.buildQuery(frequentProjection, - Contacts.TIMES_CONTACTED + " > 0 AND (" + Contacts.STARRED - + " = 0 OR " + Contacts.STARRED + " IS NULL)", + 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); // Put them together - final String query = qb.buildUnionQuery(new String[] {starredQuery, frequentQuery}, - STREQUENT_ORDER_BY, STREQUENT_LIMIT); - Cursor c = db.rawQuery(query, null); - if (c != null) { - c.setNotificationUri(getContext().getContentResolver(), + final String unionQuery = + qb.buildUnionQuery(new String[] {starredQuery, frequentQuery}, + STREQUENT_ORDER_BY, STREQUENT_LIMIT); + + // Here, we need to use selection / selectionArgs (supplied from users) "twice", + // as we want them both for starred items and for frequently contacted items. + // + // e.g. if the user specify selection = "starred =?" and selectionArgs = "0", + // the resultant SQL should be like: + // SELECT ... WHERE starred =? AND ... + // UNION ALL + // SELECT ... WHERE starred =? AND ... + String[] doubledSelectionArgs = null; + if (selectionArgs != null) { + final int length = selectionArgs.length; + doubledSelectionArgs = new String[length * 2]; + for (int i = 0; i < length; i++) { + final String arg = selectionArgs[i]; + doubledSelectionArgs[i] = arg; + doubledSelectionArgs[length + i] = arg; + } + } + + Cursor cursor = db.rawQuery(unionQuery, doubledSelectionArgs); + if (cursor != null) { + cursor.setNotificationUri(getContext().getContentResolver(), ContactsContract.AUTHORITY_URI); } - return c; + return cursor; } case CONTACTS_GROUP: { @@ -4753,8 +4782,42 @@ public class ContactsProvider2 extends SQLiteContentProvider implements OnAccoun private void setTablesAndProjectionMapForContacts(SQLiteQueryBuilder qb, Uri uri, String[] projection) { + setTablesAndProjectionMapForContacts(qb, uri, projection, false, 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}. + */ + private void setTablesAndProjectionMapForContacts(SQLiteQueryBuilder qb, Uri uri, + String[] projection, boolean forStrequentFrequent, boolean strequentPhoneCallOnly) { StringBuilder sb = new StringBuilder(); - sb.append(mDbHelper.getContactView(shouldExcludeRestrictedData(uri))); + String viewName = mDbHelper.getContactView(shouldExcludeRestrictedData(uri)); + sb.append(viewName); + + // 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. + sb.append(" INNER JOIN " + + mDbHelper.getDataUsageStatView() + " AS " + Tables.DATA_USAGE_STAT + + " ON (" + + DbQueryUtils.concatenateClauses( + DataUsageStatColumns.CONCRETE_TIMES_USED + " > 0", + RawContacts.CONTACT_ID + "=" + viewName + "." + Contacts._ID, + strequentPhoneCallOnlyClause) + + ")"); + } + appendContactPresenceJoin(sb, projection, Contacts._ID); appendContactStatusUpdateJoin(sb, projection, ContactsColumns.LAST_STATUS_UPDATE_ID); qb.setTables(sb.toString()); |