Handle zero-query case for global search.

When a zero-query request is made, return applications with a non-zero
launch count. This is used to show often used applications in QSB on
first ever launch.

Bug: 4369681
Change-Id: Ie4820e5bd278b6f9607acbada8367a1f9f2cb320
diff --git a/src/com/android/providers/applications/ApplicationsProvider.java b/src/com/android/providers/applications/ApplicationsProvider.java
index e46aa52..d6a4f00 100644
--- a/src/com/android/providers/applications/ApplicationsProvider.java
+++ b/src/com/android/providers/applications/ApplicationsProvider.java
@@ -39,9 +39,7 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteQueryBuilder;
-import android.database.sqlite.SQLiteStatement;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -52,7 +50,6 @@
 import android.util.Log;
 
 import java.lang.Runnable;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -375,8 +372,9 @@
     }
 
     private Cursor getSuggestions(String query, String[] projectionIn) {
-        // No zero-query suggestions
-        if (TextUtils.isEmpty(query)) {
+        // No zero-query suggestions except for global search, to avoid leaking info about apps
+        // that have been used.
+        if (TextUtils.isEmpty(query) && !canRankByLaunchCount()) {
             return null;
         }
         return searchApplications(query, projectionIn, sSearchSuggestionsProjectionMap);
@@ -409,34 +407,41 @@
 
     private Cursor searchApplications(String query, String[] projectionIn,
             Map<String, String> columnMap) {
+        final boolean zeroQuery = TextUtils.isEmpty(query);
         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
         qb.setTables(APPLICATIONS_LOOKUP_JOIN);
         qb.setProjectionMap(columnMap);
-        if (!TextUtils.isEmpty(query)) {
+        if (!zeroQuery) {
             qb.appendWhere(buildTokenFilter(query));
+        } else {
+            if (canRankByLaunchCount()) {
+                qb.appendWhere(LAUNCH_COUNT + " > 0");
+            }
         }
         // don't return duplicates when there are two matching tokens for an app
         String groupBy = APPLICATIONS_TABLE + "." + _ID;
-        String orderBy = getOrderBy();
+        String orderBy = getOrderBy(zeroQuery);
         Cursor cursor = qb.query(mDb, projectionIn, null, null, groupBy, null, orderBy);
         if (DBG) Log.d(TAG, "Returning " + cursor.getCount() + " results for " + query);
         return cursor;
     }
 
-    private String getOrderBy() {
+    private String getOrderBy(boolean zeroQuery) {
         // order first by whether it a full prefix match, then by launch
         // count (if allowed, frequently used apps rank higher), then name
         // MIN(token_index) != 0 is true for non-full prefix matches,
         // and since false (0) < true(1), this expression makes sure
         // that full prefix matches come first.
         StringBuilder orderBy = new StringBuilder();
-        orderBy.append("MIN(token_index) != 0");
-
-        if (canRankByLaunchCount()) {
-            orderBy.append(", " + LAUNCH_COUNT + " DESC");
+        if (!zeroQuery) {
+            orderBy.append("MIN(token_index) != 0, ");
         }
 
-        orderBy.append(", " + NAME);
+        if (canRankByLaunchCount()) {
+            orderBy.append(LAUNCH_COUNT + " DESC, ");
+        }
+
+        orderBy.append(NAME);
 
         return orderBy.toString();
     }