Handle tap on intent based search results.

Also fix how icon is loaded. IconResId is specific to the package of the
indexed result. If result comes from external app, icon needs to be
decoded against the external app's package context.

Bug: 33432310
Test: RunSettingsRoboTests
Change-Id: Ia0c53e63be757405dfaeceb2d865e7d8de87c5ee
diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java
index a4e614f..b268f6a 100644
--- a/src/com/android/settings/search2/DatabaseResultLoader.java
+++ b/src/com/android/settings/search2/DatabaseResultLoader.java
@@ -16,32 +16,37 @@
 
 package com.android.settings.search2;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Log;
 
-import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.Utils;
 import com.android.settings.search.Index;
 import com.android.settings.search.IndexDatabaseHelper;
 import com.android.settings.utils.AsyncLoader;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
-
-import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
-import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
-import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
+import java.util.Map;
 
 
 /**
  * AsyncTask to retrieve Settings, First party app and any intent based results.
  */
 public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
+    private static final String LOG = "DatabaseResultLoader";
     private final String mQueryText;
     private final Context mContext;
     protected final SQLiteDatabase mDatabase;
@@ -65,7 +70,7 @@
         }
 
         String query = getSQLQuery();
-        Cursor result  = mDatabase.rawQuery(query, null);
+        Cursor result = mDatabase.rawQuery(query, null);
 
         return parseCursorForSearch(result);
     }
@@ -78,10 +83,12 @@
 
     protected String getSQLQuery() {
         return String.format("SELECT data_rank, data_title, data_summary_on, " +
-                "data_summary_off, data_entries, data_keywords, class_name, screen_title, icon, " +
-                "intent_action, intent_target_package, intent_target_class, enabled, " +
-                "data_key_reference FROM prefs_index WHERE prefs_index MATCH 'data_title:%s* " +
-                "OR data_title_normalized:%s* OR data_keywords:%s*' AND locale = 'en_US'",
+                        "data_summary_off, data_entries, data_keywords, class_name, screen_title,"
+                        + " icon, " +
+                        "intent_action, intent_target_package, intent_target_class, enabled, " +
+                        "data_key_reference FROM prefs_index WHERE prefs_index MATCH "
+                        + "'data_title:%s* " +
+                        "OR data_title_normalized:%s* OR data_keywords:%s*' AND locale = 'en_US'",
                 mQueryText, mQueryText, mQueryText);
     }
 
@@ -90,35 +97,89 @@
         if (cursorResults == null) {
             return null;
         }
+        final Map<String, Context> contextMap = new HashMap<>();
         final ArrayList<SearchResult> results = new ArrayList<>();
 
         while (cursorResults.moveToNext()) {
-            final String title = cursorResults.getString(Index.COLUMN_INDEX_TITLE);
-            final String summaryOn = cursorResults.getString(COLUMN_INDEX_RAW_SUMMARY_ON);
-            final ArrayList<String> breadcrumbs = new ArrayList<>();
-            final int rank = cursorResults.getInt(COLUMN_INDEX_XML_RES_RANK);
-
-            final String intentString = cursorResults.getString(Index.COLUMN_INDEX_INTENT_ACTION);
-            final IntentPayload intentPayload = new IntentPayload(new Intent(intentString));
-            final int iconID = cursorResults.getInt(COLUMN_INDEX_RAW_ICON_RESID);
-            Drawable icon;
-            try {
-                icon = mContext.getDrawable(iconID);
-            } catch (Resources.NotFoundException nfe) {
-                icon = mContext.getDrawable(R.drawable.ic_search_history);
+            SearchResult result = buildSingleSearchResultFromCursor(contextMap, cursorResults);
+            if (result != null) {
+                results.add(result);
             }
-
-            SearchResult.Builder builder = new SearchResult.Builder();
-            builder.addTitle(title)
-                    .addSummary(summaryOn)
-                    .addBreadcrumbs(breadcrumbs)
-                    .addRank(rank)
-                    .addIcon(icon)
-                    .addPayload(intentPayload);
-            results.add(builder.build());
         }
         Collections.sort(results);
         return results;
     }
 
