Merge "Remove the obsolete account columns from newly created db"
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 7f24194..44357c3 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -19,13 +19,13 @@
     <string name="sharedUserLabel" msgid="8024311725474286801">"Асноўныя праграмы для Android"</string>
     <string name="app_label" msgid="3389954322874982620">"Сховішча кантактаў"</string>
     <string name="provider_label" msgid="6012150850819899907">"Кантакты"</string>
-    <string name="upgrade_msg" msgid="6174884195179549239">"Абнаўленне базы дадзеных кантактаў."</string>
-    <string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Для абнаўлення кантакту патрабуецца больш памяці"</string>
-    <string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Абнаўленне сховішча кантактаў"</string>
-    <string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Выберыце, каб завяршыць абнаўленне."</string>
+    <string name="upgrade_msg" msgid="8640807392794309950">"Абнаўленне базы дадзеных кантактаў."</string>
+    <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Для абнаўлення кантактаў патрабуецца больш памяці."</string>
+    <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Каб абнавiць кантакты, патрабуецца больш памяцi"</string>
+    <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Націсніце, каб завершыць абнаўленне."</string>
     <string name="default_directory" msgid="93961630309570294">"Кантакты"</string>
     <string name="local_invisible_directory" msgid="705244318477396120">"Іншае"</string>
     <string name="read_write_all_voicemail_label" msgid="4557216100818257560">"Доступ да ўсіх галасавых паведамленняў"</string>
-    <string name="read_write_all_voicemail_description" msgid="2249895806470926882">"Дазваляе прыкладанням захоўваць і ўзнаўляць усе галасавыя паведамленні, да якіх можа даступіцца гэта прылада"</string>
+    <string name="read_write_all_voicemail_description" msgid="8029809937805761356">"Дазваляе прыкладанню захоўваць і прайграваць усе даступныя для гэтай прылады паведамленні галасавой пошты."</string>
     <string name="voicemail_from_column" msgid="435732568832121444">"Галасавое паведамленне ад "</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 5cac543..6fda0d5 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -19,13 +19,13 @@
     <string name="sharedUserLabel" msgid="8024311725474286801">"Androidi tuumrakendused"</string>
     <string name="app_label" msgid="3389954322874982620">"Kontaktiruum"</string>
     <string name="provider_label" msgid="6012150850819899907">"Kontaktid"</string>
-    <string name="upgrade_msg" msgid="6174884195179549239">"Kontaktide andmebaasi uuendamine."</string>
-    <string name="upgrade_out_of_memory_notification_ticker" msgid="4089605622758004662">"Kontakti uuendamiseks on vaja rohkem mälu"</string>
-    <string name="upgrade_out_of_memory_notification_title" msgid="7849508493764133004">"Kontakti mäluruumi uuendamine"</string>
-    <string name="upgrade_out_of_memory_notification_text" msgid="3967762223137708403">"Valige uuenduse lõpule viimiseks."</string>
+    <string name="upgrade_msg" msgid="8640807392794309950">"Kontaktide andmebaasi uuendamine."</string>
+    <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Kontaktisikute uuendamiseks on vaja rohkem mälu"</string>
+    <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Kontaktide salvestusruumi uuendamine"</string>
+    <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Puudutage uuendamise lõpuleviimiseks."</string>
     <string name="default_directory" msgid="93961630309570294">"Kontaktid"</string>
     <string name="local_invisible_directory" msgid="705244318477396120">"Muu"</string>
     <string name="read_write_all_voicemail_label" msgid="4557216100818257560">"Juurdepääs kõigile kõnepostisõnumitele"</string>
-    <string name="read_write_all_voicemail_description" msgid="2249895806470926882">"Võimaldab rakendusel hoida ja vastu võtta kõik kõnepostisõnumid, millele see seade juurde pääseb."</string>
+    <string name="read_write_all_voicemail_description" msgid="8029809937805761356">"Võimaldab rakendusel salvestada ja vastu võtta kõik kõnepostisõnumid, mille juurde seade pääseb."</string>
     <string name="voicemail_from_column" msgid="435732568832121444">"Kõnepost kontaktilt "</string>
 </resources>
