Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bad5e4ba authored by Walter Jang's avatar Walter Jang
Browse files

Include email and phone matches in display name search experiment

Bug 26697731

Change-Id: Iadb39c40c8fdf722d1099b87b6506bffcc33270f
parent 8f01264b
Loading
Loading
Loading
Loading
+63 −16
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Directory;
import android.provider.ContactsContract.SearchSnippets;
import android.text.TextUtils;
@@ -27,9 +29,12 @@ import android.view.ViewGroup;
import android.widget.ListView;

import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
import com.android.contacts.common.Experiments;
import com.android.contacts.common.R;
import com.android.contacts.common.compat.ContactsCompat;
import com.android.contacts.common.preference.ContactsPreferences;
import com.android.contacts.common.util.ContactDisplayUtils;
import com.android.contacts.commonbind.experiments.Flags;

/**
 * A cursor adapter for the {@link ContactsContract.Contacts#CONTENT_TYPE} content type.
@@ -107,8 +112,8 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
        public static final int CONTACT_SNIPPET          = 11;
    }

    // NOTE: These projections must match those in ContactQuery above expect we omit the
    // SearchSnippers.SNIPPET column since that is only supported with Contacts.CONTENT_FILTER_URI.
    // The projection columns should match those in ContactQuery above expect we must omit the
    // columns which are not supported
    protected static class ExperimentQuery {

        private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
@@ -123,7 +128,7 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
                Contacts.PHONETIC_NAME,                 // 8
                Contacts.TIMES_CONTACTED,               // 9
                Contacts.STARRED,                       // 10
                // SearchSnippets.SNIPPET not supported
                // SearchSnippets.SNIPPET not supported for Contacts.CONTENT_URI
        };

        private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
@@ -138,21 +143,48 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
                Contacts.PHONETIC_NAME,                 // 8
                Contacts.TIMES_CONTACTED,               // 9
                Contacts.STARRED,                       // 10
                // SearchSnippets.SNIPPET not supported
                // SearchSnippets.SNIPPET not supported for Contacts.CONTENT_URI
        };

        public static final int CONTACT_ID               = 0;
        public static final int CONTACT_DISPLAY_NAME     = 1;
        public static final int CONTACT_PRESENCE_STATUS  = 2;
        public static final int CONTACT_CONTACT_STATUS   = 3;
        public static final int CONTACT_PHOTO_ID         = 4;
        public static final int CONTACT_PHOTO_URI        = 5;
        public static final int CONTACT_LOOKUP_KEY       = 6;
        public static final int CONTACT_IS_USER_PROFILE  = 7;
        public static final int CONTACT_PHONETIC_NAME    = 8;
        public static final int CONTACT_TIMES_CONTACTED  = 9;
        public static final int CONTACT_STARRED          = 10;
        // SearchSnippets.SNIPPET not supported
        public static final String[] FILTER_PROJECTION_PRIMARY_EMAIL = new String[] {
                Contacts._ID,                           // 0
                Contacts.DISPLAY_NAME_PRIMARY,          // 1
                Contacts.CONTACT_PRESENCE,              // 2
                Contacts.CONTACT_STATUS,                // 3
                Contacts.PHOTO_ID,                      // 4
                Contacts.PHOTO_THUMBNAIL_URI,           // 5
                Contacts.LOOKUP_KEY,                    // 6
                // Contacts.IS_USER_PROFILE not supported for Data.CONTENT_URI
                Contacts.IN_VISIBLE_GROUP,              // 7
                Contacts.PHONETIC_NAME,                 // 8
                Contacts.TIMES_CONTACTED,               // 9
                Contacts.STARRED,                       // 10
                // SearchSnippets.SNIPPET not supported for Data.CONTENT_URI
                Email.ADDRESS,                          // 11
        };

        public static final int EMAIL_ADDRESS = 11;

        public static final String[] FILTER_PROJECTION_PRIMARY_PHONE = new String[] {
                Contacts._ID,                           // 0
                Contacts.DISPLAY_NAME_PRIMARY,          // 1
                Contacts.CONTACT_PRESENCE,              // 2
                Contacts.CONTACT_STATUS,                // 3
                Contacts.PHOTO_ID,                      // 4
                Contacts.PHOTO_THUMBNAIL_URI,           // 5
                Contacts.LOOKUP_KEY,                    // 6
                // Contacts.IS_USER_PROFILE not supported for Data.CONTENT_URI
                Contacts.IN_VISIBLE_GROUP,              // 7
                Contacts.PHONETIC_NAME,                 // 8
                Contacts.TIMES_CONTACTED,               // 9
                Contacts.STARRED,                       // 10
                // SearchSnippets.SNIPPET not supported for Data.CONTENT_URI
                Phone.NUMBER,                           // 11
                Phone.NORMALIZED_NUMBER,                // 12
        };

        public static final int NUMBER = 11;
        public static final int NORMAILIZED_NUMBER = 12;
    }

    private CharSequence mUnknownNameText;
@@ -328,6 +360,21 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
    }

    protected void bindSearchSnippet(final ContactListItemView view, Cursor cursor) {
        if (Flags.getInstance(mContext).getBoolean(
                Experiments.FLAG_SEARCH_DISPLAY_NAME_QUERY, false)) {
            final String queryString = getQueryString();
            if (ContactDisplayUtils.isPossiblePhoneNumber(queryString)
                    && cursor.getColumnCount() > ExperimentQuery.NUMBER
                    && Phone.NUMBER.equals(cursor.getColumnName(ExperimentQuery.NUMBER))) {
                view.showSnippet(cursor, queryString, ExperimentQuery.NUMBER);
                return;
            }
            if (cursor.getColumnCount() > ExperimentQuery.EMAIL_ADDRESS
                    && Email.ADDRESS.equals(cursor.getColumnName(ExperimentQuery.EMAIL_ADDRESS))) {
                view.showSnippet(cursor, queryString, ExperimentQuery.EMAIL_ADDRESS);
                return;
            }
        }
        view.showSnippet(cursor, ContactQuery.CONTACT_SNIPPET);
    }

+26 −1
Original line number Diff line number Diff line
@@ -38,7 +38,6 @@ import android.text.SpannableString;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -56,6 +55,7 @@ import com.android.contacts.common.R;
import com.android.contacts.common.compat.CompatUtils;
import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
import com.android.contacts.common.format.TextHighlighter;
import com.android.contacts.common.list.ContactListAdapter.ExperimentQuery;
import com.android.contacts.common.util.ContactDisplayUtils;
import com.android.contacts.common.util.SearchUtil;
import com.android.contacts.common.util.ViewUtil;
@@ -1478,6 +1478,31 @@ public class ContactListItemView extends ViewGroup
        setStatus(statusMessage);
    }

    /**
     * Shows search snippet for email and phone number matches.
     */
    public void showSnippet(Cursor cursor, String query, int snippetColumn) {
        // TODO: this does not properly handle phone numbers with control characters
        // For example if the phone number is 444-5555, the search query 4445 will match the
        // number since we normalize it before querying CP2 but the snippet will fail since
        // the portion to be highlighted is 444-5 not 4445.
        final String snippet = cursor.getString(snippetColumn);
        if (snippet == null) {
            setSnippet(null);
            return;
        }
        final String displayName = cursor.getColumnIndex(Contacts.DISPLAY_NAME) >= 0
                ? cursor.getString(cursor.getColumnIndex(Contacts.DISPLAY_NAME)) : null;
        if (snippet.equals(displayName)) {
            // If the snippet exactly matches the display name (i.e. the phone number or email
            // address is being used as the display name) then no snippet is necessary
            setSnippet(null);
            return;
        }
        // Show the snippet with the part of the query that matched it
        setSnippet(updateSnippet(snippet, query, displayName));
    }

    /**
     * Shows search snippet.
     */
