summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorblong <blong@codeaurora.org>2015-08-24 10:31:19 +0800
committerGerrit - the friendly Code Review server <code-review@localhost>2015-09-05 23:51:57 -0700
commitbaabb38c523fbc0660cc0d573ed062df306c6a95 (patch)
treef839c892467ec48715e11a5051e5a576510c484d /src
parente52bc7e9db5534767f3b44f70ad4a6d25089347c (diff)
downloadpackages_providers_ContactsProvider-baabb38c523fbc0660cc0d573ed062df306c6a95.zip
packages_providers_ContactsProvider-baabb38c523fbc0660cc0d573ed062df306c6a95.tar.gz
packages_providers_ContactsProvider-baabb38c523fbc0660cc0d573ed062df306c6a95.tar.bz2
Add fuzzy search for contact number
- Add a new interface to support fuzzy search , it can search out the contacts by any parts of number, such as for phone number 13567854, it can be searched out by 356,785 and so on. Change-Id: I89175ef49f70446a3f061684ae5e2abd92e92445
Diffstat (limited to 'src')
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java226
1 files changed, 223 insertions, 3 deletions
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 94bf413..8fe1acd 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -1455,6 +1455,7 @@ public class ContactsProvider2 extends AbstractContactsProvider
private Handler mBackgroundHandler;
private long mLastPhotoCleanup = 0;
+ private boolean isPhoneNumberFuzzySearchEnabled;
private FastScrollingIndexCache mFastScrollingIndexCache;
@@ -1528,6 +1529,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;
@@ -7414,11 +7417,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 {
- appendSearchIndexJoin(sb, filter, false, null, null, null, 0, false);
+ 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 {
+ 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,