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:
@@ -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.
|
||||
|
@@ -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 + "%";
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
Reference in New Issue
Block a user