Breaking global search support into a separate class and limiting search results to visible contacts only.
diff --git a/src/com/android/providers/contacts/ContactsProvider2.java b/src/com/android/providers/contacts/ContactsProvider2.java
index 48cdac8..d8af3da 100644
--- a/src/com/android/providers/contacts/ContactsProvider2.java
+++ b/src/com/android/providers/contacts/ContactsProvider2.java
@@ -17,7 +17,6 @@
 package com.android.providers.contacts;
 
 import com.android.internal.content.SyncStateContentProviderHelper;
-import com.android.internal.database.ArrayListCursor;
 import com.android.providers.contacts.OpenHelper.AggregationExceptionColumns;
 import com.android.providers.contacts.OpenHelper.Clauses;
 import com.android.providers.contacts.OpenHelper.ContactsColumns;
@@ -44,7 +43,6 @@
 import android.content.OperationApplicationException;
 import android.content.UriMatcher;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteCursor;
@@ -56,7 +54,6 @@
 import android.os.RemoteException;
 import android.provider.BaseColumns;
 import android.provider.ContactsContract;
-import android.provider.Contacts.Intents;
 import android.provider.ContactsContract.AggregationExceptions;
 import android.provider.ContactsContract.CommonDataKinds;
 import android.provider.ContactsContract.Contacts;
@@ -71,7 +68,6 @@
 import android.provider.ContactsContract.CommonDataKinds.Nickname;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.Contacts.AggregationSuggestions;