+    private SearchResult buildSingleSearchResultFromCursor(Map<String, Context> contextMap,
+            Cursor cursor) {
+        final String pkgName = cursor.getString(Index.COLUMN_INDEX_INTENT_ACTION_TARGET_PACKAGE);
+        final String action = cursor.getString(Index.COLUMN_INDEX_INTENT_ACTION);
+        final String title = cursor.getString(Index.COLUMN_INDEX_TITLE);
+        final String summaryOn = cursor.getString(Index.COLUMN_INDEX_SUMMARY_ON);
+        final String className = cursor.getString(Index.COLUMN_INDEX_CLASS_NAME);
+        final int rank = cursor.getInt(Index.COLUMN_INDEX_RANK);
+        final String key = cursor.getString(Index.COLUMN_INDEX_KEY);
+        final String iconResStr = cursor.getString(Index.COLUMN_INDEX_ICON);
+
+        final ResultPayload payload;
+        if (TextUtils.isEmpty(action)) {
+            final String screenTitle = cursor.getString(Index.COLUMN_INDEX_SCREEN_TITLE);
+            // Action is null, we will launch it as a sub-setting
+            final Bundle args = new Bundle();
+            args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);
+            final Intent intent = Utils.onBuildStartFragmentIntent(mContext,
+                    className, args, null, 0, screenTitle, false);
+            payload = new IntentPayload(intent);
+        } else {
+            final Intent intent = new Intent(action);
+            final String targetClass = cursor.getString(
+                    Index.COLUMN_INDEX_INTENT_ACTION_TARGET_CLASS);
+            if (!TextUtils.isEmpty(pkgName) && !TextUtils.isEmpty(targetClass)) {
+                final ComponentName component = new ComponentName(pkgName, targetClass);
+                intent.setComponent(component);
+            }
+            intent.putExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, key);
+            payload = new IntentPayload(intent);
+        }
+        SearchResult.Builder builder = new SearchResult.Builder();
+        builder.addTitle(title)
+                .addSummary(summaryOn)
+                .addRank(rank)
+                .addIcon(getIconForPackage(contextMap, pkgName, className, iconResStr))
+                .addPayload(payload);
+        return builder.build();
+    }
+
+    private Drawable getIconForPackage(Map<String, Context> contextMap, String pkgName,
+            String className, String iconResStr) {
+        final int iconId = TextUtils.isEmpty(iconResStr)
+                ? 0 : Integer.parseInt(iconResStr);
+        Drawable icon;
+        Context packageContext;
+        if (iconId == 0) {
+            icon = null;
+        } else {
+            if (TextUtils.isEmpty(className) && !TextUtils.isEmpty(pkgName)) {
+                packageContext = contextMap.get(pkgName);
+                if (packageContext == null) {
+                    try {
+                        packageContext = mContext.createPackageContext(pkgName, 0);
+                    } catch (PackageManager.NameNotFoundException e) {
+                        Log.e(LOG, "Cannot create Context for package: " + pkgName);
+                        return null;
+                    }
+                    contextMap.put(pkgName, packageContext);
+                }
+            } else {
+                packageContext = mContext;
+            }
+            try {
+                icon = packageContext.getDrawable(iconId);
+            } catch (Resources.NotFoundException nfe) {
+                icon = null;
+            }
+        }
+        return icon;
+    }
+
 }
diff --git a/src/com/android/settings/search2/IntentSearchViewHolder.java b/src/com/android/settings/search2/IntentSearchViewHolder.java
index 0187c1c..0ef27d0 100644
--- a/src/com/android/settings/search2/IntentSearchViewHolder.java
+++ b/src/com/android/settings/search2/IntentSearchViewHolder.java
@@ -44,6 +44,9 @@
         titleView.setText(result.title);
         summaryView.setText(result.summary);
         iconView.setImageDrawable(result.icon);
