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

Commit 1f82861d authored by Walter Jang's avatar Walter Jang
Browse files

Add contact search display name experiment (1/2)

We make two queries which check if the display
name exactly matches the phonetic name in order
to prevent duplicates when the display name is
coming from the phonetic name.

Bug 26697731

Change-Id: Iab2e8b158bcc6f6df06c23da257d9d930363e60a
(cherry picked from commit 142ad048c17284aa14e9f88a31074fe2ee5a6f45)
parent 40c7479f
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -16,10 +16,17 @@
package com.android.contacts.common;

/**
 * Experiment flag name constants.
 * Experiment flag names.
 */
public final class Experiments {

    /**
     * Search study boolean indicating whether to perform a simple display name query, instead of
     * the normal type-to-filter query.
     */
    public static final String FLAG_SEARCH_DISPLAY_NAME_QUERY =
            "Search__display_name_query";

    /**
     * Search study boolean indicating whether to order starred and frequently occurring
     * search results first.
+32 −19
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
            Contacts.PHOTO_THUMBNAIL_URI,           // 5
            Contacts.LOOKUP_KEY,                    // 6
            Contacts.IS_USER_PROFILE,               // 7
            Contacts.PHONETIC_NAME,                 // 8
        };

        private static final String[] CONTACT_PROJECTION_ALTERNATIVE = new String[] {
@@ -59,6 +60,7 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
            Contacts.PHOTO_THUMBNAIL_URI,           // 5
            Contacts.LOOKUP_KEY,                    // 6
            Contacts.IS_USER_PROFILE,               // 7
            Contacts.PHONETIC_NAME,                 // 8
        };

        private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
@@ -70,9 +72,10 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
            Contacts.PHOTO_THUMBNAIL_URI,           // 5
            Contacts.LOOKUP_KEY,                    // 6
            Contacts.IS_USER_PROFILE,               // 7
            Contacts.TIMES_CONTACTED,               // 8
            Contacts.STARRED,                       // 9
            SearchSnippets.SNIPPET,                 // 10
            Contacts.PHONETIC_NAME,                 // 8
            Contacts.TIMES_CONTACTED,               // 9
            Contacts.STARRED,                       // 10
            SearchSnippets.SNIPPET,                 // 11
        };

        private static final String[] FILTER_PROJECTION_ALTERNATIVE = new String[] {
@@ -84,9 +87,10 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
            Contacts.PHOTO_THUMBNAIL_URI,           // 5
            Contacts.LOOKUP_KEY,                    // 6
            Contacts.IS_USER_PROFILE,               // 7
            Contacts.TIMES_CONTACTED,               // 8
            Contacts.STARRED,                       // 9
            SearchSnippets.SNIPPET,                 // 10
            Contacts.PHONETIC_NAME,                 // 8
            Contacts.TIMES_CONTACTED,               // 9
            Contacts.STARRED,                       // 10
            SearchSnippets.SNIPPET,                 // 11
        };

        public static final int CONTACT_ID               = 0;
@@ -97,12 +101,15 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
        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_TIMES_CONTACTED  = 8;
        public static final int CONTACT_STARRED          = 9;
        public static final int CONTACT_SNIPPET          = 10;
        public static final int CONTACT_PHONETIC_NAME    = 8;
        public static final int CONTACT_TIMES_CONTACTED  = 9;
        public static final int CONTACT_STARRED          = 10;
        public static final int CONTACT_SNIPPET          = 11;
    }

    protected static class StrequentQuery {
    // 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.
    protected static class ExperimentQuery {

        private static final String[] FILTER_PROJECTION_PRIMARY = new String[] {
                Contacts._ID,                           // 0
@@ -113,8 +120,9 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
                Contacts.PHOTO_THUMBNAIL_URI,           // 5
                Contacts.LOOKUP_KEY,                    // 6
                Contacts.IS_USER_PROFILE,               // 7
                Contacts.TIMES_CONTACTED,               // 8
                Contacts.STARRED,                       // 9
                Contacts.PHONETIC_NAME,                 // 8
                Contacts.TIMES_CONTACTED,               // 9
                Contacts.STARRED,                       // 10
                // SearchSnippets.SNIPPET not supported
        };

@@ -127,8 +135,9 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
                Contacts.PHOTO_THUMBNAIL_URI,           // 5
                Contacts.LOOKUP_KEY,                    // 6
                Contacts.IS_USER_PROFILE,               // 7
                Contacts.TIMES_CONTACTED,               // 8
                Contacts.STARRED,                       // 9
                Contacts.PHONETIC_NAME,                 // 8
                Contacts.TIMES_CONTACTED,               // 9
                Contacts.STARRED,                       // 10
                // SearchSnippets.SNIPPET not supported
        };

@@ -140,8 +149,9 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
        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_TIMES_CONTACTED  = 8;
        public static final int CONTACT_STARRED          = 9;
        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
    }

@@ -432,10 +442,13 @@ public abstract class ContactListAdapter extends ContactEntryListAdapter {
        }
    }

    protected final String[] getStrequentProjection() {
    /**
     * Returns the projection useful for search experiments.
     */
    protected final String[] getExperimentProjection() {
        final int sortOrder = getContactNameDisplayOrder();
        return sortOrder == ContactsPreferences.DISPLAY_ORDER_PRIMARY
                ? StrequentQuery.FILTER_PROJECTION_PRIMARY
                : StrequentQuery.FILTER_PROJECTION_ALTERNATIVE;
                ? ExperimentQuery.FILTER_PROJECTION_PRIMARY
                : ExperimentQuery.FILTER_PROJECTION_ALTERNATIVE;
    }
}
+81 −16
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Directory;
import android.provider.ContactsContract.SearchSnippets;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.view.View;