@@ -80,8 +76,6 @@
 import android.util.Log;
 
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 
 /**
@@ -944,6 +938,7 @@
     private ContactAggregator mContactAggregator;
     private NameSplitter mNameSplitter;
     private LegacyApiSupport mLegacyApiSupport;
+    private GlobalSearchSupport mGlobalSearchSupport;
 
     private ContentValues mValues = new ContentValues();
 
@@ -963,7 +958,8 @@
         final Context context = getContext();
 
         mOpenHelper = getOpenHelper(context);
-        mLegacyApiSupport = new LegacyApiSupport(context, mOpenHelper, this);
+        mGlobalSearchSupport = new GlobalSearchSupport(this);
+        mLegacyApiSupport = new LegacyApiSupport(context, mOpenHelper, this, mGlobalSearchSupport);
         mContactAggregator = new ContactAggregator(context, mOpenHelper, mAggregationScheduler);
 
         final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
@@ -2276,7 +2272,7 @@
             }
 
             case SEARCH_SUGGESTIONS: {
-                return handleSearchSuggestionsQuery(uri, limit);
+                return mGlobalSearchSupport.handleSearchSuggestionsQuery(db, uri, limit);
             }
 
             case SEARCH_SHORTCUT: {
@@ -2323,279 +2319,6 @@
         }
     }
 
-
-    public Cursor handleSearchSuggestionsQuery(Uri url, String limit) {
-        if (url.getPathSegments().size() <= 1) {
-            return null;
-        }
-
-        final String searchClause = url.getLastPathSegment();
-        if (TextUtils.isDigitsOnly(searchClause)) {
-            return buildCursorForSearchSuggestionsBasedOnPhoneNumber(searchClause);
-        } else {
-            return buildCursorForSearchSuggestionsBasedOnName(searchClause, limit);
-        }
-    }
-
-    private static final String[] SEARCH_SUGGESTIONS_BASED_ON_PHONE_NUMBER_COLUMNS = {
-            "_id",
-            SearchManager.SUGGEST_COLUMN_TEXT_1,
-            SearchManager.SUGGEST_COLUMN_TEXT_2,
-            SearchManager.SUGGEST_COLUMN_ICON_1,
-            SearchManager.SUGGEST_COLUMN_INTENT_DATA,
-            SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
-            SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
-    };
-
-    private Cursor buildCursorForSearchSuggestionsBasedOnPhoneNumber(String searchClause) {
-        Resources r = getContext().getResources();
-        String s;
-        int i;
-
-        ArrayList<Object> dialNumber = new ArrayList<Object>();
-        dialNumber.add(0);  // _id
-        s = r.getString(com.android.internal.R.string.dial_number_using, searchClause);
-        i = s.indexOf('\n');
-        if (i < 0) {
-            dialNumber.add(s);
-            dialNumber.add("");
-        } else {
-            dialNumber.add(s.substring(0, i));
-            dialNumber.add(s.substring(i + 1));
-        }
-        dialNumber.add(String.valueOf(com.android.internal.R.drawable.call_contact));
-        dialNumber.add("tel:" + searchClause);
-        dialNumber.add(Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED);
-        dialNumber.add(null);
-
-        ArrayList<Object> createContact = new ArrayList<Object>();
-        createContact.add(1);  // _id
-        s = r.getString(com.android.internal.R.string.create_contact_using, searchClause);
-        i = s.indexOf('\n');
-        if (i < 0) {
-            createContact.add(s);
-            createContact.add("");
-        } else {
-            createContact.add(s.substring(0, i));
-            createContact.add(s.substring(i + 1));
-        }
-        createContact.add(String.valueOf(com.android.internal.R.drawable.create_contact));
-        createContact.add("tel:" + searchClause);
-        createContact.add(Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED);
-        createContact.add(SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT);
-
-        @SuppressWarnings({"unchecked"}) ArrayList<ArrayList> rows = new ArrayList<ArrayList>();
-        rows.add(dialNumber);
-        rows.add(createContact);
-
-        return new ArrayListCursor(SEARCH_SUGGESTIONS_BASED_ON_PHONE_NUMBER_COLUMNS, rows);
-    }
-
-    private interface SearchSuggestionQuery {
-        public static final String JOIN_RAW_CONTACTS =
-                " JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) ";
-
-        public static final String JOIN_CONTACTS =
-                " JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
-
-        public static final String JOIN_MIMETYPES =
-                " JOIN mimetypes ON (data.mimetype_id = mimetypes._id AND mimetypes.mimetype IN ('"
-                + StructuredName.CONTENT_ITEM_TYPE + "','" + Email.CONTENT_ITEM_TYPE + "','"
-                + Phone.CONTENT_ITEM_TYPE + "','" + Organization.CONTENT_ITEM_TYPE + "','"
-                + Photo.CONTENT_ITEM_TYPE + "','" + GroupMembership.CONTENT_ITEM_TYPE + "')) ";
-
-        // TODO join with groups and ensure that suggestions are from the My Contacts group
-        public static final String JOIN_GROUPS = " JOIN groups ON (mimetypes.mimetype='"
-                + GroupMembership.CONTENT_ITEM_TYPE + "' " + " AND groups._id = data."
-                + GroupMembership.GROUP_ROW_ID + ") ";
-
-        public static final String TABLE = "data " + JOIN_RAW_CONTACTS + JOIN_MIMETYPES
-                + JOIN_CONTACTS;
-
-        public static final String PRESENCE_SQL = "(SELECT MAX(" + Presence.PRESENCE_STATUS
-                + ") FROM " + Tables.PRESENCE + " WHERE " + Tables.PRESENCE + "."
-                + Presence.RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")";
-
-        public static final String[] COLUMNS = {
-            ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID,
-            ContactsColumns.CONCRETE_DISPLAY_NAME + " AS " + Contacts.DISPLAY_NAME,
-            PRESENCE_SQL + " AS " + Contacts.PRESENCE_STATUS,
-            DataColumns.CONCRETE_ID + " AS data_id",
-            MimetypesColumns.MIMETYPE,
-            Data.IS_SUPER_PRIMARY,
-            Data.DATA2,
-        };
-
-        public static final int CONTACT_ID = 0;
-        public static final int DISPLAY_NAME = 1;
-        public static final int PRESENCE_STATUS = 2;
-        public static final int DATA_ID = 3;
-        public static final int MIMETYPE = 4;
-        public static final int IS_SUPER_PRIMARY = 5;
-        public static final int DATA2 = 6;
-    }
-
-    private static class SearchSuggestion {
-        String contactId;
-        boolean titleIsName;
-        String organization;
-        String email;
-        String phoneNumber;
-        String photoUri;
-        String normalizedName;
-        int presence = -1;
-        boolean processed;
-        String text1;
-        String text2;
-        String icon1;
-        String icon2;
-
-        public SearchSuggestion(long contactId) {
-            this.contactId = String.valueOf(contactId);
-        }
-
-        private void process() {
-            if (processed) {
-                return;
-            }
-
-            boolean hasOrganization = !TextUtils.isEmpty(organization);
-            boolean hasEmail = !TextUtils.isEmpty(email);
-            boolean hasPhone = !TextUtils.isEmpty(phoneNumber);
-
-            boolean titleIsOrganization = !titleIsName && hasOrganization;
-            boolean titleIsEmail = !titleIsName && !titleIsOrganization && hasEmail;
-            boolean titleIsPhone = !titleIsName && !titleIsOrganization && !titleIsEmail
-                    && hasPhone;
-
-            if (!titleIsOrganization && hasOrganization) {
-                text2 = organization;
-            } else if (!titleIsEmail && hasEmail) {
-                text2 = email;
-            } else if (!titleIsPhone && hasPhone) {
-                text2 = phoneNumber;
-            }
-
-            if (photoUri != null) {
-                icon1 = photoUri;
-            } else {
-                icon1 = String.valueOf(com.android.internal.R.drawable.ic_contact_picture);
-            }
-
-            if (presence != -1) {
-                icon2 = String.valueOf(Presence.getPresenceIconResourceId(presence));
-            }
-
-            processed = true;
-        }
-
-        public String getSortKey() {
-            if (normalizedName == null) {
-                process();
-                normalizedName = text1 == null ? "" : NameNormalizer.normalize(text1);
-            }
-            return normalizedName;
-        }
-
-        @SuppressWarnings({"unchecked"})
-        public ArrayList asList() {
-            process();
-
-            ArrayList<Object> list = new ArrayList<Object>();
-            list.add(contactId);
-            list.add(text1);
-            list.add(text2);
-            list.add(icon1);
-            list.add(icon2);
-            list.add(contactId);
-            list.add(contactId);
-            return list;
-        }
-    }
-
-    private static final String[] SEARCH_SUGGESTIONS_BASED_ON_NAME_COLUMNS = {
-            "_id",
-            SearchManager.SUGGEST_COLUMN_TEXT_1,
-            SearchManager.SUGGEST_COLUMN_TEXT_2,
-            SearchManager.SUGGEST_COLUMN_ICON_1,
-            SearchManager.SUGGEST_COLUMN_ICON_2,
-            SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID,
-            SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
-    };
-
-    private Cursor buildCursorForSearchSuggestionsBasedOnName(String searchClause, String limit) {
-        ArrayList<SearchSuggestion> suggestionList = new ArrayList<SearchSuggestion>();
-        HashMap<Long, SearchSuggestion> suggestionMap = new HashMap<Long, SearchSuggestion>();
-
-        StringBuilder selection = new StringBuilder();
-        selection.append(getContactsRestrictionExceptions());
-        selection.append(" AND " + DataColumns.CONCRETE_RAW_CONTACT_ID + " IN ");
-        appendRawContactsByFilterAsNestedQuery(selection, searchClause, limit);
-
-        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-        Cursor c = db.query(true, SearchSuggestionQuery.TABLE,
-                SearchSuggestionQuery.COLUMNS, selection.toString(), null, null, null, null, null);
-        try {
-            while (c.moveToNext()) {
-
-                long contactId = c.getLong(SearchSuggestionQuery.CONTACT_ID);
-                SearchSuggestion suggestion = suggestionMap.get(contactId);
-                if (suggestion == null) {
-                    suggestion = new SearchSuggestion(contactId);
-                    suggestionList.add(suggestion);
-                    suggestionMap.put(contactId, suggestion);
-                }
-
-                boolean isSuperPrimary = c.getInt(SearchSuggestionQuery.IS_SUPER_PRIMARY) != 0;
-                suggestion.text1 = c.getString(SearchSuggestionQuery.DISPLAY_NAME);
-
-                if (!c.isNull(SearchSuggestionQuery.PRESENCE_STATUS)) {
-                    suggestion.presence = c.getInt(SearchSuggestionQuery.PRESENCE_STATUS);
-                }
-
-                String mimetype = c.getString(SearchSuggestionQuery.MIMETYPE);
-                if (StructuredName.CONTENT_ITEM_TYPE.equals(mimetype)) {
-                    suggestion.titleIsName = true;
-                } else if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
-                    if (isSuperPrimary || suggestion.photoUri == null) {
-
-                        // TODO introduce a dedicate URI for contact photo: /contact/#/photo
-                        long dataId = c.getLong(SearchSuggestionQuery.DATA_ID);
-                        suggestion.photoUri =
-                                ContentUris.withAppendedId(Data.CONTENT_URI, dataId).toString();
-                    }
-                } else if (Organization.CONTENT_ITEM_TYPE.equals(mimetype)) {
-                    if (isSuperPrimary || suggestion.organization == null) {
-                        suggestion.organization = c.getString(SearchSuggestionQuery.DATA2);
-                    }
-                } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) {
-                    if (isSuperPrimary || suggestion.email == null) {
-                        suggestion.email = c.getString(SearchSuggestionQuery.DATA2);
-                    }
-                } else if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
-                    if (isSuperPrimary || suggestion.phoneNumber == null) {
-                        suggestion.phoneNumber = c.getString(SearchSuggestionQuery.DATA2);
-                    }
-                }
-            }
-        } finally {
-            c.close();
-        }
-
-        Collections.sort(suggestionList, new Comparator<SearchSuggestion>() {
-            public int compare(SearchSuggestion row1, SearchSuggestion row2) {
-                return row1.getSortKey().compareTo(row2.getSortKey());
-            }
-        });
-
-        @SuppressWarnings({"unchecked"}) ArrayList<ArrayList> rows = new ArrayList<ArrayList>();
-        for (int i = 0; i < suggestionList.size(); i++) {
-            rows.add(suggestionList.get(i).asList());
-        }
-
-        return new ArrayListCursor(SEARCH_SUGGESTIONS_BASED_ON_NAME_COLUMNS, rows);
-    }
-
     /**
      * List of package names with access to {@link RawContacts#IS_RESTRICTED} data.
      */