+        if (result.icon == null) {
+            iconView.setBackgroundResource(R.drawable.empty_icon);
+        }
         itemView.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
diff --git a/src/com/android/settings/search2/ResultPayload.java b/src/com/android/settings/search2/ResultPayload.java
index 3a4e477..84df7b6 100644
--- a/src/com/android/settings/search2/ResultPayload.java
+++ b/src/com/android/settings/search2/ResultPayload.java
@@ -31,8 +31,19 @@
     @IntDef({PayloadType.INLINE_SLIDER, PayloadType.INLINE_SWITCH, PayloadType.INTENT})
     @Retention(RetentionPolicy.SOURCE)
     public @interface PayloadType {
+        /**
+         * Resulting page will be started using an intent
+         */
         int INTENT = 0;
+
+        /**
+         * Result is a inline widget, using a slider widget as UI.
+         */
         int INLINE_SLIDER = 1;
+
+        /**
+         * Result is a inline widget, using a toggle widget as UI.
+         */
         int INLINE_SWITCH = 2;
     }
 
diff --git a/src/com/android/settings/search2/SearchResult.java b/src/com/android/settings/search2/SearchResult.java
index 9fb250f..5bf757f 100644
--- a/src/com/android/settings/search2/SearchResult.java
+++ b/src/com/android/settings/search2/SearchResult.java
@@ -83,7 +83,6 @@
         payload = builder.mResultPayload;
         viewType = payload.getType();
         stableId = Objects.hash(title, summary, breadcrumbs, rank, icon, payload, viewType);
-
     }
 
     @Override
@@ -98,7 +97,7 @@
         protected CharSequence mTitle;
         protected CharSequence mSummary;
         protected ArrayList<String> mBreadcrumbs;
-        protected int mRank = -1;
+        protected int mRank = 42;
         protected ResultPayload mResultPayload;
         protected Drawable mIcon;
 
