Loading src/com/android/settings/search2/CursorToSearchResultConverter.java +1 −2 Original line number Diff line number Diff line Loading @@ -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. Loading src/com/android/settings/search2/DatabaseResultLoader.java +123 −25 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading @@ -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++) { Loading @@ -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. * Loading @@ -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 + "%"; Loading src/com/android/settings/search2/InstalledAppResultLoader.java +2 −2 Original line number Diff line number Diff line Loading @@ -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; } } src/com/android/settings/search2/SearchResult.java +1 −1 Original line number Diff line number Diff line Loading @@ -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. Loading tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java→tests/robotests/src/com/android/settings/search2/DatabaseResultLoaderTest.java +20 −5 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ * */ package com.android.settings.search; package com.android.settings.search2; import android.content.ContentValues; import android.content.Context; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading Loading
src/com/android/settings/search2/CursorToSearchResultConverter.java +1 −2 Original line number Diff line number Diff line Loading @@ -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. Loading
src/com/android/settings/search2/DatabaseResultLoader.java +123 −25 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading @@ -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 Loading @@ -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++) { Loading @@ -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. * Loading @@ -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 + "%"; Loading
src/com/android/settings/search2/InstalledAppResultLoader.java +2 −2 Original line number Diff line number Diff line Loading @@ -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; } }
src/com/android/settings/search2/SearchResult.java +1 −1 Original line number Diff line number Diff line Loading @@ -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. Loading
tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java→tests/robotests/src/com/android/settings/search2/DatabaseResultLoaderTest.java +20 −5 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ * */ package com.android.settings.search; package com.android.settings.search2; import android.content.ContentValues; import android.content.Context; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading