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

Commit 3135409d authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Rank results that match the prefix of the first word higher"

parents aba2c95a b7b286cb
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -220,9 +220,8 @@ class CursorToSearchResultConverter {
    private List<String> getBreadcrumbs(SiteMapManager siteMapManager, Cursor cursor) {
        final String screenTitle = cursor.getString(COLUMN_INDEX_SCREEN_TITLE);
        final String screenClass = cursor.getString(COLUMN_INDEX_CLASS_NAME);
        final List<String> breadcrumbs = siteMapManager.buildBreadCrumb(mContext, screenClass,
        return siteMapManager == null ? null : siteMapManager.buildBreadCrumb(mContext, screenClass,
                screenTitle);
        return breadcrumbs;
    }

    /** Uses the breadcrumbs to determine the offset to the base rank.
+123 −25
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.overlay.FeatureFactory;
@@ -27,7 +28,6 @@ import com.android.settings.search.IndexDatabaseHelper;
import com.android.settings.utils.AsyncLoader;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static com.android.settings.search.IndexDatabaseHelper.IndexColumns;
@@ -91,11 +91,13 @@ public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResul

    /**
     * Base ranks defines the best possible rank based on what the query matches.
     * If the query matches the title, the best rank it can be is 1
     * If the query only matches the summary, the best rank it can be is 4
     * If the query only matches keywords or entries, the best rank it can be is 7
     * If the query matches the prefix of the first word in the title, the best rank it can be is 1
     * If the query matches the prefix of the other words in the title, the best rank it can be is 3
     * If the query only matches the summary, the best rank it can be is 7
     * If the query only matches keywords or entries, the best rank it can be is 9
     *
     */
    private static final int[] BASE_RANKS = {1, 4, 7};
    private static final int[] BASE_RANKS = {1, 3, 7, 9};

    private final String mQueryText;
    private final Context mContext;
@@ -121,33 +123,28 @@ public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResul
            return null;
        }

        final List<SearchResult> primaryResults;
        final List<SearchResult> primaryFirstWordResults;
        final List<SearchResult> primaryMidWordResults;
        final List<SearchResult> secondaryResults;
        final List<SearchResult> tertiaryResults;

        primaryResults = query(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]);
        secondaryResults = query(MATCH_COLUMNS_SECONDARY, BASE_RANKS[1]);
        tertiaryResults = query(MATCH_COLUMNS_TERTIARY, BASE_RANKS[2]);
        primaryFirstWordResults = firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]);
        primaryMidWordResults = secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]);
        secondaryResults = anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]);
        tertiaryResults = anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]);

        final List<SearchResult> results = new ArrayList<>(primaryResults.size()
        final List<SearchResult> results = new ArrayList<>(
                primaryFirstWordResults.size()
                + primaryMidWordResults.size()
                + secondaryResults.size()
                + tertiaryResults.size());

        results.addAll(primaryResults);
        results.addAll(primaryFirstWordResults);
        results.addAll(primaryMidWordResults);
        results.addAll(secondaryResults);
        results.addAll(tertiaryResults);
        return results;
    }

    private List<SearchResult> query(String[] matchColumns, int baseRank) {
        final String whereClause = buildWhereClause(matchColumns);
        final String[] selection = buildQuerySelection(matchColumns.length * 2);

        final SQLiteDatabase database = IndexDatabaseHelper.getInstance(mContext)
                .getReadableDatabase();
        final Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
                selection, null, null, null);
        return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
        return results;
    }

    @Override