@@ -118,10 +117,9 @@
         }
 
         public Builder addRank(int rank) {
-            if (rank < 0 || rank > 9) {
-                rank = 42;
+            if (rank >= 0 && rank <= 9) {
+                mRank = rank;
             }
-            mRank = rank;
             return this;
         }
 
@@ -139,10 +137,6 @@
             // Check that all of the mandatory fields are set.
             if (mTitle == null) {
                 throw new IllegalArgumentException("SearchResult missing title argument");
-            } else if (mRank == -1) {
-                throw new IllegalArgumentException("SearchResult missing rank argument");
-            } else if (mIcon == null) {
-                throw new IllegalArgumentException("SearchResult missing icon argument");
             } else if (mResultPayload == null) {
                 throw new IllegalArgumentException("SearchResult missing Payload argument");
             }
diff --git a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
index a744bb7..1df7b1f 100644
--- a/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
+++ b/tests/robotests/src/com/android/settings/search/DatabaseResultLoaderTest.java
@@ -22,20 +22,23 @@
 import android.content.Intent;
 import android.database.MatrixCursor;
 import android.graphics.drawable.Drawable;
+
+import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.SubSettings;
 import com.android.settings.TestConfig;
+import com.android.settings.gestures.GestureSettings;
 import com.android.settings.search2.DatabaseResultLoader;
 import com.android.settings.search2.IntentPayload;
 import com.android.settings.search2.ResultPayload;
 import com.android.settings.search2.ResultPayload.PayloadType;
 import com.android.settings.search2.SearchResult;
-import com.android.settings.R;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-
-import org.robolectric.annotation.Config;
 import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -47,7 +50,11 @@
 public class DatabaseResultLoaderTest {
     private DatabaseResultLoader mLoader;
 
-    private static final String[] TITLES = new String[] {"title1", "title2", "title3"};
+    private static final String[] COLUMNS = new String[]{"rank", "title", "summary_on",
+            "summary off", "entries", "keywords", "class name", "screen title", "icon",
+            "intent action", "target package", "target class", "enabled", "key", "user id"};
+
+    private static final String[] TITLES = new String[]{"title1", "title2", "title3"};
     private static final String SUMMARY = "SUMMARY";
     private static final int EXAMPLES = 3;
     private static final Intent mIntent = new Intent("com.android.settings");
@@ -108,6 +115,16 @@
     }
 
     @Test
+    public void testParseCursor_NoIcon() {
+        List<SearchResult> results = mLoader.parseCursorForSearch(
+                getDummyCursor(false /* hasIcon */));
+        for (int i = 0; i < EXAMPLES; i++) {
+            Drawable resultDrawable = results.get(i).icon;
+            assertThat(resultDrawable).isNull();
+        }
+    }
+
+    @Test
     public void testParseCursor_MatchesPayloadType() {
         List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
         ResultPayload payload;
@@ -118,6 +135,33 @@
     }
 
     @Test
+    public void testParseCursor_MatchesIntentForSubSettings() {
+        MatrixCursor cursor = new MatrixCursor(COLUMNS);
+        final String BLANK = "";
+        cursor.addRow(new Object[]{
+                0,       // rank
+                TITLES[0],
+                SUMMARY,
+                SUMMARY, // summary off
+                BLANK,   // entries
+                BLANK,   // Keywords
+                GestureSettings.class.getName(),
+                BLANK,   // screen title
+                null,    // icon
+                BLANK,   // action
+                null,    // target package
+                BLANK,   // target class
+                BLANK,   // enabled
+                BLANK,   // key
+                BLANK    // user id
+        });
+        List<SearchResult> results = mLoader.parseCursorForSearch(cursor);
+        IntentPayload payload = (IntentPayload) results.get(0).payload;
+        Intent intent = payload.intent;
+        assertThat(intent.getComponent().getClassName()).isEqualTo(SubSettings.class.getName());
+    }
+
+    @Test
     public void testParseCursor_MatchesIntentPayload() {
         List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
         IntentPayload payload;
@@ -129,14 +173,15 @@
     }
 
     private MatrixCursor getDummyCursor() {
-        String[] columns = new String[] {"rank", "title",  "summary_on", "summary off", "entries",
-                "keywords", "class name", "screen title", "icon", "intent action",
-                "target package", "target class", "enabled", "key", "user id"};
-        MatrixCursor cursor = new MatrixCursor(columns);
+        return getDummyCursor(true /* hasIcon */);
+    }
+
+    private MatrixCursor getDummyCursor(boolean hasIcon) {
+        MatrixCursor cursor = new MatrixCursor(COLUMNS);
         final String BLANK = "";
 
         for (int i = 0; i < EXAMPLES; i++) {
-            ArrayList<String> item = new ArrayList<>(columns.length);
+            ArrayList<String> item = new ArrayList<>(COLUMNS.length);
             item.add(Integer.toString(i));
             item.add(TITLES[i]);
             item.add(SUMMARY);
@@ -145,7 +190,7 @@
             item.add(BLANK); // keywords
             item.add(BLANK); // classname
             item.add(BLANK); // screen title
-            item.add(Integer.toString(mIcon));
+            item.add(hasIcon ? Integer.toString(mIcon) : null);
             item.add(mIntent.getAction());
             item.add(BLANK); // target package
             item.add(BLANK); // target class
diff --git a/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java b/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java
index a0f4cc5..5649a95 100644
--- a/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchResultBuilderTest.java
@@ -124,13 +124,7 @@
                 .addBreadcrumbs(mBreadcrumbs)
                 .addPayload(mResultPayload);
 
-        SearchResult result = null;
-        try {
-            result = mBuilder.build();
-        } catch (IllegalArgumentException e) {
-            // passes.
-        }
-        assertThat(result).isNull();
+        assertThat(mBuilder.build()).isNotNull();
     }
 
     @Test