diff --git a/res/values-rm/strings.xml b/res/values-rm/strings.xml
index 317a60d..b0e2898 100644
--- a/res/values-rm/strings.xml
+++ b/res/values-rm/strings.xml
@@ -22,9 +22,12 @@
     <string name="provider_label" msgid="6012150850819899907">"Contacts"</string>
     <!-- no translation found for upgrade_msg (8640807392794309950) -->
     <skip />
-    <!-- outdated translation 4089605622758004662 -->     <string name="upgrade_out_of_memory_notification_ticker" msgid="7638747231223520477">"Ina actualisaziun da contacts basegna dapli memoria"</string>
-    <!-- outdated translation 7849508493764133004 -->     <string name="upgrade_out_of_memory_notification_title" msgid="8888171924684998531">"Actualisar la memoria da contacts"</string>
-    <!-- outdated translation 3967762223137708403 -->     <string name="upgrade_out_of_memory_notification_text" msgid="8438179450336437626">"Tscherner per cumplettar l\'actualisaziun"</string>
+    <!-- no translation found for upgrade_out_of_memory_notification_ticker (7638747231223520477) -->
+    <skip />
+    <!-- no translation found for upgrade_out_of_memory_notification_title (8888171924684998531) -->
+    <skip />
+    <!-- no translation found for upgrade_out_of_memory_notification_text (8438179450336437626) -->
+    <skip />
     <string name="default_directory" msgid="93961630309570294">"Contacts"</string>
     <string name="local_invisible_directory" msgid="705244318477396120">"Auter"</string>
     <!-- no translation found for read_write_all_voicemail_label (4557216100818257560) -->
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 4dbfbcb..89b722d 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -60,6 +60,7 @@
 import android.accounts.AccountManager;
 import android.accounts.OnAccountsUpdateListener;
 import android.app.SearchManager;
+import android.content.CancelationSignal;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentResolver;
@@ -2933,7 +2934,7 @@
         if (dataSet != null) {
             settingsUri.appendQueryParameter(Settings.DATA_SET, dataSet);
         }
-        Cursor c = queryLocal(settingsUri.build(), null, null, null, null, 0);
+        Cursor c = queryLocal(settingsUri.build(), null, null, null, null, 0, null);
         try {
             if (c.getCount() > 0) {
                 // If a record was found, replace it with the new values.
@@ -3163,7 +3164,7 @@
                     Cursor c = queryLocal(streamUri, new String[]{StreamItems._ID},
                             StreamItems.RAW_CONTACT_ID + "=?",
                             new String[]{String.valueOf(rawContactId)},
-                            null, -1 /* directory ID */);
+                            null, -1 /* directory ID */, null);
                     try {
                         if (c.getCount() > 0) {
                             c.moveToFirst();
@@ -3287,7 +3288,7 @@
                 args[1] = Uri.encode(lookupKey);
                 lookupQb.appendWhere(Contacts._ID + "=? AND " + Contacts.LOOKUP_KEY + "=?");
                 Cursor c = query(mActiveDb.get(), lookupQb, null, selection, args, null, null,
-                        null);
+                        null, null);
                 try {
                     if (c.getCount() == 1) {
                         // contact was unmodified so go ahead and delete it
@@ -4198,7 +4199,7 @@
         // so we don't need to worry about updating data we don't have permission to read.
         Cursor c = queryLocal(uri,
                 DataRowHandler.DataUpdateQuery.COLUMNS,
-                selection, selectionArgs, null, -1 /* directory ID */);
+                selection, selectionArgs, null, -1 /* directory ID */, null);
         try {
             while(c.moveToNext()) {
                 count += updateData(mValues, c, callerIsSyncAdapter);
@@ -4625,6 +4626,12 @@
     @Override
     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
             String sortOrder) {
+        return query(uri, projection, selection, selectionArgs, sortOrder, null);
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder, CancelationSignal cancelationSignal) {
 
         waitForAccess(mReadAccessLatch);
 
@@ -4634,7 +4641,8 @@
         // Query the profile DB if appropriate.
         if (mapsToProfileDb(uri)) {
             switchToProfileMode();
-            return mProfileProvider.query(uri, projection, selection, selectionArgs, sortOrder);
+            return mProfileProvider.query(uri, projection, selection, selectionArgs, sortOrder,
+                    cancelationSignal);
         }
 
         // Otherwise proceed with a normal query against the contacts DB.
@@ -4643,15 +4651,16 @@
         String directory = getQueryParameter(uri, ContactsContract.DIRECTORY_PARAM_KEY);
         if (directory == null) {
             return addSnippetExtrasToCursor(uri,
-                    queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1));
+                    queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1,
+                    cancelationSignal));
         } else if (directory.equals("0")) {
             return addSnippetExtrasToCursor(uri,
                     queryLocal(uri, projection, selection, selectionArgs, sortOrder,
-                            Directory.DEFAULT));
+                    Directory.DEFAULT, cancelationSignal));
         } else if (directory.equals("1")) {
             return addSnippetExtrasToCursor(uri,
                     queryLocal(uri, projection, selection, selectionArgs, sortOrder,
-                            Directory.LOCAL_INVISIBLE));
+                    Directory.LOCAL_INVISIBLE, cancelationSignal));
         }
 
         DirectoryInfo directoryInfo = getDirectoryAuthority(directory);
