Put 3rd party sources last in searchable items settings

Fixes http://b/issue?id=2085216
(3rd party suggestion sources can appear before built-in sources in search settings)

Change-Id: Ib7f6961ed5becd8963e9f7415dd328b24c8deabb
diff --git a/src/com/android/globalsearch/SessionManager.java b/src/com/android/globalsearch/SessionManager.java
index 5faa58b..a02ce81 100644
--- a/src/com/android/globalsearch/SessionManager.java
+++ b/src/com/android/globalsearch/SessionManager.java
@@ -120,7 +120,7 @@
 
         Sources sources = orderSources(
                 mSources.getEnabledSuggestionSources(),
-                webSearchSource,
+                mSources,
                 mShortcutRepo.getSourceRanking(),
                 SuggestionSession.NUM_PROMOTED_SOURCES);
 
@@ -182,14 +182,14 @@
      * Quick search box.
      *
      * @param enabledSources The enabled sources.
-     * @param webSearchSource The name of the web search source, or <code>null</code> otherwise.
      * @param sourceRanking The order the sources should be in.
+     * @param sourceLookup For getting the web search source and trusted sources.
      * @param numPromoted  The number of promoted sources.
      * @return The order of the promotable and non-promotable sources.
      */
     static Sources orderSources(
             List<SuggestionSource> enabledSources,
-            SuggestionSource webSearchSource,
+            SourceLookup sourceLookup,
             ArrayList<ComponentName> sourceRanking,
             int numPromoted) {
 
@@ -208,6 +208,7 @@
         final HashSet<ComponentName> allRanked = new HashSet<ComponentName>(sourceRanking);
 
         // start with the web source if it exists
+        SuggestionSource webSearchSource = sourceLookup.getSelectedWebSearchSource();
         if (webSearchSource != null) {
             if (DBG) Log.d(TAG, "Adding web search source: " + webSearchSource);
             sources.add(webSearchSource, true);
@@ -230,17 +231,22 @@
             SuggestionSource source = sourceIterator.next();
             if (!allRanked.contains(source.getComponentName())) {
                 if (DBG) Log.d(TAG, "Adding unranked source: " + source);
-                sources.add(source, false);
+                // To fix the empty room problem, we allow a small set of system apps
+                // to start putting their results in the promoted list before we
+                // have enough data to pick the high ranking ones.
+                sources.add(source, sourceLookup.isTrustedSource(source));
                 sourceIterator.remove();
             }
         }
 
-        // finally, add any remaining ranked to mUnpromotableSources
+        // finally, add any remaining ranked
         for (int i = nextRanked; i < numRanked; i++) {
             final ComponentName ranked = sourceRanking.get(i);
             final SuggestionSource source = linkMap.get(ranked);
-            if (DBG) Log.d(TAG, "Adding ranked source: (" + ranked + ") " + source);
-            sources.add(source, false);
+            if (source != null) {
+                if (DBG) Log.d(TAG, "Adding ranked source: (" + ranked + ") " + source);
+                sources.add(source, sourceLookup.isTrustedSource(source));
+            }
         }
 
         if (DBG) Log.d(TAG, "Promotable sources: " + sources.mPromotableSources);
@@ -258,7 +264,7 @@
         }
         public void add(SuggestionSource source, boolean forcePromotable) {
             if (source == null) return;
-            if (forcePromotable || promotableWhenInsufficientRankingInfo(source)) {
+            if (forcePromotable) {
                 if (DBG) Log.d(TAG, "  Promotable: " + source);
                 mPromotableSources.add(source);
             } else {
@@ -267,15 +273,4 @@
             }
         }
     }
-
-    /**
-     * To fix the empty room problem, we allow a small set of system apps to start putting their
-     * results in the promoted list before we have enough data to pick the high ranking ones.
-     */
-    private static boolean promotableWhenInsufficientRankingInfo(SuggestionSource source) {
-        final String packageName = source.getComponentName().getPackageName();
-        return "com.android.contacts".equals(packageName) ||
-                "com.android.browser".equals(packageName) ||
-                "com.android.providers.applications".equals(packageName);
-    }
 }
