diff options
author | Xiaojing Zhang <zhangx@codeaurora.org> | 2015-10-20 17:03:10 +0800 |
---|---|---|
committer | Xiaojing Zhang <zhangx@codeaurora.org> | 2015-10-20 17:03:10 +0800 |
commit | 4d76184edff21e7a175d83f9481885f29624c99a (patch) | |
tree | 705dc9646b45a3575e972587034ab954baa2c53c | |
parent | 5d66d7a44b1fce9258d34892800c3f8f3c4f9522 (diff) | |
parent | b7da0059835ea0df491fa2a1be14b87ca46dd85d (diff) | |
download | packages_providers_ContactsProvider-4d76184edff21e7a175d83f9481885f29624c99a.zip packages_providers_ContactsProvider-4d76184edff21e7a175d83f9481885f29624c99a.tar.gz packages_providers_ContactsProvider-4d76184edff21e7a175d83f9481885f29624c99a.tar.bz2 |
Merge remote-tracking branch 'remotes/quic/ui_dev_2.0' into HEAD
Change-Id: I2af4be7298adebbb7c6b40dbe31a37c9f4441bfa
-rwxr-xr-x[-rw-r--r--] | res/values-zh-rCN/strings.xml | 4 | ||||
-rw-r--r-- | res/values/config.xml | 36 | ||||
-rwxr-xr-x[-rw-r--r--] | res/values/strings.xml | 3 | ||||
-rw-r--r-- | src/com/android/providers/contacts/CallLogProvider.java | 5 | ||||
-rw-r--r-- | src/com/android/providers/contacts/ContactsDatabaseHelper.java | 91 | ||||
-rw-r--r-- | src/com/android/providers/contacts/ContactsProvider2.java | 301 |
6 files changed, 430 insertions, 10 deletions
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index ad4eda6..c3754e8 100644..100755 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -32,4 +32,8 @@ <string name="debug_dump_email_sender_picker" msgid="3534420908672176460">"选择用于发送文件的程序"</string> <string name="debug_dump_email_subject" msgid="108188398416385976">"随附通讯录数据库"</string> <string name="debug_dump_email_body" msgid="4577749800871444318">"附件是我的通讯录数据库,其中包含我所有的联系人信息,因此请谨慎处理。"</string> + + <string name="group_title_co_workers">"同事"</string> + <string name="group_title_family">"家人"</string> + <string name="group_title_friends">"朋友"</string> </resources> diff --git a/res/values/config.xml b/res/values/config.xml new file mode 100644 index 0000000..7d79bbe --- /dev/null +++ b/res/values/config.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2015, The Linux Foundation. All Rights Reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of The Linux Foundation nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--> + +<resources> + + <!-- If true, it supports fuzzy search for contacts number --> + <bool name="phone_number_fuzzy_search">true</bool> + +</resources>
\ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 8be7bca..5c0ef96 100644..100755 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -69,4 +69,7 @@ <!-- Debug tool - email body [CHAR LIMIT=NONE] --> <string name="debug_dump_email_body">Attached is my contacts database with all my contacts information. Handle with care.</string> + <string name="group_title_co_workers">"Coworkers"</string> + <string name="group_title_family">"Family"</string> + <string name="group_title_friends">"Friends"</string> </resources> diff --git a/src/com/android/providers/contacts/CallLogProvider.java b/src/com/android/providers/contacts/CallLogProvider.java index f83e4a7..9070e78 100644 --- a/src/com/android/providers/contacts/CallLogProvider.java +++ b/src/com/android/providers/contacts/CallLogProvider.java @@ -49,6 +49,7 @@ import com.android.providers.contacts.ContactsDatabaseHelper.DbProperties; import com.android.providers.contacts.ContactsDatabaseHelper.Tables; import com.android.providers.contacts.util.SelectionBuilder; import com.android.providers.contacts.util.UserUtils; + import com.google.common.annotations.VisibleForTesting; import java.util.HashMap; @@ -364,6 +365,10 @@ public class CallLogProvider extends ContentProvider { case CALLS: return getDatabaseModifier(db).delete(Tables.CALLS, selectionBuilder.build(), selectionArgs); + case CALLS_ID: + return getDatabaseModifier(db).delete(Tables.CALLS, + new SelectionBuilder(Calls._ID + "=?").build(), + new String[] { uri.getLastPathSegment() }); default: throw new UnsupportedOperationException("Cannot delete that URL: " + uri); } diff --git a/src/com/android/providers/contacts/ContactsDatabaseHelper.java b/src/com/android/providers/contacts/ContactsDatabaseHelper.java index 1d43fbc..c84c97f 100644 --- a/src/com/android/providers/contacts/ContactsDatabaseHelper.java +++ b/src/com/android/providers/contacts/ContactsDatabaseHelper.java @@ -285,11 +285,11 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { */ public static final String GROUP_MEMBER_COUNT = " LEFT OUTER JOIN (SELECT " - + "data.data1 AS member_count_group_id, " - + "COUNT(data.raw_contact_id) AS group_member_count " - + "FROM data " + + "view_data.data1 AS member_count_group_id, " + + "COUNT(DISTINCT view_data.contact_id) AS group_member_count " + + "FROM view_data " + "WHERE " - + "data.mimetype_id = (SELECT _id FROM mimetypes WHERE " + + "view_data.mimetype_id = (SELECT _id FROM mimetypes WHERE " + "mimetypes.mimetype = '" + GroupMembership.CONTENT_ITEM_TYPE + "')" + "GROUP BY member_count_group_id) AS member_count_table" // End of inner query + " ON (groups._id = member_count_table.member_count_group_id)"; @@ -1597,6 +1597,9 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { // Add the legacy API support views, etc. LegacyApiSupport.createDatabase(db); + createPhoneAccount(db); + createDefaultGroups4PhoneAccount(db); + if (mDatabaseOptimizationEnabled) { // This will create a sqlite_stat1 table that is used for query optimization db.execSQL("ANALYZE;"); @@ -3478,6 +3481,7 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { insertNameLookup(db); rebuildSortKeys(db); createContactsIndexes(db, rebuildSqliteStats); + rebuildDefaultGroupTitles(db, locales.getPrimaryLocale()); FastScrollingIndexCache.getInstance(mContext).invalidate(); // Update the ICU version used to generate the locale derived data @@ -3487,6 +3491,35 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { } /** + * change the default groups' title according to the locale + */ + private void rebuildDefaultGroupTitles(SQLiteDatabase db, Locale locale) { + String[] PROJECTION = new String[] {Groups._ID, Groups.TITLE_RES}; + Cursor cursor = db.query(Tables.GROUPS, PROJECTION, Groups.TITLE_RES + " IS NOT NULL ", null + ,null, null, Groups._ID); + + if (cursor == null) { + return; + } + + try { + long groupId = -1; + int titleRes = 0; + ContentValues values = new ContentValues(); + while (cursor.moveToNext()) { + groupId = cursor.getLong(0); + titleRes = cursor.getInt(1); + values.clear(); + values.put(Groups.TITLE, mContext.getResources().getString(titleRes)); + db.update(Tables.GROUPS, values, Groups._ID + " = ?", new String[] { + String.valueOf(groupId)}); + } + } finally { + cursor.close(); + } + } + + /** * Regenerates all locale-sensitive data if needed: * nickname_lookup, name_lookup and sort keys. Invalidates the fast * scrolling index cache. @@ -5989,4 +6022,54 @@ public class ContactsDatabaseHelper extends SQLiteOpenHelper { " WHERE " + SearchIndexColumns.CONTACT_ID + "=CAST(? AS int)", new String[] {String.valueOf(contactId)}); } + + private void createDefaultGroups4PhoneAccount(SQLiteDatabase db) { + // 3 default groups + String[] title = new String[3]; + int[] titleRes = new int[3]; + + title[0] = mContext.getResources().getString(R.string.group_title_co_workers); + titleRes[0] = R.string.group_title_co_workers; + + title[1] = mContext.getResources().getString(R.string.group_title_family); + titleRes[1] = R.string.group_title_family; + + title[2] = mContext.getResources().getString(R.string.group_title_friends); + titleRes[2] = R.string.group_title_friends; + + for (int i = 0; i < title.length; i++) { + db.execSQL("INSERT INTO " + Tables.GROUPS + " (" + + GroupsColumns.ACCOUNT_ID + "," + + Groups.SOURCE_ID + "," + + Groups.VERSION + "," + + Groups.DIRTY + "," + + Groups.TITLE + "," + + Groups.TITLE_RES + "," + + Groups.NOTES + "," + + Groups.SYSTEM_ID + "," + + Groups.DELETED + "," + + Groups.GROUP_VISIBLE + "," + + Groups.SHOULD_SYNC + "," + + Groups.AUTO_ADD + "," + + Groups.FAVORITES + "," + + Groups.GROUP_IS_READ_ONLY + "," + + Groups.SYNC1 + ", " + + Groups.SYNC2 + ", " + + Groups.SYNC3 + ", " + + Groups.SYNC4 + ") " + + "VALUES (1,1,1,0,'" + + title[i] + "'," + titleRes[i] + + ",NULL,NULL,0,1,1,0,0,1,'','','','');" + ); + } + } + + public void createPhoneAccount(SQLiteDatabase db) { + String sql = "INSERT INTO " + Tables.ACCOUNTS + + "(" + AccountsColumns.ACCOUNT_NAME + "," + + AccountsColumns.ACCOUNT_TYPE + ") " + + "VALUES ('" + AccountWithDataSet.PHONE_NAME + "','" + + AccountWithDataSet.ACCOUNT_TYPE_PHONE + "')"; + db.execSQL(sql); + } } diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java index 94bf413..55bae54 100644 --- a/src/com/android/providers/contacts/ContactsProvider2.java +++ b/src/com/android/providers/contacts/ContactsProvider2.java @@ -302,6 +302,10 @@ public class ContactsProvider2 extends AbstractContactsProvider + Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; private static String WITHOUT_SIM_FLAG = "no_sim"; + private boolean isWhereAppended = false; + + public static final String ADD_GROUP_MEMBERS = "add_group_members"; + private static final int CONTACTS = 1000; private static final int CONTACTS_ID = 1001; private static final int CONTACTS_LOOKUP = 1002; @@ -533,6 +537,22 @@ public class ContactsProvider2 extends AbstractContactsProvider + " FROM " + Tables.GROUPS + " WHERE " + Groups.TITLE + "=?)))"; + private static final String CONTACTS_IN_GROUP_ID_SELECT = + Contacts._ID + " IN " + + "(SELECT DISTINCT " + + Data.CONTACT_ID + + " FROM " + Views.DATA + + " WHERE " + Data.MIMETYPE + " = '" + GroupMembership.CONTENT_ITEM_TYPE + + "' AND " + Data.DATA1 + " = ?)"; + + private static final String CONTACTS_NOT_IN_GROUP_ID_SELECT = + Contacts._ID + " NOT IN " + + "(SELECT DISTINCT " + + Data.CONTACT_ID + + " FROM " + Views.DATA + + " WHERE " + Data.MIMETYPE + " = '" + GroupMembership.CONTENT_ITEM_TYPE + + "' AND " + Data.DATA1 + " = ?)"; + /** Sql for updating DIRTY flag on multiple raw contacts */ private static final String UPDATE_RAW_CONTACT_SET_DIRTY_SQL = "UPDATE " + Tables.RAW_CONTACTS + @@ -1207,6 +1227,7 @@ public class ContactsProvider2 extends AbstractContactsProvider matcher.addURI(ContactsContract.AUTHORITY, "contacts/strequent/", CONTACTS_STREQUENT); matcher.addURI(ContactsContract.AUTHORITY, "contacts/strequent/filter/*", CONTACTS_STREQUENT_FILTER); + matcher.addURI(ContactsContract.AUTHORITY, "contacts/group", CONTACTS_GROUP); matcher.addURI(ContactsContract.AUTHORITY, "contacts/group/*", CONTACTS_GROUP); matcher.addURI(ContactsContract.AUTHORITY, "contacts/frequent", CONTACTS_FREQUENT); matcher.addURI(ContactsContract.AUTHORITY, "contacts/delete_usage", CONTACTS_DELETE_USAGE); @@ -1455,6 +1476,7 @@ public class ContactsProvider2 extends AbstractContactsProvider private Handler mBackgroundHandler; private long mLastPhotoCleanup = 0; + private boolean isPhoneNumberFuzzySearchEnabled; private FastScrollingIndexCache mFastScrollingIndexCache; @@ -1528,6 +1550,8 @@ public class ContactsProvider2 extends AbstractContactsProvider profileInfo.authority = ContactsContract.AUTHORITY; mProfileProvider.attachInfo(getContext(), profileInfo); mProfileHelper = mProfileProvider.getDatabaseHelper(getContext()); + isPhoneNumberFuzzySearchEnabled = getContext().getResources().getBoolean( + R.bool.phone_number_fuzzy_search); // Initialize the pre-authorized URI duration. mPreAuthorizedUriDuration = DEFAULT_PREAUTHORIZED_URI_EXPIRATION; @@ -5354,13 +5378,43 @@ public class ContactsProvider2 extends AbstractContactsProvider filterParam = uri.getLastPathSegment(); } - // If the query consists of a single word, we can do snippetizing after-the-fact for - // a performance boost. Otherwise, we can't defer. + // If the query consists of a single word, we can do snippetizing + // after-the-fact for a performance boost. Otherwise, we can't defer. snippetDeferred = isSingleWordQuery(filterParam) && deferredSnipRequested && snippetNeeded(projection); setTablesAndProjectionMapForContactsWithSnippet( qb, uri, projection, filterParam, directoryId, snippetDeferred); + long groupId = -1; + try { + groupId = Long.parseLong(uri.getQueryParameter(Groups._ID)); + } catch (Exception exception) { + groupId = -1; + } + if (groupId != -1) { + StringBuilder groupBuilder = new StringBuilder(); + if (uri.getBooleanQueryParameter(ADD_GROUP_MEMBERS, false)) { + // filter all the contacts that are NOT assigned to the + // group whose id is 'groupId' + groupBuilder.append(Contacts._ID + " NOT IN (" + " SELECT DISTINCT " + + Data.CONTACT_ID + + " FROM " + Views.DATA + " WHERE " + Data.MIMETYPE + " = '" + + GroupMembership.CONTENT_ITEM_TYPE + "' AND " + Data.DATA1 + + " = " + groupId + ")"); + } else { + // filter all the contacts that are assigned to the + // group whose id is 'groupId' + groupBuilder.append(Contacts._ID + " IN (" + " SELECT DISTINCT " + + Data.CONTACT_ID + + " FROM " + Views.DATA + " WHERE " + Data.MIMETYPE + " = '" + + GroupMembership.CONTENT_ITEM_TYPE + "' AND " + Data.DATA1 + + " = " + groupId + ")"); + } + if (isWhereAppended) { + qb.appendWhere(" AND "); + } + qb.appendWhere(groupBuilder.toString()); + } break; } @@ -5542,7 +5596,23 @@ public class ContactsProvider2 extends AbstractContactsProvider case CONTACTS_GROUP: { setTablesAndProjectionMapForContacts(qb, projection); - if (uri.getPathSegments().size() > 2) { + appendLocalDirectoryAndAccountSelectionIfNeeded(qb, directoryId, uri); + long groupId = -1; + try { + groupId = Long.parseLong(uri.getQueryParameter(Groups._ID)); + } catch (Exception exception) { + groupId = -1; + } + if (groupId != -1) { + qb.appendWhere(" AND "); + if (uri.getBooleanQueryParameter(ADD_GROUP_MEMBERS, false)) { + qb.appendWhere(CONTACTS_NOT_IN_GROUP_ID_SELECT); + } else { + qb.appendWhere(CONTACTS_IN_GROUP_ID_SELECT); + } + selectionArgs = insertSelectionArg(selectionArgs, String.valueOf(groupId)); + } else if (uri.getPathSegments().size() > 2) { + qb.appendWhere(" AND "); qb.appendWhere(CONTACTS_IN_GROUP_SELECT); String groupMimeTypeId = String.valueOf( mDbHelper.get().getMimeTypeId(GroupMembership.CONTENT_ITEM_TYPE)); @@ -7323,6 +7393,7 @@ public class ContactsProvider2 extends AbstractContactsProvider private void setTablesAndProjectionMapForContactsWithSnippet(SQLiteQueryBuilder qb, Uri uri, String[] projection, String filter, long directoryId, boolean deferSnippeting) { + isWhereAppended = false; StringBuilder sb = new StringBuilder(); sb.append(Views.CONTACTS); @@ -7373,6 +7444,7 @@ public class ContactsProvider2 extends AbstractContactsProvider if (!TextUtils.isEmpty(sbWhere.toString())) { if ("true".equals(withoutSim)) { qb.appendWhere(sbWhere.toString()); + isWhereAppended = true; } else { sb.append(sbWhere.toString()); } @@ -7414,11 +7486,228 @@ public class ContactsProvider2 extends AbstractContactsProvider int maxTokens = args != null && args.length > 3 ? Integer.parseInt(args[3]) : DEFAULT_SNIPPET_ARG_MAX_TOKENS; - appendSearchIndexJoin( - sb, filter, true, startMatch, endMatch, ellipsis, maxTokens, deferSnippeting); + if (isPhoneNumberFuzzySearchEnabled) { + appendSearchIndexJoinForFuzzySearch(sb, filter, true, + startMatch, endMatch, ellipsis, maxTokens, deferSnippeting); + } else { + appendSearchIndexJoin(sb, filter, true, startMatch, endMatch, + ellipsis, maxTokens, deferSnippeting); + } + } else { + if (isPhoneNumberFuzzySearchEnabled) { + appendSearchIndexJoinForFuzzySearch(sb, filter, false, null, + null, null, 0, false); + } else { + appendSearchIndexJoin(sb, filter, false, null, null, null, 0, + false); + } + } + } + + public void appendSearchIndexJoinForFuzzySearch(StringBuilder sb, String filter, + boolean snippetNeeded, String startMatch, String endMatch, String ellipsis, + int maxTokens, boolean deferSnippeting) { + boolean isEmailAddress = false; + String emailAddress = null; + boolean isPhoneNumber = false; + String phoneNumber = null; + String numberE164 = null; + + + if (filter.indexOf('@') != -1) { + emailAddress = mDbHelper.get().extractAddressFromEmailAddress(filter); + isEmailAddress = !TextUtils.isEmpty(emailAddress); } else { - appendSearchIndexJoin(sb, filter, false, null, null, null, 0, false); + isPhoneNumber = isPhoneNumber(filter); + if (isPhoneNumber) { + phoneNumber = PhoneNumberUtils.normalizeNumber(filter); + numberE164 = PhoneNumberUtils.formatNumberToE164(phoneNumber, + mDbHelper.get().getCurrentCountryIso()); + } } + + final String SNIPPET_CONTACT_ID = "snippet_contact_id"; + sb.append(" JOIN (SELECT " + SearchIndexColumns.CONTACT_ID + " AS " + SNIPPET_CONTACT_ID); + if (snippetNeeded) { + sb.append(", "); + if (isEmailAddress) { + sb.append("ifnull("); + if (!deferSnippeting) { + // Add the snippet marker only when we're really creating snippet. + DatabaseUtils.appendEscapedSQLString(sb, startMatch); + sb.append("||"); + } + sb.append("(SELECT MIN(" + Email.ADDRESS + ")"); + sb.append(" FROM " + Tables.DATA_JOIN_RAW_CONTACTS); + sb.append(" WHERE " + Tables.SEARCH_INDEX + "." + SearchIndexColumns.CONTACT_ID); + sb.append("=" + RawContacts.CONTACT_ID + " AND " + Email.ADDRESS + " LIKE "); + DatabaseUtils.appendEscapedSQLString(sb, filter + "%"); + sb.append(")"); + if (!deferSnippeting) { + sb.append("||"); + DatabaseUtils.appendEscapedSQLString(sb, endMatch); + } + sb.append(","); + + if (deferSnippeting) { + sb.append(SearchIndexColumns.CONTENT); + } else { + appendSnippetFunction(sb, startMatch, endMatch, ellipsis, maxTokens); + } + sb.append(")"); + } else if (isPhoneNumber) { + sb.append("ifnull("); + if (!deferSnippeting) { + // Add the snippet marker only when we're really creating snippet. + DatabaseUtils.appendEscapedSQLString(sb, startMatch); + sb.append("||"); + } + sb.append("(SELECT MIN(" + Phone.NUMBER + ")"); + sb.append(" FROM " + + Tables.DATA_JOIN_RAW_CONTACTS + " JOIN " + Tables.PHONE_LOOKUP); + sb.append(" ON " + DataColumns.CONCRETE_ID); + sb.append("=" + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.DATA_ID); + sb.append(" WHERE " + Tables.SEARCH_INDEX + "." + SearchIndexColumns.CONTACT_ID); + sb.append("=" + RawContacts.CONTACT_ID); + sb.append(" AND (" + PhoneLookupColumns.NORMALIZED_NUMBER + " LIKE '%"); + sb.append(phoneNumber); + sb.append("%'"); + sb.append("))"); + if (! deferSnippeting) { + sb.append("||"); + DatabaseUtils.appendEscapedSQLString(sb, endMatch); + } + sb.append(","); + + if (deferSnippeting) { + sb.append(SearchIndexColumns.CONTENT); + } else { + appendSnippetFunction(sb, startMatch, endMatch, ellipsis, maxTokens); + } + sb.append(")"); + } else { + final String normalizedFilter = NameNormalizer.normalize(filter); + if (!TextUtils.isEmpty(normalizedFilter)) { + if (deferSnippeting) { + sb.append(SearchIndexColumns.CONTENT); + } else { + sb.append("(CASE WHEN EXISTS (SELECT 1 FROM "); + sb.append(Tables.RAW_CONTACTS + " AS rc INNER JOIN "); + sb.append(Tables.NAME_LOOKUP + " AS nl ON (rc." + RawContacts._ID); + sb.append("=nl." + NameLookupColumns.RAW_CONTACT_ID); + sb.append(") WHERE nl." + NameLookupColumns.NORMALIZED_NAME); + sb.append(" GLOB '" + normalizedFilter + "*' AND "); + sb.append("nl." + NameLookupColumns.NAME_TYPE + "="); + sb.append(NameLookupType.NAME_COLLATION_KEY + " AND "); + sb.append(Tables.SEARCH_INDEX + "." + SearchIndexColumns.CONTACT_ID); + sb.append("=rc." + RawContacts.CONTACT_ID); + sb.append(") THEN NULL ELSE "); + appendSnippetFunction(sb, startMatch, endMatch, ellipsis, maxTokens); + sb.append(" END)"); + } + } else { + sb.append("NULL"); + } + } + sb.append(" AS " + SearchSnippets.SNIPPET); + } + + sb.append(" FROM " + Tables.SEARCH_INDEX); + sb.append(" WHERE "); + if (isPhoneNumber) { + sb.append(Tables.SEARCH_INDEX + " MATCH '"); + // normalized version of the phone number (phoneNumber can only have + // + and digits) + final String phoneNumberCriteria = " OR tokens:" + phoneNumber + + "*"; + + // international version of this number (numberE164 can only have + + // and digits) + final String numberE164Criteria = (numberE164 != null && !TextUtils + .equals(numberE164, phoneNumber)) ? " OR tokens:" + + numberE164 + "*" : ""; + + // combine all criteria + final String commonCriteria = phoneNumberCriteria + + numberE164Criteria; + + // search in content + sb.append(SearchIndexManager.getFtsMatchQuery(filter, + FtsQueryBuilder.getDigitsQueryBuilder(commonCriteria))); + sb.append("' AND " + SNIPPET_CONTACT_ID + " IN " + + Tables.DEFAULT_DIRECTORY); + if (snippetNeeded) { + // only support fuzzy search when there is snippet column and + // the filter is phone number! + sb.append(" UNION SELECT " + SearchIndexColumns.CONTACT_ID + " AS " + + SNIPPET_CONTACT_ID); + sb.append(", "); + if (!deferSnippeting) { + // Add the snippet marker only when we're really creating snippet. + DatabaseUtils.appendEscapedSQLString(sb, startMatch); + sb.append("||"); + } + sb.append("(SELECT MIN(" + Phone.NUMBER + ")"); + sb.append(" FROM " + + Tables.DATA_JOIN_RAW_CONTACTS + " JOIN " + Tables.PHONE_LOOKUP); + sb.append(" ON " + DataColumns.CONCRETE_ID); + sb.append("=" + Tables.PHONE_LOOKUP + "." + PhoneLookupColumns.DATA_ID); + sb.append(" WHERE " + Tables.SEARCH_INDEX + "." + SearchIndexColumns.CONTACT_ID); + sb.append("=" + RawContacts.CONTACT_ID); + sb.append(" AND (" + PhoneLookupColumns.NORMALIZED_NUMBER + " LIKE '%"); + sb.append(phoneNumber); + sb.append("%'"); + sb.append("))"); + if (!deferSnippeting) { + sb.append("||"); + DatabaseUtils.appendEscapedSQLString(sb, endMatch); + } + sb.append(" AS " + SearchSnippets.SNIPPET); + sb.append(" FROM " + Tables.SEARCH_INDEX); + sb.append(" WHERE " + SearchSnippets.SNIPPET + " IS NOT NULL "); + sb.append(" AND " + SNIPPET_CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY + ")"); + } else { + sb.append(")"); + } + } else { + sb.append(Tables.SEARCH_INDEX + " MATCH '"); + if (isEmailAddress) { + // we know that the emailAddress contains a @. This phrase search should be + // scoped against "content:" only, but unfortunately SQLite doesn't support + // phrases and scoped columns at once. This is fine in this case however, because: + // We can't erroneously match against name, as it is all-hex (so the @ can't match) + // We can't match against tokens, because phone-numbers can't contain @ + final String sanitizedEmailAddress = + emailAddress == null ? "" : sanitizeMatch(emailAddress); + sb.append("\""); + sb.append(sanitizedEmailAddress); + sb.append("*\""); + } else if (isPhoneNumber) { + // normalized version of the phone number (phoneNumber can only have + and digits) + final String phoneNumberCriteria = " OR tokens:" + phoneNumber + "*"; + + // international version of this number (numberE164 can only have + and digits) + final String numberE164Criteria = + (numberE164 != null && !TextUtils.equals(numberE164, phoneNumber)) + ? " OR tokens:" + numberE164 + "*" + : ""; + + // combine all criteria + final String commonCriteria = + phoneNumberCriteria + numberE164Criteria; + + // search in content + sb.append(SearchIndexManager.getFtsMatchQuery(filter, + FtsQueryBuilder.getDigitsQueryBuilder(commonCriteria))); + } else { + // general case: not a phone number, not an email-address + sb.append(SearchIndexManager.getFtsMatchQuery(filter, + FtsQueryBuilder.SCOPED_NAME_NORMALIZING)); + } + // Omit results in "Other Contacts". + sb.append("' AND " + SNIPPET_CONTACT_ID + " IN " + Tables.DEFAULT_DIRECTORY + ")"); + } + sb.append(" ON (" + Contacts._ID + "=" + SNIPPET_CONTACT_ID + ")"); } public void appendSearchIndexJoin(StringBuilder sb, String filter, |