+36 −7
Original line number Diff line number Diff line
@@ -25,6 +25,9 @@ import android.net.Uri.Builder;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Directory;
import android.provider.ContactsContract.SearchSnippets;
import android.support.annotation.VisibleForTesting;
@@ -33,7 +36,9 @@ import android.view.View;

import com.android.contacts.common.Experiments;
import com.android.contacts.common.compat.ContactsCompat;
import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
import com.android.contacts.common.preference.ContactsPreferences;
import com.android.contacts.common.util.ContactDisplayUtils;
import com.android.contacts.commonbind.experiments.Flags;

import java.util.ArrayList;
@@ -71,10 +76,10 @@ public class DefaultContactListAdapter extends ContactListAdapter {
                loader.setSelection("0");
            } else if (flags.getBoolean(Experiments.FLAG_SEARCH_DISPLAY_NAME_QUERY, false)
                && directoryId == Directory.DEFAULT) {
                // Configure the loader to match display and phonetic names
                final String displayNameColumn =
                        getContactNameDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY
                                ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME_ALTERNATIVE;

                final Builder builder = Contacts.CONTENT_URI.buildUpon();
                builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
                        String.valueOf(directoryId));
@@ -82,6 +87,30 @@ public class DefaultContactListAdapter extends ContactListAdapter {
                loader.setProjection(getExperimentProjection());
                loader.setSelection(getDisplayNameSelection(query, displayNameColumn));
                loader.setSelectionArgs(getDisplayNameSelectionArgs(query));

                // Configure an extra query to show email and phone number matches and merge
                // them in after the display name loader query result.
                final ProfileAndContactsLoader profileAndContactsLoader =
                        (ProfileAndContactsLoader) loader;
                final Builder extraBuilder = Data.CONTENT_URI.buildUpon();
                if (ContactDisplayUtils.isPossiblePhoneNumber(query)) {
                    final String normalizedQuery = PhoneNumberUtilsCompat.normalizeNumber(query);
                    profileAndContactsLoader.setLoadExtraContactsLast(
                            extraBuilder.build(),
                            ExperimentQuery.FILTER_PROJECTION_PRIMARY_PHONE,
                            Data.MIMETYPE + "=? AND " + Phone.NORMALIZED_NUMBER + " LIKE ? AND " +
                                    Contacts.IN_VISIBLE_GROUP + "=?",
                            new String[]{Phone.CONTENT_ITEM_TYPE, "%" + normalizedQuery + "%",
                                    "1"});
                } else {
                    final Builder emailBuilder = Data.CONTENT_URI.buildUpon();
                    profileAndContactsLoader.setLoadExtraContactsLast(
                            emailBuilder.build(),
                            ExperimentQuery.FILTER_PROJECTION_PRIMARY_EMAIL,
                            Data.MIMETYPE + "=? AND " + Email.ADDRESS + " LIKE ? AND " +
                                    Contacts.IN_VISIBLE_GROUP + "=?",
                            new String[]{Email.CONTENT_ITEM_TYPE, "%" + query + "%", "1"});
                }
                if (flags.getBoolean(Experiments.FLAG_SEARCH_STREQUENTS_FIRST, false)) {
                    sortOrder = String.format("%s DESC, %s DESC",
                            Contacts.TIMES_CONTACTED, Contacts.STARRED);
@@ -97,14 +126,14 @@ public class DefaultContactListAdapter extends ContactListAdapter {
                    loader.setSelection(Contacts.TIMES_CONTACTED + "=0 AND "
                            + Contacts.STARRED + "=0");

                    // Strequent contacts will be merged back in after the profile (ME) and before
                    // the main loader query results.
                    // Configure an extra query to load strequent contacts and merge them in
                    // before the main loader query results.
                    final ProfileAndContactsLoader profileAndContactsLoader =
                            (ProfileAndContactsLoader) loader;
                    final Builder strequentBuilder =
                            Contacts.CONTENT_STREQUENT_FILTER_URI.buildUpon();
                    appendSearchParameters(strequentBuilder, query, directoryId);
                    profileAndContactsLoader.setLoadStrequents(
                    profileAndContactsLoader.setLoadExtraContactsFirst(
                            strequentBuilder.build(), getExperimentProjection());
                }
            }
@@ -139,7 +168,7 @@ public class DefaultContactListAdapter extends ContactListAdapter {
     */
    @VisibleForTesting
    static String getDisplayNameSelection(String query, String displayNameColumn) {
        final String[] tokens = getDisplayNameSearchSelectionTokens(query);
        final String[] tokens = getDisplayNameSelectionTokens(query);
        if (tokens == null) return null;
        final StringBuilder builder = new StringBuilder();
        for (int i = 0; i < tokens.length; i++) {
@@ -157,7 +186,7 @@ public class DefaultContactListAdapter extends ContactListAdapter {
     */
    @VisibleForTesting
    static String[] getDisplayNameSelectionArgs(String query) {
        final String[] tokens = getDisplayNameSearchSelectionTokens(query);
        final String[] tokens = getDisplayNameSelectionTokens(query);
        if (tokens == null) return null;
        for (int i = 0; i < tokens.length; i++) {
            tokens[i] = "%" + tokens[i] + "%";
@@ -165,7 +194,7 @@ public class DefaultContactListAdapter extends ContactListAdapter {
        return tokens;
    }

    private static String[] getDisplayNameSearchSelectionTokens(String query) {
    private static String[] getDisplayNameSelectionTokens(String query) {
        if (query == null) return null;
        query = query.trim();
        if (query.length() == 0) return null;
+32 −12
Original line number Diff line number Diff line
@@ -38,13 +38,17 @@ public class ProfileAndContactsLoader extends CursorLoader {

    private String[] mProjection;

    private Uri mStrequentUri;
    private String[] mStrequentProjection;
    private Uri mExtraUri;
    private String[] mExtraProjection;
    private String mExtraSelection;
    private String[] mExtraSelectionArgs;
    private boolean mMergeExtraContactsAfterPrimary;

    public ProfileAndContactsLoader(Context context) {
        super(context);
    }

    /** Whether to load the profile and merge results in before any other results. */
    public void setLoadProfile(boolean flag) {
        mLoadProfile = flag;
    }
@@ -54,9 +58,25 @@ public class ProfileAndContactsLoader extends CursorLoader {
        mProjection = projection;
    }

    public void setLoadStrequents(Uri uri, String[] projection) {
        mStrequentUri = uri;
        mStrequentProjection = projection;
    /** Configure an extra query and merge results in before the primary results. */
    public void setLoadExtraContactsFirst(Uri uri, String[] projection) {
        mExtraUri = uri;
        mExtraProjection = projection;
        mMergeExtraContactsAfterPrimary = false;
    }

    /** Configure an extra query and merge results in after the primary results. */
    public void setLoadExtraContactsLast(Uri uri, String[] projection, String selection,
            String[] selectionArgs) {
        mExtraUri = uri;
        mExtraProjection = projection;
        mExtraSelection = selection;
        mExtraSelectionArgs = selectionArgs;
        mMergeExtraContactsAfterPrimary = true;
    }

    private boolean canLoadExtraContacts() {
        return mExtraUri != null && mExtraProjection != null;
    }

    @Override
@@ -66,8 +86,8 @@ public class ProfileAndContactsLoader extends CursorLoader {
        if (mLoadProfile) {
            cursors.add(loadProfile());
        }
        if (mStrequentUri != null && mStrequentProjection != null) {
            cursors.add(loadStrequent());
        if (canLoadExtraContacts() && !mMergeExtraContactsAfterPrimary) {
            cursors.add(loadExtraContacts());
        }
        // ContactsCursor.loadInBackground() can return null; MergeCursor
        // correctly handles null cursors.
@@ -79,6 +99,9 @@ public class ProfileAndContactsLoader extends CursorLoader {
        }
        final Cursor contactsCursor = cursor;
        cursors.add(contactsCursor);
        if (canLoadExtraContacts() && mMergeExtraContactsAfterPrimary) {
            cursors.add(loadExtraContacts());
        }
        return new MergeCursor(cursors.toArray(new Cursor[cursors.size()])) {
            @Override
            public Bundle getExtras() {
@@ -115,11 +138,8 @@ public class ProfileAndContactsLoader extends CursorLoader {
        }
    }

    /**
     * Loads starred and frequently contacted contacts
     */
    private Cursor loadStrequent() {
    private Cursor loadExtraContacts() {
        return getContext().getContentResolver().query(
                mStrequentUri, mStrequentProjection, null, null, null);
                mExtraUri, mExtraProjection, mExtraSelection, mExtraSelectionArgs, null);
    }
}