@@ -4787,7 +4796,8 @@
     }
 
     protected Cursor queryLocal(Uri uri, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder, long directoryId) {
+            String[] selectionArgs, String sortOrder, long directoryId,
+            CancelationSignal cancelationSignal) {
         if (VERBOSE_LOGGING) {
             Log.v(TAG, "query=" + uri + " selection=" + selection);
         }
@@ -4843,7 +4853,8 @@
 
                     Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
                             projection, selection, selectionArgs, sortOrder, groupBy, limit,
-                            Contacts._ID, contactId, Contacts.LOOKUP_KEY, lookupKey);
+                            Contacts._ID, contactId, Contacts.LOOKUP_KEY, lookupKey,
+                            cancelationSignal);
                     if (c != null) {
                         return c;
                     }
@@ -4877,7 +4888,8 @@
                     lookupQb.appendWhere(" AND ");
                     Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
                             projection, selection, selectionArgs, sortOrder, groupBy, limit,
-                            Data.CONTACT_ID, contactId, Data.LOOKUP_KEY, lookupKey);
+                            Data.CONTACT_ID, contactId, Data.LOOKUP_KEY, lookupKey,
+                            cancelationSignal);
                     if (c != null) {
                         return c;
                     }
@@ -4920,7 +4932,8 @@
                     Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
                             projection, selection, selectionArgs, sortOrder, groupBy, limit,
                             StreamItems.CONTACT_ID, contactId,
-                            StreamItems.CONTACT_LOOKUP_KEY, lookupKey);
+                            StreamItems.CONTACT_LOOKUP_KEY, lookupKey,
+                            cancelationSignal);
                     if (c != null) {
                         return c;
                     }
@@ -4960,11 +4973,14 @@
                 if (uri.getPathSegments().size() > 2) {
                     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.
+                snippetDeferred = isSingleWordQuery(filterParam)
+                    && deferredSnipRequested && snippetNeeded(projection);
                 setTablesAndProjectionMapForContactsWithSnippet(
                         qb, uri, projection, filterParam, directoryId,
-                        deferredSnipRequested);
-                snippetDeferred = isSingleWordQuery(filterParam) &&
-                        deferredSnipRequested && snippetNeeded(projection);
+                        snippetDeferred);
                 break;
             }
 
@@ -5174,7 +5190,8 @@
                     Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
                             projection, selection, selectionArgs, sortOrder, groupBy, limit,
                             Contacts.Entity.CONTACT_ID, contactId,
-                            Contacts.Entity.LOOKUP_KEY, lookupKey);
+                            Contacts.Entity.LOOKUP_KEY, lookupKey,
+                            cancelationSignal);
                     if (c != null) {
                         return c;
                     }
