OSDN Git Service

Rank results that match the prefix of the first word higher
authorMatthew Fritze <mfritze@google.com>
Tue, 14 Feb 2017 01:45:46 +0000 (17:45 -0800)
committerMatthew Fritze <mfritze@google.com>
Sat, 18 Feb 2017 22:22:29 +0000 (22:22 +0000)
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

src/com/android/settings/search2/CursorToSearchResultConverter.java
src/com/android/settings/search2/DatabaseResultLoader.java
src/com/android/settings/search2/InstalledAppResultLoader.java
src/com/android/settings/search2/SearchResult.java
tests/robotests/src/com/android/settings/search2/DatabaseResultLoaderTest.java [moved from tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java with 95% similarity]

index bcb2e9d..880a65b 100644 (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.
index 854b8dd..b7f5dd7 100644 (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,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++) {
@@ -185,13 +269,27 @@ public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResul
     }
 
     /**
+     * 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.
      *
      * @param size is twice the number of columns to be matched. The first match is for the prefix
      *             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;
     }
-}
+}
\ No newline at end of file
index c6a1b82..73cfc81 100644 (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;
     }
 }
index 37559aa..a82c47f 100644 (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.
@@ -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);
     }
-}
+}
\ No newline at end of file