@@ -2674,7 +2397,7 @@
         qb.appendWhere(getContactsRestrictionExceptions());
     }
 
-    private String getContactsRestrictionExceptions() {
+    String getContactsRestrictionExceptions() {
         if (hasRestrictedAccess()) {
             return "1";
         } else {
@@ -3157,7 +2880,7 @@
         return sb.toString();
     }
 
-    private void appendRawContactsByFilterAsNestedQuery(StringBuilder sb, String filterParam,
+    public void appendRawContactsByFilterAsNestedQuery(StringBuilder sb, String filterParam,
             String limit) {
         sb.append("(SELECT DISTINCT raw_contact_id FROM name_lookup WHERE normalized_name GLOB '");
         sb.append(NameNormalizer.normalize(filterParam));
diff --git a/src/com/android/providers/contacts/GlobalSearchSupport.java b/src/com/android/providers/contacts/GlobalSearchSupport.java
new file mode 100644
index 0000000..40e1089
--- /dev/null
+++ b/src/com/android/providers/contacts/GlobalSearchSupport.java
@@ -0,0 +1,327 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.providers.contacts;
+
+import com.android.internal.database.ArrayListCursor;
+import com.android.providers.contacts.OpenHelper.ContactsColumns;
+import com.android.providers.contacts.OpenHelper.DataColumns;
+import com.android.providers.contacts.OpenHelper.MimetypesColumns;
+import com.android.providers.contacts.OpenHelper.RawContactsColumns;
+import com.android.providers.contacts.OpenHelper.Tables;
+
+import android.app.SearchManager;
+import android.content.ContentUris;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.provider.Contacts.Intents;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Presence;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+
+/**
+ * Support for global search integration for Contacts.
+ */
+public class GlobalSearchSupport {
+
+    private static final String[] SEARCH_SUGGESTIONS_BASED_ON_PHONE_NUMBER_COLUMNS = {
+            "_id",
+            SearchManager.SUGGEST_COLUMN_TEXT_1,
+            SearchManager.SUGGEST_COLUMN_TEXT_2,
+            SearchManager.SUGGEST_COLUMN_ICON_1,
+            SearchManager.SUGGEST_COLUMN_INTENT_DATA,
+            SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
+            SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
+    };
+
+    private static final String[] SEARCH_SUGGESTIONS_BASED_ON_NAME_COLUMNS = {
+            "_id",
+            SearchManager.SUGGEST_COLUMN_TEXT_1,
+            SearchManager.SUGGEST_COLUMN_TEXT_2,
+            SearchManager.SUGGEST_COLUMN_ICON_1,
+            SearchManager.SUGGEST_COLUMN_ICON_2,
+            SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID,
+            SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
+    };
+
+    private interface SearchSuggestionQuery {
+        public static final String JOIN_RAW_CONTACTS =
+                " JOIN raw_contacts ON (data.raw_contact_id = raw_contacts._id) ";
+
+        public static final String JOIN_CONTACTS =
+                " JOIN contacts ON (raw_contacts.contact_id = contacts._id)";
+
+        public static final String JOIN_MIMETYPES =
+                " JOIN mimetypes ON (data.mimetype_id = mimetypes._id AND mimetypes.mimetype IN ('"
+                + StructuredName.CONTENT_ITEM_TYPE + "','" + Email.CONTENT_ITEM_TYPE + "','"
+                + Phone.CONTENT_ITEM_TYPE + "','" + Organization.CONTENT_ITEM_TYPE + "','"
+                + Photo.CONTENT_ITEM_TYPE + "','" + GroupMembership.CONTENT_ITEM_TYPE + "')) ";
+
+        public static final String TABLE = "data " + JOIN_RAW_CONTACTS + JOIN_MIMETYPES
+                + JOIN_CONTACTS;
+
+        public static final String PRESENCE_SQL = "(SELECT MAX(" + Presence.PRESENCE_STATUS
+                + ") FROM " + Tables.PRESENCE + " WHERE " + Tables.PRESENCE + "."
+                + Presence.RAW_CONTACT_ID + "=" + RawContactsColumns.CONCRETE_ID + ")";
+
+        public static final String[] COLUMNS = {
+            ContactsColumns.CONCRETE_ID + " AS " + Contacts._ID,
+            ContactsColumns.CONCRETE_DISPLAY_NAME + " AS " + Contacts.DISPLAY_NAME,
+            PRESENCE_SQL + " AS " + Contacts.PRESENCE_STATUS,
+            DataColumns.CONCRETE_ID + " AS data_id",
+            MimetypesColumns.MIMETYPE,
+            Data.IS_SUPER_PRIMARY,
+            Data.DATA2,
+        };
+
+        public static final int CONTACT_ID = 0;
+        public static final int DISPLAY_NAME = 1;
+        public static final int PRESENCE_STATUS = 2;
+        public static final int DATA_ID = 3;
+        public static final int MIMETYPE = 4;
+        public static final int IS_SUPER_PRIMARY = 5;
+        public static final int DATA2 = 6;
+    }
+
+    private static class SearchSuggestion {
+        String contactId;
+        boolean titleIsName;
+        String organization;
+        String email;
+        String phoneNumber;
+        String photoUri;
+        String normalizedName;
+        int presence = -1;
+        boolean processed;
+        String text1;
+        String text2;
+        String icon1;
+        String icon2;
+
+        public SearchSuggestion(long contactId) {
+            this.contactId = String.valueOf(contactId);
+        }
+
+        private void process() {
+            if (processed) {
+                return;
+            }
+
+            boolean hasOrganization = !TextUtils.isEmpty(organization);
+            boolean hasEmail = !TextUtils.isEmpty(email);
+            boolean hasPhone = !TextUtils.isEmpty(phoneNumber);
+
+            boolean titleIsOrganization = !titleIsName && hasOrganization;
+            boolean titleIsEmail = !titleIsName && !titleIsOrganization && hasEmail;
+            boolean titleIsPhone = !titleIsName && !titleIsOrganization && !titleIsEmail
+                    && hasPhone;
+
+            if (!titleIsOrganization && hasOrganization) {
+                text2 = organization;
+            } else if (!titleIsEmail && hasEmail) {
+                text2 = email;
+            } else if (!titleIsPhone && hasPhone) {
+                text2 = phoneNumber;
+            }
+
+            if (photoUri != null) {
+                icon1 = photoUri;
+            } else {
+                icon1 = String.valueOf(com.android.internal.R.drawable.ic_contact_picture);
+            }
+
+            if (presence != -1) {
+                icon2 = String.valueOf(Presence.getPresenceIconResourceId(presence));
+            }
+
+            processed = true;
+        }
+
+        public String getSortKey() {
+            if (normalizedName == null) {
+                process();
+                normalizedName = text1 == null ? "" : NameNormalizer.normalize(text1);
+            }
+            return normalizedName;
+        }
+
+        @SuppressWarnings({"unchecked"})
+        public ArrayList asList() {
+            process();
+
+            ArrayList<Object> list = new ArrayList<Object>();
+            list.add(contactId);
+            list.add(text1);
+            list.add(text2);
+            list.add(icon1);
+            list.add(icon2);
+            list.add(contactId);
+            list.add(contactId);
+            return list;
+        }
+    }
+
+    private final ContactsProvider2 mContactsProvider;
+
+    public GlobalSearchSupport(ContactsProvider2 contactsProvider) {
+        mContactsProvider = contactsProvider;
+    }
+
+    public Cursor handleSearchSuggestionsQuery(SQLiteDatabase db, Uri url, String limit) {
+        if (url.getPathSegments().size() <= 1) {
+            return null;
+        }
+
+        final String searchClause = url.getLastPathSegment();
+        if (TextUtils.isDigitsOnly(searchClause)) {
+            return buildCursorForSearchSuggestionsBasedOnPhoneNumber(searchClause);
+        } else {
+            return buildCursorForSearchSuggestionsBasedOnName(db, searchClause, limit);
+        }
+    }
+
+    private Cursor buildCursorForSearchSuggestionsBasedOnPhoneNumber(String searchClause) {
+        Resources r = mContactsProvider.getContext().getResources();
+        String s;
+        int i;
+
+        ArrayList<Object> dialNumber = new ArrayList<Object>();
+        dialNumber.add(0);  // _id
+        s = r.getString(com.android.internal.R.string.dial_number_using, searchClause);
+        i = s.indexOf('\n');
+        if (i < 0) {
+            dialNumber.add(s);
+            dialNumber.add("");
+        } else {
+            dialNumber.add(s.substring(0, i));
+            dialNumber.add(s.substring(i + 1));
+        }
+        dialNumber.add(String.valueOf(com.android.internal.R.drawable.call_contact));
+        dialNumber.add("tel:" + searchClause);
+        dialNumber.add(Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED);
+        dialNumber.add(null);
+
+        ArrayList<Object> createContact = new ArrayList<Object>();
+        createContact.add(1);  // _id
+        s = r.getString(com.android.internal.R.string.create_contact_using, searchClause);
+        i = s.indexOf('\n');
+        if (i < 0) {
+            createContact.add(s);
+            createContact.add("");
+        } else {
+            createContact.add(s.substring(0, i));
+            createContact.add(s.substring(i + 1));
+        }
+        createContact.add(String.valueOf(com.android.internal.R.drawable.create_contact));
+        createContact.add("tel:" + searchClause);
+        createContact.add(Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED);
+        createContact.add(SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT);
+
+        @SuppressWarnings({"unchecked"}) ArrayList<ArrayList> rows = new ArrayList<ArrayList>();
+        rows.add(dialNumber);
+        rows.add(createContact);
+
+        return new ArrayListCursor(SEARCH_SUGGESTIONS_BASED_ON_PHONE_NUMBER_COLUMNS, rows);
+    }
+
+    private Cursor buildCursorForSearchSuggestionsBasedOnName(SQLiteDatabase db,
+            String searchClause, String limit) {
+        ArrayList<SearchSuggestion> suggestionList = new ArrayList<SearchSuggestion>();
+        HashMap<Long, SearchSuggestion> suggestionMap = new HashMap<Long, SearchSuggestion>();
+
+        StringBuilder selection = new StringBuilder();
+        selection.append(mContactsProvider.getContactsRestrictionExceptions());
+        selection.append(" AND " + DataColumns.CONCRETE_RAW_CONTACT_ID + " IN ");
+        mContactsProvider.appendRawContactsByFilterAsNestedQuery(selection, searchClause, limit);
+        selection.append(" AND " + Contacts.IN_VISIBLE_GROUP + "=1");
+
+        Cursor c = db.query(true, SearchSuggestionQuery.TABLE,
+                SearchSuggestionQuery.COLUMNS, selection.toString(), null, null, null, null, null);
+        try {
+            while (c.moveToNext()) {
+
+                long contactId = c.getLong(SearchSuggestionQuery.CONTACT_ID);
+                SearchSuggestion suggestion = suggestionMap.get(contactId);
+                if (suggestion == null) {
+                    suggestion = new SearchSuggestion(contactId);
+                    suggestionList.add(suggestion);
+                    suggestionMap.put(contactId, suggestion);
+                }
+
+                boolean isSuperPrimary = c.getInt(SearchSuggestionQuery.IS_SUPER_PRIMARY) != 0;
+                suggestion.text1 = c.getString(SearchSuggestionQuery.DISPLAY_NAME);
+
+                if (!c.isNull(SearchSuggestionQuery.PRESENCE_STATUS)) {
+                    suggestion.presence = c.getInt(SearchSuggestionQuery.PRESENCE_STATUS);
+                }
+
+                String mimetype = c.getString(SearchSuggestionQuery.MIMETYPE);
+                if (StructuredName.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                    suggestion.titleIsName = true;
+                } else if (Photo.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                    if (isSuperPrimary || suggestion.photoUri == null) {
+
+                        // TODO introduce a dedicated URI for contact photo: /contact/#/photo
+                        long dataId = c.getLong(SearchSuggestionQuery.DATA_ID);
+                        suggestion.photoUri =
+                                ContentUris.withAppendedId(Data.CONTENT_URI, dataId).toString();
+                    }
+                } else if (Organization.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                    if (isSuperPrimary || suggestion.organization == null) {
+                        suggestion.organization = c.getString(SearchSuggestionQuery.DATA2);
+                    }
+                } else if (Email.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                    if (isSuperPrimary || suggestion.email == null) {
+                        suggestion.email = c.getString(SearchSuggestionQuery.DATA2);
+                    }
+                } else if (Phone.CONTENT_ITEM_TYPE.equals(mimetype)) {
+                    if (isSuperPrimary || suggestion.phoneNumber == null) {
+                        suggestion.phoneNumber = c.getString(SearchSuggestionQuery.DATA2);
+                    }
+                }
+            }
+        } finally {
+            c.close();
+        }
+
+        Collections.sort(suggestionList, new Comparator<SearchSuggestion>() {
+            public int compare(SearchSuggestion row1, SearchSuggestion row2) {
+                return row1.getSortKey().compareTo(row2.getSortKey());
+            }
+        });
+
+        @SuppressWarnings({"unchecked"}) ArrayList<ArrayList> rows = new ArrayList<ArrayList>();
+        for (int i = 0; i < suggestionList.size(); i++) {
+            rows.add(suggestionList.get(i).asList());
+        }
+
+        return new ArrayListCursor(SEARCH_SUGGESTIONS_BASED_ON_NAME_COLUMNS, rows);
+    }
+}
diff --git a/src/com/android/providers/contacts/LegacyApiSupport.java b/src/com/android/providers/contacts/LegacyApiSupport.java
index d04bf63..af4a74e 100644
--- a/src/com/android/providers/contacts/LegacyApiSupport.java
+++ b/src/com/android/providers/contacts/LegacyApiSupport.java
@@ -422,17 +422,20 @@
     private final OpenHelper mOpenHelper;
     private final ContactsProvider2 mContactsProvider;
     private final NameSplitter mPhoneticNameSplitter;
+    private final GlobalSearchSupport mGlobalSearchSupport;
 
     /** Precompiled sql statement for incrementing times contacted for a contact */
     private final SQLiteStatement mLastTimeContactedUpdate;
 
     private final ContentValues mValues = new ContentValues();
 
+
     public LegacyApiSupport(Context context, OpenHelper openHelper,
-            ContactsProvider2 contactsProvider) {
+            ContactsProvider2 contactsProvider, GlobalSearchSupport globalSearchSupport) {
         mContext = context;
         mContactsProvider = contactsProvider;
         mOpenHelper = openHelper;
+        mGlobalSearchSupport = globalSearchSupport;
         mOpenHelper.setDelegate(this);
 
         mPhoneticNameSplitter = new NameSplitter("", "", "",
@@ -1302,7 +1305,7 @@
             case SEARCH_SUGGESTIONS:
 
                 // No legacy compatibility for search suggestions
-                return mContactsProvider.handleSearchSuggestionsQuery(uri, limit);
+                return mGlobalSearchSupport.handleSearchSuggestionsQuery(db, uri, limit);
 
             case DELETED_PEOPLE:
             case DELETED_GROUPS:
diff --git a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
index e4186d7..cc27428 100644
--- a/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/BaseContactsProvider2Test.java
@@ -23,6 +23,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Entity;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.ContactsContract;
@@ -43,6 +44,9 @@
 import android.test.mock.MockContentResolver;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -133,6 +137,7 @@
         ContentValues values = new ContentValues();
         values.put(Groups.SOURCE_ID, sourceId);
         values.put(Groups.TITLE, title);
+        values.put(Groups.GROUP_VISIBLE, 1);
         final Uri uri = maybeAddAccountQueryParameters(Groups.CONTENT_URI, account);
         return ContentUris.parseId(mResolver.insert(uri, values));
     }
@@ -579,4 +584,17 @@
             c.close();
         }
     }
+
+    protected byte[] loadTestPhoto() throws IOException {
+        final Resources resources = getContext().getResources();
+        InputStream is =
+                resources.openRawResource(com.android.internal.R.drawable.ic_contact_picture);
+        ByteArrayOutputStream os = new ByteArrayOutputStream();
+        byte[] buffer = new byte[1000];
+        int count;
+        while((count = is.read(buffer)) != -1) {
+            os.write(buffer, 0, count);
+        }
+        return os.toByteArray();
+    }
 }
diff --git a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
index de9dd14..f6d5bd7 100644
--- a/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
+++ b/tests/src/com/android/providers/contacts/ContactsProvider2Test.java
@@ -17,18 +17,14 @@
 
 import com.android.internal.util.ArrayUtils;
 
-import android.app.SearchManager;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Entity;
 import android.content.EntityIterator;
-import android.content.res.Resources;
 import android.database.Cursor;
-import android.database.DatabaseUtils;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.provider.ContactsContract;
-import android.provider.Contacts.Intents;
 import android.provider.ContactsContract.AggregationExceptions;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Data;
@@ -37,16 +33,10 @@
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
 import android.provider.ContactsContract.CommonDataKinds.Im;
-import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.Photo;
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
 /**
  * Unit tests for {@link ContactsProvider2}.
  *
@@ -477,212 +467,5 @@
         version++;
         assertEquals(version, getVersion(uri));
     }
-
-    // TODO fix and enable test
-    public void __testSearchSuggestionsNotInMyContacts() throws Exception {
-
-        long rawContactId = createRawContact();
-        insertStructuredName(rawContactId, "Deer", "Dough");
-
-        Uri searchUri = new Uri.Builder().scheme("content").authority(ContactsContract.AUTHORITY)
-                .appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath("D").build();
-
-        // If the contact is not in the "my contacts" group, nothing should be found
-        Cursor c = mResolver.query(searchUri, null, null, null, null);
-        assertEquals(0, c.getCount());
-        c.close();
-    }
-
-    public void testSearchSuggestionsByName() throws Exception {
-        assertSearchSuggestion(
-                true,  // name
-                true,  // photo
-                false, // organization
-                false, // phone
-                false, // email
-                "D",   // query
-                true,  // expect icon URI
-                null, "Deer Dough", null);
-
-        assertSearchSuggestion(
-                true,  // name
-                true,  // photo
-                true,  // organization
-                false, // phone
-                false, // email
-                "D",   // query
-                true,  // expect icon URI
-                null, "Deer Dough", "Google");
-
-        assertSearchSuggestion(
-                true,  // name
-                true,  // photo
-                false, // organization
-                true,  // phone
-                false, // email
-                "D",   // query
-                true,  // expect icon URI
-                null, "Deer Dough", "1-800-4664-411");
-
-        assertSearchSuggestion(
-                true,  // name
-                true,  // photo
-                false, // organization
-                false, // phone
-                true,  // email
-                "D",   // query
-                true,  // expect icon URI
-                String.valueOf(Presence.getPresenceIconResourceId(Presence.OFFLINE)),
-                "Deer Dough", "foo@acme.com");
-
-        assertSearchSuggestion(
-                true,  // name
-                false, // photo
-                true,  // organization
-                false, // phone
-                false, // email
-                "D",   // query
-                false, // expect icon URI
-                null, "Deer Dough", "Google");
-    }
-
-    private void assertSearchSuggestion(boolean name, boolean photo, boolean organization,
-            boolean phone, boolean email, String query, boolean expectIcon1Uri, String expectedIcon2,
-            String expectedText1, String expectedText2) throws IOException {
-        ContentValues values = new ContentValues();
-
-        long rawContactId = createRawContact();
-
-        if (name) {
-            insertStructuredName(rawContactId, "Deer", "Dough");
-        }
-
-
-        final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
-        if (photo) {
-            values.clear();
-            byte[] photoData = loadTestPhoto();
-            values.put(Data.RAW_CONTACT_ID, rawContactId);
-            values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
-            values.put(Photo.PHOTO, photoData);
-            mResolver.insert(Data.CONTENT_URI, values);
-        }
-
-        if (organization) {
-            values.clear();
-            values.put(Data.RAW_CONTACT_ID, rawContactId);
-            values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
-            values.put(Organization.TYPE, Organization.TYPE_WORK);
-            values.put(Organization.COMPANY, "Google");
-            mResolver.insert(Data.CONTENT_URI, values);
-        }
-
-        if (email) {
-            values.clear();
-            values.put(Data.RAW_CONTACT_ID, rawContactId);
-            values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
-            values.put(Email.TYPE, Email.TYPE_WORK);
-            values.put(Email.DATA, "foo@acme.com");
-            mResolver.insert(Data.CONTENT_URI, values);
-
-            values.clear();
-            values.put(Presence.IM_PROTOCOL, Im.PROTOCOL_GOOGLE_TALK);
-            values.put(Presence.IM_HANDLE, "foo@acme.com");
-            values.put(Presence.IM_ACCOUNT, "foo");
-            values.put(Presence.PRESENCE_STATUS, Presence.OFFLINE);
-            values.put(Presence.PRESENCE_CUSTOM_STATUS, "Coding for Android");
-            mResolver.insert(Presence.CONTENT_URI, values);
-        }
-
-        if (phone) {
-            values.clear();
-            values.put(Data.RAW_CONTACT_ID, rawContactId);
-            values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
-            values.put(Data.IS_PRIMARY, 1);
-            values.put(Phone.TYPE, Phone.TYPE_HOME);
-            values.put(Phone.NUMBER, "1-800-4664-411");
-            mResolver.insert(Data.CONTENT_URI, values);
-        }
-
-        long contactId = queryContactId(rawContactId);
-        Uri searchUri = new Uri.Builder().scheme("content").authority(ContactsContract.AUTHORITY)
-                .appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath(query).build();
-
-        Cursor c = mResolver.query(searchUri, null, null, null, null);
-        DatabaseUtils.dumpCursor(c);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        values.clear();
-
-        // SearchManager does not declare a constant for _id
-        values.put("_id", contactId);
-        values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, expectedText1);
-        values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, expectedText2);
-
-        String icon1 = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1));
-        if (expectIcon1Uri) {
-            assertTrue(icon1.startsWith("content:"));
-        } else {
-            assertEquals(String.valueOf(com.android.internal.R.drawable.ic_contact_picture), icon1);
-        }
-
-        values.put(SearchManager.SUGGEST_COLUMN_ICON_2, expectedIcon2);
-        values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, contactId);
-        values.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, contactId);
-        assertCursorValues(c, values);
-        c.close();
-
-        // Cleanup
-        mResolver.delete(rawContactUri, null, null);
-    }
-
-    public void testSearchSuggestionsByPhoneNumber() throws Exception {
-        ContentValues values = new ContentValues();
-
-        Uri searchUri = new Uri.Builder().scheme("content").authority(ContactsContract.AUTHORITY)
-                .appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath("12345").build();
-
-        Cursor c = mResolver.query(searchUri, null, null, null, null);
-        DatabaseUtils.dumpCursor(c);
-        assertEquals(2, c.getCount());
-        c.moveToFirst();
-
-        values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, "Dial number");
-        values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, "using 12345");
-        values.put(SearchManager.SUGGEST_COLUMN_ICON_1,
-                String.valueOf(com.android.internal.R.drawable.call_contact));
-        values.put(SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
-                Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED);
-        values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA, "tel:12345");
-        values.putNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
-        assertCursorValues(c, values);
-
-        c.moveToNext();
-        values.clear();
-        values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, "Create contact");
-        values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, "using 12345");
-        values.put(SearchManager.SUGGEST_COLUMN_ICON_1,
-                String.valueOf(com.android.internal.R.drawable.create_contact));
-        values.put(SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
-                Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED);
-        values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA, "tel:12345");
-        values.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
-                SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT);
-        assertCursorValues(c, values);
-        c.close();
-    }
-
-    private byte[] loadTestPhoto() throws IOException {
-        final Resources resources = getContext().getResources();
-        InputStream is =
-                resources.openRawResource(com.android.internal.R.drawable.ic_contact_picture);
-        ByteArrayOutputStream os = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1000];
-        int count;
-        while((count = is.read(buffer)) != -1) {
-            os.write(buffer, 0, count);
-        }
-        return os.toByteArray();
-    }
 }
 
diff --git a/tests/src/com/android/providers/contacts/GlobalSearchSupportTest.java b/tests/src/com/android/providers/contacts/GlobalSearchSupportTest.java
new file mode 100644
index 0000000..8921d41
--- /dev/null
+++ b/tests/src/com/android/providers/contacts/GlobalSearchSupportTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.providers.contacts;
+
+import android.app.SearchManager;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.Contacts.Intents;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.Presence;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Im;
+import android.provider.ContactsContract.CommonDataKinds.Organization;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.Photo;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link GlobalSearchSupport}.
+ *
+ * Run the test like this:
+ * <code>
+ * adb shell am instrument -w \
+ *         com.android.providers.contacts.tests/android.test.InstrumentationTestRunner
+ * </code>
+ */
+@LargeTest
+public class GlobalSearchSupportTest extends BaseContactsProvider2Test {
+    public void testSearchSuggestionsNotInVisibleGroup() throws Exception {
+        long rawContactId = createRawContact();
+        insertStructuredName(rawContactId, "Deer", "Dough");
+
+        Uri searchUri = new Uri.Builder().scheme("content").authority(ContactsContract.AUTHORITY)
+                .appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath("D").build();
+
+        // If the contact is not in the "my contacts" group, nothing should be found
+        Cursor c = mResolver.query(searchUri, null, null, null, null);
+        assertEquals(0, c.getCount());
+        c.close();
+    }
+
+    public void testSearchSuggestionsByName() throws Exception {
+        long groupId = createGroup(mAccount, "gsid1", "title1");
+
+        assertSearchSuggestion(groupId,
+                true,  // name
+                true,  // photo
+                false, // organization
+                false, // phone
+                false, // email
+                "D",   // query
+                true,  // expect icon URI
+                null, "Deer Dough", null);
+
+        assertSearchSuggestion(groupId,
+                true,  // name
+                true,  // photo
+                true,  // organization
+                false, // phone
+                false, // email
+                "D",   // query
+                true,  // expect icon URI
+                null, "Deer Dough", "Google");
+
+        assertSearchSuggestion(groupId,
+                true,  // name
+                true,  // photo
+                false, // organization
+                true,  // phone
+                false, // email
+                "D",   // query
+                true,  // expect icon URI
+                null, "Deer Dough", "1-800-4664-411");
+
+        assertSearchSuggestion(groupId,
+                true,  // name
+                true,  // photo
+                false, // organization
+                false, // phone
+                true,  // email
+                "D",   // query
+                true,  // expect icon URI
+                String.valueOf(Presence.getPresenceIconResourceId(Presence.OFFLINE)),
+                "Deer Dough", "foo@acme.com");
+
+        assertSearchSuggestion(groupId,
+                true,  // name
+                false, // photo
+                true,  // organization
+                false, // phone
+                false, // email
+                "D",   // query
+                false, // expect icon URI
+                null, "Deer Dough", "Google");
+    }
+
+    private void assertSearchSuggestion(long groupId, boolean name, boolean photo,
+            boolean organization, boolean phone, boolean email, String query,
+            boolean expectIcon1Uri, String expectedIcon2, String expectedText1, String expectedText2)
+            throws IOException {
+        ContentValues values = new ContentValues();
+
+        long rawContactId = createRawContact();
+        insertGroupMembership(rawContactId, groupId);
+
+        if (name) {
+            insertStructuredName(rawContactId, "Deer", "Dough");
+        }
+
+        final Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId);
+        if (photo) {
+            values.clear();
+            byte[] photoData = loadTestPhoto();
+            values.put(Data.RAW_CONTACT_ID, rawContactId);
+            values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
+            values.put(Photo.PHOTO, photoData);
+            mResolver.insert(Data.CONTENT_URI, values);
+        }
+
+        if (organization) {
+            values.clear();
+            values.put(Data.RAW_CONTACT_ID, rawContactId);
+            values.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
+            values.put(Organization.TYPE, Organization.TYPE_WORK);
+            values.put(Organization.COMPANY, "Google");
+            mResolver.insert(Data.CONTENT_URI, values);
+        }
+
+        if (email) {
+            values.clear();
+            values.put(Data.RAW_CONTACT_ID, rawContactId);
+            values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+            values.put(Email.TYPE, Email.TYPE_WORK);
+            values.put(Email.DATA, "foo@acme.com");
+            mResolver.insert(Data.CONTENT_URI, values);
+
+            int protocol = Im.PROTOCOL_GOOGLE_TALK;
+
+            values.clear();
+            values.put(Presence.IM_PROTOCOL, protocol);
+            values.put(Presence.IM_HANDLE, "foo@acme.com");
+            values.put(Presence.IM_ACCOUNT, "foo");
+            values.put(Presence.PRESENCE_STATUS, Presence.OFFLINE);
+            values.put(Presence.PRESENCE_CUSTOM_STATUS, "Coding for Android");
+            mResolver.insert(Presence.CONTENT_URI, values);
+        }
+
+        if (phone) {
+            values.clear();
+            values.put(Data.RAW_CONTACT_ID, rawContactId);
+            values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+            values.put(Data.IS_PRIMARY, 1);
+            values.put(Phone.TYPE, Phone.TYPE_HOME);
+            values.put(Phone.NUMBER, "1-800-4664-411");
+            mResolver.insert(Data.CONTENT_URI, values);
+        }
+
+        long contactId = queryContactId(rawContactId);
+        Uri searchUri = new Uri.Builder().scheme("content").authority(ContactsContract.AUTHORITY)
+                .appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath(query).build();
+
+        Cursor c = mResolver.query(searchUri, null, null, null, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        values.clear();
+
+        // SearchManager does not declare a constant for _id
+        values.put("_id", contactId);
+        values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, expectedText1);
+        values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, expectedText2);
+
+        String icon1 = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1));
+        if (expectIcon1Uri) {
+            assertTrue(icon1.startsWith("content:"));
+        } else {
+            assertEquals(String.valueOf(com.android.internal.R.drawable.ic_contact_picture), icon1);
+        }
+
+        values.put(SearchManager.SUGGEST_COLUMN_ICON_2, expectedIcon2);
+        values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, contactId);
+        values.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, contactId);
+        assertCursorValues(c, values);
+        c.close();
+
+        // Cleanup
+        mResolver.delete(rawContactUri, null, null);
+    }
+
+    public void testSearchSuggestionsByPhoneNumber() throws Exception {
+        ContentValues values = new ContentValues();
+
+        Uri searchUri = new Uri.Builder().scheme("content").authority(ContactsContract.AUTHORITY)
+                .appendPath(SearchManager.SUGGEST_URI_PATH_QUERY).appendPath("12345").build();
+
+        Cursor c = mResolver.query(searchUri, null, null, null, null);
+        DatabaseUtils.dumpCursor(c);
+        assertEquals(2, c.getCount());
+        c.moveToFirst();
+
+        values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, "Dial number");
+        values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, "using 12345");
+        values.put(SearchManager.SUGGEST_COLUMN_ICON_1,
+                String.valueOf(com.android.internal.R.drawable.call_contact));
+        values.put(SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
+                Intents.SEARCH_SUGGESTION_DIAL_NUMBER_CLICKED);
+        values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA, "tel:12345");
+        values.putNull(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID);
+        assertCursorValues(c, values);
+
+        c.moveToNext();
+        values.clear();
+        values.put(SearchManager.SUGGEST_COLUMN_TEXT_1, "Create contact");
+        values.put(SearchManager.SUGGEST_COLUMN_TEXT_2, "using 12345");
+        values.put(SearchManager.SUGGEST_COLUMN_ICON_1,
+                String.valueOf(com.android.internal.R.drawable.create_contact));
+        values.put(SearchManager.SUGGEST_COLUMN_INTENT_ACTION,
+                Intents.SEARCH_SUGGESTION_CREATE_CONTACT_CLICKED);
+        values.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA, "tel:12345");
+        values.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
+                SearchManager.SUGGEST_NEVER_MAKE_SHORTCUT);
+        assertCursorValues(c, values);
+        c.close();
+    }
+}
+
diff --git a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
index 502010d..870c7d2 100644
--- a/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
+++ b/tests/src/com/android/providers/contacts/LegacyContactsProviderTest.java
@@ -777,19 +777,6 @@
         values.put(People.STARRED, 1);
     }
 
-    private byte[] loadTestPhoto() throws IOException {
-        final Resources resources = getContext().getResources();
-        InputStream is =
-                resources.openRawResource(com.android.internal.R.drawable.ic_contact_picture);
-        ByteArrayOutputStream os = new ByteArrayOutputStream();
-        byte[] buffer = new byte[1000];
-        int count;
-        while((count = is.read(buffer)) != -1) {
-            os.write(buffer, 0, count);
-        }
-        return os.toByteArray();
-    }
-
     private void assertFilteredContacts(String filter, String... expectedNames) {
         Uri filterUri = Uri.withAppendedPath(People.CONTENT_FILTER_URI, filter);
         Cursor c = mResolver.query(filterUri, null, null, null, null);