@@ -5637,7 +5654,7 @@
                     qb.setStrict(true);
                     boolean foundResult = false;
                     Cursor cursor = query(mActiveDb.get(), qb, projection, selection, selectionArgs,
-                            sortOrder, groupBy, limit);
+                            sortOrder, groupBy, limit, cancelationSignal);
                     try {
                         if (cursor.getCount() > 0) {
                             foundResult = true;
@@ -5826,11 +5843,11 @@
 
         Cursor cursor =
                 query(mActiveDb.get(), qb, projection, selection, selectionArgs, sortOrder, groupBy,
-                        limit);
+                limit, cancelationSignal);
 
         if (readBooleanQueryParameter(uri, ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, false)) {
             cursor = bundleLetterCountExtras(cursor, mActiveDb.get(), qb, selection,
-                    selectionArgs, sortOrder, addressBookIndexerCountExpression);
+                    selectionArgs, sortOrder, addressBookIndexerCountExpression, cancelationSignal);
         }
         if (snippetDeferred) {
             cursor = addDeferredSnippetingExtra(cursor);
@@ -5841,13 +5858,13 @@
 
     private Cursor query(final SQLiteDatabase db, SQLiteQueryBuilder qb, String[] projection,
             String selection, String[] selectionArgs, String sortOrder, String groupBy,
-            String limit) {
+            String limit, CancelationSignal cancelationSignal) {
         if (projection != null && projection.length == 1
                 && BaseColumns._COUNT.equals(projection[0])) {
             qb.setProjectionMap(sCountProjectionMap);
         }
         final Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null,
-                sortOrder, limit);
+                sortOrder, limit, cancelationSignal);
         if (c != null) {
             c.setNotificationUri(getContext().getContentResolver(), ContactsContract.AUTHORITY_URI);
         }
@@ -5874,12 +5891,14 @@
      * Runs the query with the supplied contact ID and lookup ID.  If the query succeeds,
      * it returns the resulting cursor, otherwise it returns null and the calling
      * method needs to resolve the lookup key and rerun the query.
