summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXiaojing Zhang <zhangx@codeaurora.org>2015-10-20 17:03:10 +0800
committerXiaojing Zhang <zhangx@codeaurora.org>2015-10-20 17:03:10 +0800
commit4d76184edff21e7a175d83f9481885f29624c99a (patch)
tree705dc9646b45a3575e972587034ab954baa2c53c
parent5d66d7a44b1fce9258d34892800c3f8f3c4f9522 (diff)
parentb7da0059835ea0df491fa2a1be14b87ca46dd85d (diff)
downloadpackages_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.xml4
-rw-r--r--res/values/config.xml36
-rwxr-xr-x[-rw-r--r--]res/values/strings.xml3
-rw-r--r--src/com/android/providers/contacts/CallLogProvider.java5
-rw-r--r--src/com/android/providers/contacts/ContactsDatabaseHelper.java91
-rw-r--r--src/com/android/providers/contacts/ContactsProvider2.java301
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,