Rank results that match the prefix of the first word higher

Differentiates the results that match the first word in
the title from matches farther into the title.

Change-Id: I69f9804a51d1c2b0e476b0f082d634b3a598997c
Fixes: 34975472
Test: make RunSettingsRoboTests
This commit is contained in:
Matthew Fritze
2017-02-13 17:45:46 -08:00
parent c65e3a19a1
commit b7b286cb89
5 changed files with 147 additions and 35 deletions

View File

@@ -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.

View File

@@ -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,35 +123,30 @@ 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);
}
@Override
protected boolean onCancelLoad() {
// TODO
@@ -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 + "%";
@@ -202,4 +300,4 @@ public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResul
}
return selection;
}
}
}

View File

@@ -207,8 +207,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;
}
}

View File

@@ -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.

View File

@@ -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);
@@ -313,4 +328,4 @@ public class DatabaseResultLoaderTest {
mDb.replaceOrThrow(IndexDatabaseHelper.Tables.TABLE_PREFS_INDEX, null, values);
}
}
}