@@ -168,7 +165,94 @@ public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResul
        return query.trim();
    }

    private static String buildWhereClause(String[] matchColumns) {
    /**
     * Creates and executes the query which matches prefixes of the first word of the given columns.
     *
     * @param matchColumns The columns to match on
     * @param baseRank The highest rank achievable by these results
     * @return A list of the matching results.
     */
    private List<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {
        final String whereClause = buildSingleWordWhereClause(matchColumns);
        final String query = mQueryText + "%";
        final String[] selection = buildSingleWordSelection(query, matchColumns.length);

        return query(whereClause, selection, baseRank);
    }

    /**
     * Creates and executes the query which matches prefixes of the non-first words of the
     * given columns.
     *
     * @param matchColumns The columns to match on
     * @param baseRank The highest rank achievable by these results
     * @return A list of the matching results.
     */
    private List<SearchResult> secondaryWordQuery(String[] matchColumns, int baseRank) {
        final String whereClause = buildSingleWordWhereClause(matchColumns);
        final String query = "% " + mQueryText + "%";
        final String[] selection = buildSingleWordSelection(query, matchColumns.length);

        return query(whereClause, selection, baseRank);
    }

    /**
     * Creates and executes the query which matches prefixes of the any word of the given columns.
     *
     * @param matchColumns The columns to match on
     * @param baseRank The highest rank achievable by these results
     * @return A list of the matching results.
     */
    private List<SearchResult> anyWordQuery(String[] matchColumns, int baseRank) {
        final String whereClause = buildTwoWordWhereClause(matchColumns);
        final String[] selection = buildAnyWordSelection(matchColumns.length * 2);

        return query(whereClause, selection, baseRank);
    }

    /**
     * Generic method used by all of the query methods above to execute a query.
     *
     * @param whereClause Where clause for the SQL query which uses bindings.
     * @param selection List of the transformed query to match each bind in the whereClause
     * @param baseRank The highest rank achievable by these results.
     * @return A list of the matching results.
     */
    private List<SearchResult> query(String whereClause, String[] selection, int baseRank) {
        final SQLiteDatabase database = IndexDatabaseHelper.getInstance(mContext)
                .getReadableDatabase();
        final Cursor resultCursor = database.query(TABLE_PREFS_INDEX, SELECT_COLUMNS, whereClause,
                selection, null, null, null);
        return mConverter.convertCursor(mSiteMapManager, resultCursor, baseRank);
    }

    /**
     * Builds the SQLite WHERE clause that matches all matchColumns for a single query.
     *
     * @param matchColumns List of columns that will be used for matching.
     * @return The constructed WHERE clause.
     */
    private static String buildSingleWordWhereClause(String[] matchColumns) {
        StringBuilder sb = new StringBuilder(" (");
        final int count = matchColumns.length;
        for (int n = 0; n < count; n++) {
            sb.append(matchColumns[n]);
            sb.append(" like ? ");
            if (n < count - 1) {
                sb.append(" OR ");
            }
        }
        sb.append(") AND enabled = 1");
        return sb.toString();
    }

    /**
     * Builds the SQLite WHERE clause that matches all matchColumns to two different queries.
     *
     * @param matchColumns List of columns that will be used for matching.
     * @return The constructed WHERE clause.
     */
    private static String buildTwoWordWhereClause(String[] matchColumns) {
        StringBuilder sb = new StringBuilder(" (");
        final int count = matchColumns.length;
        for (int n = 0; n < count; n++) {
@@ -184,6 +268,20 @@ public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResul
        return sb.toString();
    }

    /**
     * Fills out the selection array to match the query as the prefix of a single word.
     *
     * @param size is the number of columns to be matched.
     */
    private String[] buildSingleWordSelection(String query, int size) {
        String[] selection = new String[size];

        for(int i = 0; i < size; i ++) {
            selection[i] = query;
        }
        return selection;
    }

    /**
     * Fills out the selection array to match the query as the prefix of a word.
     *
@@ -191,7 +289,7 @@ public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResul
     *             of the first word in the column. The second match is for any subsequent word
     *             prefix match.
     */
    private String[] buildQuerySelection(int size) {
    private String[] buildAnyWordSelection(int size) {
        String[] selection = new String[size];
        final String query = mQueryText + "%";
        final String subStringQuery = "% " + mQueryText + "%";
+2 −2
Original line number Diff line number Diff line
@@ -211,8 +211,8 @@ public class InstalledAppResultLoader extends AsyncLoader<List<? extends SearchR
     */
    private int getRank(int wordDiff) {
        if (wordDiff < 6) {
            return 3;
            return 2;
        }
        return 4;
        return 3;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -30,7 +30,7 @@ public class SearchResult implements Comparable<SearchResult> {
     * Defines the max rank for a search result to be considered as ranked. Results with ranks
     * higher than this have no guarantee for sorting order.
     */
    public static final int MAX_RANK  = 9;
    public static final int MAX_RANK  = 10;

    /**
     * The title of the result and main text displayed.
+20 −5
Original line number Diff line number Diff line
@@ -15,7 +15,7 @@
 *
 */

package com.android.settings.search;
package com.android.settings.search2;

import android.content.ContentValues;
import android.content.Context;
@@ -24,8 +24,7 @@ import android.database.sqlite.SQLiteDatabase;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.dashboard.SiteMapManager;
import com.android.settings.search2.DatabaseIndexingUtils;
import com.android.settings.search2.DatabaseResultLoader;
import com.android.settings.search.IndexDatabaseHelper;
import com.android.settings.testutils.DatabaseTestUtils;
import com.android.settings.testutils.FakeFeatureFactory;

@@ -39,6 +38,8 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

import java.util.List;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
@@ -200,12 +201,26 @@ public class DatabaseResultLoaderTest {
        assertThat(loader.loadInBackground().size()).isEqualTo(1);
    }

    @Test
    public void testSpecialCaseTwoWords_FirstWordMatches_RanksHigher() {
        final String caseOne = "Apple pear";
        final String caseTwo = "Banana apple";
        insertSpecialCase(caseOne);
        insertSpecialCase(caseTwo);
        loader = new DatabaseResultLoader(mContext, "App", null);
        List<? extends SearchResult> results = loader.loadInBackground();

        assertThat(results.get(0).title).isEqualTo(caseOne);
        assertThat(results.get(1).title).isEqualTo(caseTwo);
        assertThat(results.get(0).rank).isLessThan(results.get(1).rank);
    }

    private void insertSpecialCase(String specialCase) {
        String normalized = DatabaseIndexingUtils.normalizeHyphen(specialCase);
        normalized = DatabaseIndexingUtils.normalizeString(normalized);

        ContentValues values = new ContentValues();
        values.put(IndexDatabaseHelper.IndexColumns.DOCID, 0);
        values.put(IndexDatabaseHelper.IndexColumns.DOCID, normalized.hashCode());
        values.put(IndexDatabaseHelper.IndexColumns.LOCALE, "en-us");
        values.put(IndexDatabaseHelper.IndexColumns.DATA_RANK, 1);
        values.put(IndexDatabaseHelper.IndexColumns.DATA_TITLE, specialCase);