Merge "Move static search ranking from DatabaseResultLoader to Search Adapter."
diff --git a/src/com/android/settings/search/DatabaseResultLoader.java b/src/com/android/settings/search/DatabaseResultLoader.java
index 03f5cb4..26bfd52 100644
--- a/src/com/android/settings/search/DatabaseResultLoader.java
+++ b/src/com/android/settings/search/DatabaseResultLoader.java
@@ -25,10 +25,7 @@
 import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.utils.AsyncLoader;
 
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
 import static com.android.settings.search.IndexDatabaseHelper.IndexColumns;
@@ -37,7 +34,7 @@
 /**
  * AsyncTask to retrieve Settings, First party app and any intent based results.
  */
-public class DatabaseResultLoader extends AsyncLoader<List<? extends SearchResult>> {
+public class DatabaseResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
     private static final String LOG = "DatabaseResultLoader";
 
     /* These indices are used to match the columns of the this loader's SELECT statement.
@@ -114,25 +111,22 @@
     }
 
     @Override
-    protected void onDiscardResult(List<? extends SearchResult> result) {
+    protected void onDiscardResult(Set<? extends SearchResult> result) {
         // TODO Search
     }
 
     @Override
-    public List<? extends SearchResult> loadInBackground() {
+    public Set<? extends SearchResult> loadInBackground() {
         if (mQueryText == null || mQueryText.isEmpty()) {
             return null;
         }
 
-        final Set<SearchResult> resultSet = new HashSet<>();
+        final Set<SearchResult> results = new HashSet<>();
 
-        resultSet.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));
-        resultSet.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));
-        resultSet.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));
-        resultSet.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));
-
-        final List<SearchResult> results = new ArrayList<>(resultSet);
-        Collections.sort(results);
+        results.addAll(firstWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[0]));
+        results.addAll(secondaryWordQuery(MATCH_COLUMNS_PRIMARY, BASE_RANKS[1]));
+        results.addAll(anyWordQuery(MATCH_COLUMNS_SECONDARY, BASE_RANKS[2]));
+        results.addAll(anyWordQuery(MATCH_COLUMNS_TERTIARY, BASE_RANKS[3]));
         return results;
     }
 
@@ -159,7 +153,7 @@
      *
      * @param matchColumns The columns to match on
      * @param baseRank The highest rank achievable by these results
-     * @return A list of the matching results.
+     * @return A set of the matching results.
      */
     private Set<SearchResult> firstWordQuery(String[] matchColumns, int baseRank) {
         final String whereClause = buildSingleWordWhereClause(matchColumns);
@@ -175,7 +169,7 @@
      *
      * @param matchColumns The columns to match on
      * @param baseRank The highest rank achievable by these results
-     * @return A list of the matching results.
+     * @return A set of the matching results.
      */
     private Set<SearchResult> secondaryWordQuery(String[] matchColumns, int baseRank) {
         final String whereClause = buildSingleWordWhereClause(matchColumns);
@@ -190,7 +184,7 @@
      *
      * @param matchColumns The columns to match on
      * @param baseRank The highest rank achievable by these results
-     * @return A list of the matching results.
+     * @return A set of the matching results.
      */
     private Set<SearchResult> anyWordQuery(String[] matchColumns, int baseRank) {
         final String whereClause = buildTwoWordWhereClause(matchColumns);
@@ -205,7 +199,7 @@
      * @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.
+     * @return A set of the matching results.
      */
     private Set<SearchResult> query(String whereClause, String[] selection, int baseRank) {
         SQLiteDatabase database = IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();
diff --git a/src/com/android/settings/search/InstalledAppResultLoader.java b/src/com/android/settings/search/InstalledAppResultLoader.java
index 76f3a00..25a3e89 100644
--- a/src/com/android/settings/search/InstalledAppResultLoader.java
+++ b/src/com/android/settings/search/InstalledAppResultLoader.java
@@ -37,14 +37,14 @@
 import com.android.settings.dashboard.SiteMapManager;
 import com.android.settings.utils.AsyncLoader;
 
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Search loader for installed apps.
  */
-public class InstalledAppResultLoader extends AsyncLoader<List<? extends SearchResult>> {
+public class InstalledAppResultLoader extends AsyncLoader<Set<? extends SearchResult>> {
 
     private static final int NAME_NO_MATCH = -1;
     private static final Intent LAUNCHER_PROBE = new Intent(Intent.ACTION_MAIN)
@@ -67,8 +67,8 @@
     }
 
     @Override
-    public List<? extends SearchResult> loadInBackground() {
-        final List<AppSearchResult> results = new ArrayList<>();
+    public Set<? extends SearchResult> loadInBackground() {
+        final Set<AppSearchResult> results = new HashSet<>();
         final PackageManager pm = mPackageManager.getPackageManager();
 
         for (UserInfo user : getUsersToCount()) {
@@ -103,7 +103,6 @@
                 results.add(builder.build());
             }
         }
-        Collections.sort(results);
         return results;
     }
 
@@ -124,7 +123,7 @@
     }
 
     @Override
-    protected void onDiscardResult(List<? extends SearchResult> result) {
+    protected void onDiscardResult(Set<? extends SearchResult> result) {
 
     }
 
diff --git a/src/com/android/settings/search/SearchFragment.java b/src/com/android/settings/search/SearchFragment.java
index e50558b..00eb591 100644
--- a/src/com/android/settings/search/SearchFragment.java
+++ b/src/com/android/settings/search/SearchFragment.java
@@ -44,6 +44,7 @@
 import com.android.settings.overlay.FeatureFactory;
 
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -57,7 +58,7 @@
  * the query if the user has entered text.
  */
 public class SearchFragment extends InstrumentedFragment implements SearchView.OnQueryTextListener,
-        LoaderManager.LoaderCallbacks<List<? extends SearchResult>>, IndexingCallback {
+        LoaderManager.LoaderCallbacks<Set<? extends SearchResult>>, IndexingCallback {
     private static final String TAG = "SearchFragment";
 
     @VisibleForTesting
@@ -251,7 +252,7 @@
     }
 
     @Override
-    public Loader<List<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
+    public Loader<Set<? extends SearchResult>> onCreateLoader(int id, Bundle args) {
         final Activity activity = getActivity();
 
         switch (id) {
@@ -265,8 +266,8 @@
     }
 
     @Override
-    public void onLoadFinished(Loader<List<? extends SearchResult>> loader,
-            List<? extends SearchResult> data) {
+    public void onLoadFinished(Loader<Set<? extends SearchResult>> loader,
+            Set<? extends SearchResult> data) {
         mSearchAdapter.addSearchResults(data, loader.getClass().getName());
         if (mUnfinishedLoadersCount.decrementAndGet() != 0) {
             return;
@@ -284,7 +285,7 @@
     }
 
     @Override
-    public void onLoaderReset(Loader<List<? extends SearchResult>> loader) {
+    public void onLoaderReset(Loader<Set<? extends SearchResult>> loader) {
     }
 
     /**
diff --git a/src/com/android/settings/search/SearchResultsAdapter.java b/src/com/android/settings/search/SearchResultsAdapter.java
index 7861b08..31e0793 100644
--- a/src/com/android/settings/search/SearchResultsAdapter.java
+++ b/src/com/android/settings/search/SearchResultsAdapter.java
@@ -30,15 +30,17 @@
 import com.android.settings.R;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class SearchResultsAdapter extends RecyclerView.Adapter<SearchViewHolder> {
 
     private final SearchFragment mFragment;
 
     private List<SearchResult> mSearchResults;
-    private Map<String, List<? extends SearchResult>> mResultsMap;
+    private Map<String, Set<? extends SearchResult>> mResultsMap;
     private final SearchFeatureProvider mSearchFeatureProvider;
 
     public SearchResultsAdapter(SearchFragment fragment,
@@ -98,7 +100,7 @@
      * @param loaderClassName class name of the loader.
      */
     @MainThread
-    public void addSearchResults(List<? extends SearchResult> results, String loaderClassName) {
+    public void addSearchResults(Set<? extends SearchResult> results, String loaderClassName) {
         if (results == null) {
             return;
         }
@@ -125,12 +127,22 @@
      * @return Number of matched results
      */
     public int displaySearchResults(String query) {
-        final List<? extends SearchResult> databaseResults = mResultsMap
-                .get(DatabaseResultLoader.class.getName());
-        final List<? extends SearchResult> installedAppResults = mResultsMap
-                .get(InstalledAppResultLoader.class.getName());
-        final int dbSize = (databaseResults != null) ? databaseResults.size() : 0;
-        final int appSize = (installedAppResults != null) ? installedAppResults.size() : 0;
+        List<? extends SearchResult> databaseResults = null;
+        List<? extends SearchResult> installedAppResults = null;
+        final String dbLoaderKey = DatabaseResultLoader.class.getName();
+        final String appLoaderKey = InstalledAppResultLoader.class.getName();
+        int dbSize = 0;
+        int appSize = 0;
+        if (mResultsMap.containsKey(dbLoaderKey)) {
+            databaseResults = new ArrayList<>(mResultsMap.get(dbLoaderKey));
+            dbSize = databaseResults.size();
+            Collections.sort(databaseResults);
+        }
+        if (mResultsMap.containsKey(appLoaderKey)) {
+            installedAppResults = new ArrayList<>(mResultsMap.get(appLoaderKey));
+            appSize = installedAppResults.size();
+            Collections.sort(installedAppResults);
+        }
         final List<SearchResult> newResults = new ArrayList<>(dbSize + appSize);
 
         int dbIndex = 0;
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
index bcd3371..3d38a27 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
@@ -45,7 +45,10 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Matchers.anyString;
@@ -224,17 +227,19 @@
     }
 
     @Test
-    public void testSpecialCaseTwoWords_firstWordMatches_ranksHigher() {
+    public void testSpecialCaseTwoWords_multipleResults() {
         final String caseOne = "Apple pear";
         final String caseTwo = "Banana apple";
         insertSpecialCase(caseOne);
         insertSpecialCase(caseTwo);
         DatabaseResultLoader 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);
+        Set<? extends SearchResult> results = loader.loadInBackground();
+        Set<CharSequence> expectedTitles = new HashSet<>(Arrays.asList(caseOne, caseTwo));
+        Set<CharSequence> actualTitles = new HashSet<>();
+        for (SearchResult result : results) {
+            actualTitles.add(result.title);
+        }
+        assertThat(actualTitles).isEqualTo(expectedTitles);
     }
 
     private void insertSpecialCase(String specialCase) {
diff --git a/tests/robotests/src/com/android/settings/search/InstalledAppResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/InstalledAppResultLoaderTest.java
index e167091..1a30157 100644
--- a/tests/robotests/src/com/android/settings/search/InstalledAppResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/InstalledAppResultLoaderTest.java
@@ -43,7 +43,9 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
 import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
@@ -185,18 +187,19 @@
     }
 
     @Test
-    public void query_matchingQuery_shouldRankBasedOnSimilarity() {
+    public void query_matchingQuery_multipleResults() {
         final String query = "app";
 
         mLoader = new InstalledAppResultLoader(mContext, mPackageManagerWrapper, query,
                 mSiteMapManager);
-        final List<? extends SearchResult> results = mLoader.loadInBackground();
+        final Set<? extends SearchResult> results = mLoader.loadInBackground();
 
-        // List is sorted by rank
-        assertThat(results.get(0).rank).isAtMost(results.get(1).rank);
-        assertThat(results.get(0).title).isEqualTo("app4");
-        assertThat(results.get(1).title).isEqualTo("app");
-        assertThat(results.get(2).title).isEqualTo("appBuffer");
+        Set<CharSequence> expectedTitles = new HashSet<>(Arrays.asList("app4", "app", "appBuffer"));
+        Set<CharSequence> actualTitles = new HashSet<>();
+        for (SearchResult result : results) {
+            actualTitles.add(result.title);
+        }
+        assertThat(actualTitles).isEqualTo(expectedTitles);
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/search/MockAppLoader.java b/tests/robotests/src/com/android/settings/search/MockAppLoader.java
index 35e56a1..c68cbdf 100644
--- a/tests/robotests/src/com/android/settings/search/MockAppLoader.java
+++ b/tests/robotests/src/com/android/settings/search/MockAppLoader.java
@@ -21,8 +21,8 @@
 import com.android.settings.search.InstalledAppResultLoader;
 import com.android.settings.search.SearchResult;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Mock loader to subvert the requirements of returning data while also driving the Loader
@@ -35,12 +35,12 @@
     }
 
     @Override
-    public List<? extends SearchResult> loadInBackground() {
-        return new ArrayList<>();
+    public Set<? extends SearchResult> loadInBackground() {
+        return new HashSet<>();
     }
 
     @Override
-    protected void onDiscardResult(List<? extends SearchResult> result) {
+    protected void onDiscardResult(Set<? extends SearchResult> result) {
 
     }
 }
diff --git a/tests/robotests/src/com/android/settings/search/MockDBLoader.java b/tests/robotests/src/com/android/settings/search/MockDBLoader.java
index 562e375..b28c1ed 100644
--- a/tests/robotests/src/com/android/settings/search/MockDBLoader.java
+++ b/tests/robotests/src/com/android/settings/search/MockDBLoader.java
@@ -21,8 +21,8 @@
 import com.android.settings.search.DatabaseResultLoader;
 import com.android.settings.search.SearchResult;
 
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Mock loader to subvert the requirements of returning data while also driving the Loader
@@ -35,12 +35,12 @@
     }
 
     @Override
-    public List<? extends SearchResult> loadInBackground() {
-        return new ArrayList<>();
+    public Set<? extends SearchResult> loadInBackground() {
+        return new HashSet<>();
     }
 
     @Override
-    protected void onDiscardResult(List<? extends SearchResult> result) {
+    protected void onDiscardResult(Set<? extends SearchResult> result) {
 
     }
 }
diff --git a/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java
index 94dc233..b7154ce 100644
--- a/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchFragmentTest.java
@@ -41,7 +41,7 @@
 import org.robolectric.util.ActivityController;
 import org.robolectric.util.ReflectionHelpers;
 
-import java.util.List;
+import java.util.Set;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.Matchers.any;
@@ -249,7 +249,7 @@
 
         Robolectric.flushForegroundThreadScheduler();
 
-        verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(List.class));
+        verify(fragment, times(2)).onLoadFinished(any(Loader.class), any(Set.class));
     }
 
     @Test
diff --git a/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java b/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java
index 5af8550..8290348 100644
--- a/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchResultsAdapterTest.java
@@ -46,8 +46,10 @@
 import static org.mockito.Mockito.when;
 
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -85,7 +87,7 @@
 
     @Test
     public void testSingleSourceMerge_exactCopyReturned() {
-        ArrayList<SearchResult> intentResults = getIntentSampleResults();
+        Set<SearchResult> intentResults = getIntentSampleResults();
         mAdapter.addSearchResults(intentResults, mLoaderClassName);
         mAdapter.displaySearchResults("");
 
@@ -111,8 +113,10 @@
 
     @Test
     public void testEndToEndSearch_properResultsMerged_correctOrder() {
-        mAdapter.addSearchResults(getDummyAppResults(), InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(getDummyDbResults(), DatabaseResultLoader.class.getName());
+        mAdapter.addSearchResults(new HashSet<SearchResult>(getDummyAppResults()),
+                InstalledAppResultLoader.class.getName());
+        mAdapter.addSearchResults(new HashSet<SearchResult>(getDummyDbResults()),
+                DatabaseResultLoader.class.getName());
         int count = mAdapter.displaySearchResults("");
 
         List<SearchResult> results = mAdapter.getSearchResults();
@@ -130,13 +134,16 @@
         List<AppSearchResult> appResults = getDummyAppResults();
         List<SearchResult> dbResults = getDummyDbResults();
         // Add two individual items
-        mAdapter.addSearchResults(appResults.subList(0,1),
+        mAdapter.addSearchResults(new HashSet<SearchResult>(appResults.subList(0, 1)),
                 InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(dbResults.subList(0,1), DatabaseResultLoader.class.getName());
+        mAdapter.addSearchResults(new HashSet<SearchResult>(dbResults.subList(0, 1)),
+                DatabaseResultLoader.class.getName());
         mAdapter.displaySearchResults("");
         // Add super-set of items
-        mAdapter.addSearchResults(appResults, InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(dbResults, DatabaseResultLoader.class.getName());
+        mAdapter.addSearchResults(
+                new HashSet<SearchResult>(appResults), InstalledAppResultLoader.class.getName());
+        mAdapter.addSearchResults(
+                new HashSet<SearchResult>(dbResults), DatabaseResultLoader.class.getName());
         int count = mAdapter.displaySearchResults("");
 
         List<SearchResult> results = mAdapter.getSearchResults();
@@ -170,13 +177,16 @@
         List<AppSearchResult> appResults = getDummyAppResults();
         List<SearchResult> dbResults = getDummyDbResults();
         // Add list of items
-        mAdapter.addSearchResults(appResults, InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(dbResults, DatabaseResultLoader.class.getName());
+        mAdapter.addSearchResults(new HashSet<SearchResult>(appResults),
+                InstalledAppResultLoader.class.getName());
+        mAdapter.addSearchResults(new HashSet<SearchResult>(dbResults),
+                DatabaseResultLoader.class.getName());
         mAdapter.displaySearchResults("");
         // Add subset of items
-        mAdapter.addSearchResults(appResults.subList(0,1),
+        mAdapter.addSearchResults(new HashSet<SearchResult>(appResults.subList(0, 1)),
                 InstalledAppResultLoader.class.getName());
-        mAdapter.addSearchResults(dbResults.subList(0,1), DatabaseResultLoader.class.getName());
+        mAdapter.addSearchResults(new HashSet<>(dbResults.subList(0, 1)),
+                DatabaseResultLoader.class.getName());
         int count = mAdapter.displaySearchResults("");
 
         List<SearchResult> results = mAdapter.getSearchResults();
@@ -231,8 +241,8 @@
         return results;
     }
 
-    private ArrayList<SearchResult> getIntentSampleResults() {
-        ArrayList<SearchResult> sampleResults = new ArrayList<>();
+    private Set<SearchResult> getIntentSampleResults() {
+        Set<SearchResult> sampleResults = new HashSet<>();
         ArrayList<String> breadcrumbs = new ArrayList<>();
         final Drawable icon = mContext.getDrawable(R.drawable.ic_search_history);
         final ResultPayload payload = new ResultPayload(null);