diff --git a/src/com/android/globalsearch/SourceLookup.java b/src/com/android/globalsearch/SourceLookup.java
index 6cdc01a..6e85f79 100644
--- a/src/com/android/globalsearch/SourceLookup.java
+++ b/src/com/android/globalsearch/SourceLookup.java
@@ -36,4 +36,9 @@
      */
     SuggestionSource getSelectedWebSearchSource();
 
+    /**
+     * Checks if we trust the given source not to be spammy.
+     */
+    boolean isTrustedSource(SuggestionSource source);
+
 }
diff --git a/src/com/android/globalsearch/SuggestionSources.java b/src/com/android/globalsearch/SuggestionSources.java
index 9c72db4..18956bd 100644
--- a/src/com/android/globalsearch/SuggestionSources.java
+++ b/src/com/android/globalsearch/SuggestionSources.java
@@ -33,6 +33,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 
 /**
@@ -56,6 +57,7 @@
     private Context mContext;
     private SearchManager mSearchManager;
     private SharedPreferences mPreferences;
+    private HashSet<String> mTrustedPackages;
     private boolean mLoaded;
 
     // All available suggestion sources.
@@ -174,6 +176,8 @@
             return;
         }
 
+        loadTrustedPackages();
+
         // Listen for searchables changes.
         mContext.registerReceiver(mBroadcastReceiver,
                 new IntentFilter(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED));
@@ -212,6 +216,14 @@
         mLoaded = false;
     }
 
+    // TODO: should get this form a resource file, to allow vendor overlays
+    private void loadTrustedPackages() {
+        mTrustedPackages = new HashSet<String>();
+        mTrustedPackages.add("com.android.contacts");
+        mTrustedPackages.add("com.android.browser");
+        mTrustedPackages.add("com.android.providers.applications");
+    }
+
     /**
      * Loads the list of suggestion sources. This method is package private so that
      * it can be called efficiently from inner classes.
@@ -224,10 +236,22 @@
         mSelectedWebSearchSource = findWebSearchSource();
     }
 
-    private void addExternalSources()  {
+    private void addExternalSources() {
+        ArrayList<SuggestionSource> trusted = new ArrayList<SuggestionSource>();
+        ArrayList<SuggestionSource> untrusted = new ArrayList<SuggestionSource>();
         for (SearchableInfo searchable : mSearchManager.getSearchablesInGlobalSearch()) {
             SuggestionSource source = new SearchableSuggestionSource(mContext, searchable);
-            addSuggestionSource(source);
+            if (isTrustedSource(source)) {
+                trusted.add(source);
+            } else {
+                untrusted.add(source);
+            }
+        }
+        for (SuggestionSource s : trusted) {
+            addSuggestionSource(s);
+        }
+        for (SuggestionSource s : untrusted) {
+            addSuggestionSource(s);
         }
     }
 
@@ -263,6 +287,12 @@
         return mPreferences.getBoolean(sourceEnabledPref, defaultEnabled);
     }
 
+    public boolean isTrustedSource(SuggestionSource source) {
+        if (source == null) return false;
+        final String packageName = source.getComponentName().getPackageName();
+        return mTrustedPackages != null && mTrustedPackages.contains(packageName);
+    }
+
     /**
      * Finds the selected web search source.
      */
diff --git a/tests/src/com/android/globalsearch/SessionManagerTest.java b/tests/src/com/android/globalsearch/SessionManagerTest.java
index 60ac249..55eab2f 100644
--- a/tests/src/com/android/globalsearch/SessionManagerTest.java
+++ b/tests/src/com/android/globalsearch/SessionManagerTest.java
@@ -53,8 +53,9 @@
     static final ComponentName F =
             new ComponentName("com.android.contacts","com.example.F");
 
+    private SimpleSourceLookup mSourceLookup;
 
-    private List<SuggestionSource> mAllSuggestionSources;
+    private ArrayList<SuggestionSource> mAllSuggestionSources;
 
 
     @Override
@@ -64,6 +65,8 @@
         mAllSuggestionSources = Lists.newArrayList(
                 makeSource(B), makeSource(C), makeSource(D),
                 makeSource(E), makeSource(F));