@@ -56,12 +57,11 @@ public class DefaultContactListAdapter extends ContactListAdapter {
            ((ProfileAndContactsLoader) loader).setLoadProfile(shouldIncludeProfile());
        }

        ContactListFilter filter = getFilter();
        String sortOrder = null;
        if (isSearchMode()) {
            final Flags flags = Flags.getInstance(mContext);
            String query = getQueryString();
            if (query == null) {
                query = "";
            }
            if (query == null) query = "";
            query = query.trim();
            if (TextUtils.isEmpty(query)) {
                // Regardless of the directory, we don't want anything returned,
@@ -69,55 +69,120 @@ public class DefaultContactListAdapter extends ContactListAdapter {
                loader.setUri(Contacts.CONTENT_URI);
                loader.setProjection(getProjection(false));
                loader.setSelection("0");
            } else if (flags.getBoolean(Experiments.FLAG_SEARCH_DISPLAY_NAME_QUERY, false)) {
                final String displayNameColumn =
                        getContactNameDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY
                                ? Contacts.DISPLAY_NAME_PRIMARY : Contacts.DISPLAY_NAME_ALTERNATIVE;

                final Builder builder = Contacts.CONTENT_URI.buildUpon();
                appendSearchDirectoryParameters(builder, directoryId);
                loader.setUri(builder.build());
                loader.setProjection(getExperimentProjection());
                loader.setSelection(getDisplayNameSelection(query, displayNameColumn));
                loader.setSelectionArgs(getDisplayNameSelectionArgs(query));
                if (flags.getBoolean(Experiments.FLAG_SEARCH_STREQUENTS_FIRST, false)) {
                    sortOrder = String.format("%s DESC, %s DESC",
                            Contacts.TIMES_CONTACTED, Contacts.STARRED);
                }
            } else {
                final Builder builder = ContactsCompat.getContentUri().buildUpon();
                appendSearchParameters(builder, query, directoryId);
                loader.setUri(builder.build());
                loader.setProjection(getProjection(true));
                if (Flags.getInstance(mContext).getBoolean(
                        Experiments.FLAG_SEARCH_STREQUENTS_FIRST, false)) {
                if (flags.getBoolean(Experiments.FLAG_SEARCH_STREQUENTS_FIRST, false)) {
                    // Filter out starred and frequently contacted contacts from the main loader
                    // query results
                    loader.setSelection(Contacts.TIMES_CONTACTED + "=0 AND "
                            + Contacts.STARRED + "=0");

                    // Strequent contacts will be merged back in before the main loader query
                    // results and after the profile (ME).
                    // Strequent contacts will be merged back in after the profile (ME) and before
                    // the main loader query results.
                    final ProfileAndContactsLoader profileAndContactsLoader =
                            (ProfileAndContactsLoader) loader;
                    profileAndContactsLoader.setLoadStrequent(true);
                    final Builder strequentBuilder =
                            Contacts.CONTENT_STREQUENT_FILTER_URI.buildUpon();
                    appendSearchParameters(strequentBuilder, query, directoryId);
                    profileAndContactsLoader.setStrequentUri(strequentBuilder.build());
                    profileAndContactsLoader.setStrequentProjection(getStrequentProjection());
                    profileAndContactsLoader.setLoadStrequents(
                            strequentBuilder.build(), getExperimentProjection());
                }
            }
        } else {
            final ContactListFilter filter = getFilter();
            configureUri(loader, directoryId, filter);
            loader.setProjection(getProjection(false));
            configureSelection(loader, directoryId, filter);
        }

        String sortOrder;
        if (getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY) {
            if (sortOrder == null) {
                sortOrder = Contacts.SORT_KEY_PRIMARY;
            } else {
                sortOrder += ", " + Contacts.SORT_KEY_PRIMARY;
            }
        } else {
            if (sortOrder == null) {
                sortOrder = Contacts.SORT_KEY_ALTERNATIVE;
            } else {
                sortOrder += ", " + Contacts.SORT_KEY_ALTERNATIVE;
            }
        }

        loader.setSortOrder(sortOrder);
    }

    /**
     * Splits the given query by whitespace and adds a display name and phonetic name selection
     * clause once for each token.
     *
     * @param displayNameColumn The display name column to use in the returned selection String
     */
    @VisibleForTesting
    static String getDisplayNameSelection(String query, String displayNameColumn) {
        final String[] tokens = getDisplayNameSearchSelectionTokens(query);
        if (tokens == null) return null;
        final StringBuilder builder = new StringBuilder();
        for (int i = 0; i < tokens.length; i++) {
            if (builder.length() > 0) builder.append(" OR ");
            final String param = "?" + (i + 1);
            builder.append("(" + displayNameColumn + " LIKE " + param +
                    " OR " + Contacts.PHONETIC_NAME + " LIKE " + param + ")");
        }
        return builder.toString();
    }

    /**
     * Splits the given query by whitespace and returns the resulting tokens, each one
     * wrapped with "%" on either side.
     */
    @VisibleForTesting
    static String[] getDisplayNameSelectionArgs(String query) {
        final String[] tokens = getDisplayNameSearchSelectionTokens(query);
        if (tokens == null) return null;
        for (int i = 0; i < tokens.length; i++) {
            tokens[i] = "%" + tokens[i] + "%";
        }
        return tokens;
    }

    private static String[] getDisplayNameSearchSelectionTokens(String query) {
        if (query == null) return null;
        query = query.trim();
        if (query.length() == 0) return null;
        return query.split("\\s+");
    }

    private void appendSearchParameters(Builder builder, String query, long directoryId) {
        builder.appendPath(query); // Builder will encode the query
        appendSearchDirectoryParameters(builder, directoryId);
        builder.appendQueryParameter(SearchSnippets.DEFERRED_SNIPPETING_KEY, "1");
    }

    private void appendSearchDirectoryParameters(Builder builder, long directoryId) {
        builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
                String.valueOf(directoryId));
        if (directoryId != Directory.DEFAULT && directoryId != Directory.LOCAL_INVISIBLE) {
            builder.appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
                    String.valueOf(getDirectoryResultLimit(getDirectoryById(directoryId))));
        }
        builder.appendQueryParameter(SearchSnippets.DEFERRED_SNIPPETING_KEY, "1");
    }

    protected void configureUri(CursorLoader loader, long directoryId, ContactListFilter filter) {
+6 −13
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import android.database.MatrixCursor;
import android.database.MergeCursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Profile;

import com.google.common.collect.Lists;
@@ -36,10 +35,11 @@ import java.util.List;
public class ProfileAndContactsLoader extends CursorLoader {

    private boolean mLoadProfile;
    private boolean mLoadStrequent;

    private String[] mProjection;
    private String[] mStrequentProjection;

    private Uri mStrequentUri;
    private String[] mStrequentProjection;

    public ProfileAndContactsLoader(Context context) {
        super(context);
@@ -49,21 +49,14 @@ public class ProfileAndContactsLoader extends CursorLoader {
        mLoadProfile = flag;
    }

    public void setLoadStrequent(boolean flag) {
        mLoadStrequent = flag;
    }

    public void setProjection(String[] projection) {
        super.setProjection(projection);
        mProjection = projection;
    }

    public void setStrequentProjection(String[] projection) {
        mStrequentProjection = projection;
    }

    public void setStrequentUri(Uri uri) {
    public void setLoadStrequents(Uri uri, String[] projection) {
        mStrequentUri = uri;
        mStrequentProjection = projection;
    }

    @Override
@@ -73,7 +66,7 @@ public class ProfileAndContactsLoader extends CursorLoader {
        if (mLoadProfile) {
            cursors.add(loadProfile());
        }
        if (mLoadStrequent) {
        if (mStrequentUri != null && mStrequentProjection != null) {
            cursors.add(loadStrequent());
        }
        // ContactsCursor.loadInBackground() can return null; MergeCursor
+1 −1
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ public final class Flags {
    }

    public boolean getBoolean(String flagName, boolean defValue) {
        return false;
        return defValue;
    }

    public float getFloat(String flagName, float defValue) {
Loading