+     * @param cancelationSignal
      */
     private Cursor queryWithContactIdAndLookupKey(SQLiteQueryBuilder lookupQb,
             SQLiteDatabase db, Uri uri,
             String[] projection, String selection, String[] selectionArgs,
             String sortOrder, String groupBy, String limit,
-            String contactIdColumn, long contactId, String lookupKeyColumn, String lookupKey) {
+            String contactIdColumn, long contactId, String lookupKeyColumn, String lookupKey,
+            CancelationSignal cancelationSignal) {
         String[] args;
         if (selectionArgs == null) {
             args = new String[2];
@@ -5891,7 +5910,7 @@
         args[1] = Uri.encode(lookupKey);
         lookupQb.appendWhere(contactIdColumn + "=? AND " + lookupKeyColumn + "=?");
         Cursor c = query(db, lookupQb, projection, selection, args, sortOrder,
-                groupBy, limit);
+                groupBy, limit, cancelationSignal);
         if (c.getCount() != 0) {
             return c;
         }
@@ -5925,7 +5944,7 @@
      */
     private Cursor bundleLetterCountExtras(Cursor cursor, final SQLiteDatabase db,
             SQLiteQueryBuilder qb, String selection, String[] selectionArgs, String sortOrder,
-            String countExpression) {
+            String countExpression, CancelationSignal cancelationSignal) {
         if (!(cursor instanceof AbstractCursor)) {
             Log.w(TAG, "Unable to bundle extras.  Cursor is not AbstractCursor.");
             return cursor;
@@ -5976,7 +5995,8 @@
 
         Cursor indexCursor = qb.query(db, AddressBookIndexQuery.COLUMNS, selection, selectionArgs,
                 AddressBookIndexQuery.ORDER_BY, null /* having */,
-                AddressBookIndexQuery.ORDER_BY + sortOrderSuffix);
+                AddressBookIndexQuery.ORDER_BY + sortOrderSuffix,
+                null, cancelationSignal);
 
         try {
             int groupCount = indexCursor.getCount();
@@ -6340,7 +6360,7 @@
      * contact and joins that with other contacts tables.
      */
     private void setTablesAndProjectionMapForContactsWithSnippet(SQLiteQueryBuilder qb, Uri uri,
-            String[] projection, String filter, long directoryId, boolean deferredSnippeting) {
+            String[] projection, String filter, long directoryId, boolean deferSnippeting) {
 
         StringBuilder sb = new StringBuilder();
         sb.append(Views.CONTACTS);
@@ -6352,7 +6372,7 @@
         if (TextUtils.isEmpty(filter) || (directoryId != -1 && directoryId != Directory.DEFAULT)) {
             sb.append(" JOIN (SELECT NULL AS " + SearchSnippetColumns.SNIPPET + " WHERE 0)");
         } else {
-            appendSearchIndexJoin(sb, uri, projection, filter, deferredSnippeting);
+            appendSearchIndexJoin(sb, uri, projection, filter, deferSnippeting);
         }
         appendContactPresenceJoin(sb, projection, Contacts._ID);
         appendContactStatusUpdateJoin(sb, projection, ContactsColumns.LAST_STATUS_UPDATE_ID);
@@ -6362,7 +6382,7 @@
 
     private void appendSearchIndexJoin(
             StringBuilder sb, Uri uri, String[] projection, String filter,
-            boolean  deferredSnippeting) {
+            boolean  deferSnippeting) {
 
         if (snippetNeeded(projection)) {
             String[] args = null;
@@ -6383,7 +6403,7 @@
 
             appendSearchIndexJoin(
                     sb, filter, true, startMatch, endMatch, ellipsis, maxTokens,
-                    deferredSnippeting);
+                    deferSnippeting);
         } else {
             appendSearchIndexJoin(sb, filter, false, null, null, null, 0, false);
         }
@@ -6391,16 +6411,13 @@
 
     public void appendSearchIndexJoin(StringBuilder sb, String filter,
             boolean snippetNeeded, String startMatch, String endMatch, String ellipsis,
-            int maxTokens, boolean deferredSnippeting) {
+            int maxTokens, boolean deferSnippeting) {
         boolean isEmailAddress = false;
         String emailAddress = null;
         boolean isPhoneNumber = false;
         String phoneNumber = null;
         String numberE164 = null;
 
-        // If the query consists of a single word, we can do snippetizing after-the-fact for a
-        // performance boost.
-        boolean singleTokenSearch = isSingleWordQuery(filter);
 
         if (filter.indexOf('@') != -1) {
             emailAddress = mDbHelper.get().extractAddressFromEmailAddress(filter);
@@ -6420,18 +6437,24 @@
             sb.append(", ");
             if (isEmailAddress) {
                 sb.append("ifnull(");
-                DatabaseUtils.appendEscapedSQLString(sb, startMatch);
-                sb.append("||(SELECT MIN(" + Email.ADDRESS + ")");
+                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(")||");
-                DatabaseUtils.appendEscapedSQLString(sb, endMatch);
+                sb.append(")");
+                if (!deferSnippeting) {
+                    sb.append("||");
+                    DatabaseUtils.appendEscapedSQLString(sb, endMatch);
+                }
                 sb.append(",");
 
-                // Optimization for single-token search (do only if requested).
-                if (singleTokenSearch && deferredSnippeting) {
+                if (deferSnippeting) {
                     sb.append(SearchIndexColumns.CONTENT);
                 } else {
                     appendSnippetFunction(sb, startMatch, endMatch, ellipsis, maxTokens);
@@ -6439,8 +6462,12 @@
                 sb.append(")");
             } else if (isPhoneNumber) {
                 sb.append("ifnull(");
-                DatabaseUtils.appendEscapedSQLString(sb, startMatch);
-                sb.append("||(SELECT MIN(" + Phone.NUMBER + ")");
+                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);
@@ -6455,12 +6482,14 @@
                     sb.append(numberE164);
                     sb.append("%'");
                 }
-                sb.append(")||");
-                DatabaseUtils.appendEscapedSQLString(sb, endMatch);
+                sb.append(")");
+                if (! deferSnippeting) {
+                    sb.append("||");
+                    DatabaseUtils.appendEscapedSQLString(sb, endMatch);
+                }
                 sb.append(",");
 
-                // Optimization for single-token search (do only if requested).
-                if (singleTokenSearch && deferredSnippeting) {
+                if (deferSnippeting) {
                     sb.append(SearchIndexColumns.CONTENT);
                 } else {
                     appendSnippetFunction(sb, startMatch, endMatch, ellipsis, maxTokens);
@@ -6469,8 +6498,7 @@
             } else {
                 final String normalizedFilter = NameNormalizer.normalize(filter);
                 if (!TextUtils.isEmpty(normalizedFilter)) {
-                    // Optimization for single-token search (do only if requested)..
-                    if (singleTokenSearch && deferredSnippeting) {
+                    if (deferSnippeting) {
                         sb.append(SearchIndexColumns.CONTENT);
                     } else {
                         sb.append("(CASE WHEN EXISTS (SELECT 1 FROM ");
@@ -6997,7 +7025,7 @@
                     setTablesAndProjectionMapForContacts(lookupQb, uri, projection);
                     Cursor c = queryWithContactIdAndLookupKey(lookupQb, mActiveDb.get(), uri,
                             projection, null, null, null, null, null,
-                            Contacts._ID, contactId, Contacts.LOOKUP_KEY, lookupKey);
+                            Contacts._ID, contactId, Contacts.LOOKUP_KEY, lookupKey, null);
                     if (c != null) {
                         try {
                             c.moveToFirst();
diff --git a/src/com/android/providers/contacts/Hex.java b/src/com/android/providers/contacts/Hex.java
index 991f095..090764a 100644
--- a/src/com/android/providers/contacts/Hex.java
+++ b/src/com/android/providers/contacts/Hex.java
@@ -61,7 +61,7 @@
         int j = 0;
         for (int i = 0; i < array.length; i++) {
             int index = array[i] & 0xFF;
-            if (index == 0 && zeroTerminated) {
+            if (zeroTerminated && index == 0 && i == array.length-1) {
                 break;
             }
 
diff --git a/src/com/android/providers/contacts/ProfileProvider.java b/src/com/android/providers/contacts/ProfileProvider.java
index d40f1fd..ca77620 100644
--- a/src/com/android/providers/contacts/ProfileProvider.java
+++ b/src/com/android/providers/contacts/ProfileProvider.java
@@ -15,6 +15,7 @@
  */
 package com.android.providers.contacts;
 
+import android.content.CancelationSignal;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
@@ -73,9 +74,16 @@
     @Override
     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
             String sortOrder) {
+        return query(uri, projection, selection, selectionArgs, sortOrder, null);
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder, CancelationSignal cancelationSignal) {
         enforceReadPermission(uri);
         mDelegate.substituteDb(getDatabaseHelper().getReadableDatabase());
-        return mDelegate.queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1);
+        return mDelegate.queryLocal(uri, projection, selection, selectionArgs, sortOrder, -1,
+                cancelationSignal);
     }
 
     @Override
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index c008003..b3b3c28 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -1073,9 +1073,7 @@
 
     private boolean equalsWithExpectedValues(Cursor cursor, ContentValues expectedValues,
             StringBuilder msgBuffer) {
-        Set<Map.Entry<String, Object>> entries = expectedValues.valueSet();
-        for (Map.Entry<String, Object> entry : entries) {
-            String column = entry.getKey();
+        for (String column : expectedValues.keySet()) {
             int index = cursor.getColumnIndex(column);
             if (index == -1) {
                 msgBuffer.append("No such column: ").append(column);
diff --git a/tests/src/com/android/providers/contacts/SearchIndexManagerTest.java b/tests/src/com/android/providers/contacts/SearchIndexManagerTest.java
index ab20396..ed1c23a 100644
--- a/tests/src/com/android/providers/contacts/SearchIndexManagerTest.java
+++ b/tests/src/com/android/providers/contacts/SearchIndexManagerTest.java
@@ -438,6 +438,35 @@
                 "...doe.com\nthe eighteenth episode of Seinfeld, [650]-[253]-0000");
     }
 
+    /**
+     * Test case for bug 5904515
+     */
+    public void testSearchByPhoneNumber_diferSnippetting() {
+        long rawContactId = createRawContact();
+        insertPhoneNumber(rawContactId, "505-123-4567");
+
+        // The bug happened with the old code only when we use \u0001 as the snippet marker.
+        // But note that the expected result has [ and ] instead of \u0001.  This is because when
+        // we differ snippetizing, the marker passe to the provider will be ignored; instead
+        // assertStoredValue internally do the client-side snippetizing, which done by
+        // getCursorStringValue(), which is hardcoded to use [ and ].
+        assertStoredValue(buildSearchUri("505", "\u0001,\u0001,\u2026,5", true),
+                SearchSnippetColumns.SNIPPET, "[505]-123-4567");
+    }
+
+    /**
+     * Equivalent to {@link #testSearchByPhoneNumber_diferSnippetting} for email addresses, although
+     * the original bug didn't happen with email addresses... (It *did* happen internally, but
+     * there's no visible breakage.)
+     */
+    public void testSearchByEmail_diferSnippetting() {
+        long rawContactId = createRawContact();
+        insertEmail(rawContactId, "john@doe.com");
+
+        assertStoredValue(buildSearchUri("john", "\u0001,\u0001,\u2026,5", true),
+                SearchSnippetColumns.SNIPPET, "[john@doe.com]");
+    }
+
     private Uri buildSearchUri(String filter) {
         return buildSearchUri(filter, false);
     }