+
+        mSourceLookup = new SimpleSourceLookup(mAllSuggestionSources, makeSource(WEB));
     }
 
     private SuggestionSource makeSource(ComponentName componentName) {
@@ -73,7 +76,7 @@
     public void testOrderSources_onlyIncludeEnabled() {
         SessionManager.Sources sources1 = SessionManager.orderSources(
                 Lists.newArrayList(makeSource(B)),
-                makeSource(WEB),
+                mSourceLookup,
                 Lists.newArrayList(C, D, WEB), // ranking
                 3);
         assertContentsInOrder(
@@ -83,7 +86,7 @@
 
         SessionManager.Sources sources2 = SessionManager.orderSources(
                 Lists.newArrayList(makeSource(B)),
-                makeSource(WEB),
+                mSourceLookup,
                 Lists.newArrayList(C, B, WEB), // ranking
                 3);
 
@@ -97,7 +100,7 @@
     public void testOrderSources_webAlwaysFirst() {
         SessionManager.Sources sources = SessionManager.orderSources(
                 mAllSuggestionSources,
-                makeSource(WEB),
+                mSourceLookup,
                 Lists.newArrayList(C, D, WEB), // ranking
                 3);
 
@@ -115,7 +118,7 @@
     public void testOrderSources_unRankedAfterPromoted() {
         SessionManager.Sources sources = SessionManager.orderSources(
                 mAllSuggestionSources,
-                makeSource(WEB),
+                mSourceLookup,
                 Lists.newArrayList(C, D, WEB, B), // ranking
                 3);
         assertContentsInOrder(
diff --git a/tests/src/com/android/globalsearch/SimpleSourceLookup.java b/tests/src/com/android/globalsearch/SimpleSourceLookup.java
new file mode 100644
index 0000000..3226dc6
--- /dev/null
+++ b/tests/src/com/android/globalsearch/SimpleSourceLookup.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.globalsearch;
+
+import android.content.ComponentName;
+
+import java.util.ArrayList;
+
+/**
+ * Simple mock implementation of SourceLookup.
+ */
+public class SimpleSourceLookup implements SourceLookup {
+    private final ArrayList<SuggestionSource> mSources;
+    private final SuggestionSource mWebSource;
+
+    public SimpleSourceLookup(ArrayList<SuggestionSource> sources, SuggestionSource webSource) {
+        mSources = sources;
+        mWebSource = webSource;
+    }
+
+    public SuggestionSource getSourceByComponentName(ComponentName componentName) {
+        for (SuggestionSource source : mSources) {
+            if (componentName.equals(source.getComponentName())) {
+                return source;
+            }
+        }
+        return null;
+    }
+
+    public SuggestionSource getSelectedWebSearchSource() {
+        return mWebSource;
+    }
+
+    public boolean isTrustedSource(SuggestionSource source) {
+        final String packageName = source.getComponentName().getPackageName();
+        return "com.android.contacts".equals(packageName)
+                || "com.android.browser".equals(packageName)
+                || "com.android.providers.applications".equals(packageName);
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/globalsearch/SuggestionSessionTest.java b/tests/src/com/android/globalsearch/SuggestionSessionTest.java
index 88f700f..6e5ea60 100644
--- a/tests/src/com/android/globalsearch/SuggestionSessionTest.java
+++ b/tests/src/com/android/globalsearch/SuggestionSessionTest.java
@@ -633,29 +633,6 @@
         }
     }
 
-    static class SimpleSourceLookup implements SourceLookup {
-        private final ArrayList<SuggestionSource> mSources;
-        private final SuggestionSource mWebSource;
-
-        public SimpleSourceLookup(ArrayList<SuggestionSource> sources, SuggestionSource webSource) {
-            mSources = sources;
-            mWebSource = webSource;
-        }
-
-        public SuggestionSource getSourceByComponentName(ComponentName componentName) {
-            for (SuggestionSource source : mSources) {
-                if (componentName.equals(source.getComponentName())) {
-                    return source;
-                }
-            }
-            return null;
-        }
-
-        public SuggestionSource getSelectedWebSearchSource() {
-            return mWebSource;
-        }
-    }
-
     static class TestSuggestionSession extends SuggestionSession {
         private final QueryEngine mEngine;