Merge "Query search result intent before launching to avoid crash" into oc-dr1-dev
am: 9f93faf7ae

Change-Id: I596f52f4db123c3df485a1a194966771706650db
diff --git a/src/com/android/settings/search/IntentSearchViewHolder.java b/src/com/android/settings/search/IntentSearchViewHolder.java
index 2722d56..711d08e 100644
--- a/src/com/android/settings/search/IntentSearchViewHolder.java
+++ b/src/com/android/settings/search/IntentSearchViewHolder.java
@@ -17,17 +17,24 @@
 package com.android.settings.search;
 
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.UserHandle;
+import android.util.Log;
 import android.view.View;
 
 import com.android.internal.logging.nano.MetricsProto;
 
+import java.util.List;
+
 /**
  * ViewHolder for intent based search results.
  * The DatabaseResultLoader is the primary use case for this ViewHolder.
  */
 public class IntentSearchViewHolder extends SearchViewHolder {
 
+    private static final String TAG = "IntentSearchViewHolder";
+
     public IntentSearchViewHolder(View view) {
         super(view);
     }
@@ -42,7 +49,7 @@
         super.onBind(fragment, result);
 
         itemView.setOnClickListener(v -> {
-           fragment.onSearchResultClicked(this, result);
+            fragment.onSearchResultClicked(this, result);
             final Intent intent = result.payload.getIntent();
             // Use app user id to support work profile use case.
             if (result instanceof AppSearchResult) {
@@ -50,7 +57,14 @@
                 UserHandle userHandle = appResult.getAppUserHandle();
                 fragment.getActivity().startActivityAsUser(intent, userHandle);
             } else {
-                fragment.startActivity(intent);
+                final PackageManager pm = fragment.getActivity().getPackageManager();
+                final List<ResolveInfo> info = pm.queryIntentActivities(intent, 0 /* flags */);
+                if (info != null && !info.isEmpty()) {
+                    fragment.startActivity(intent);
+                } else {
+                    Log.e(TAG, "Cannot launch search result, title: "
+                            + result.title + ", " + intent);
+                }
             }
         });
     }
diff --git a/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
index 38e6285..a3826f6 100644
--- a/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
+++ b/tests/robotests/src/com/android/settings/search/IntentSearchViewHolderTest.java
@@ -18,10 +18,10 @@
 package com.android.settings.search;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -31,6 +31,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
 import android.view.LayoutInflater;
@@ -52,6 +53,7 @@
 import org.robolectric.annotation.Config;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -82,7 +84,7 @@
         mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
 
         final Context context = RuntimeEnvironment.application;
-        View view = LayoutInflater.from(context).inflate(R.layout.search_intent_item, null);
+        final View view = LayoutInflater.from(context).inflate(R.layout.search_intent_item, null);
         mHolder = new IntentSearchViewHolder(view);
 
         mIcon = context.getDrawable(R.drawable.ic_search_24dp);
@@ -100,7 +102,7 @@
 
     @Test
     public void testBindViewElements_allUpdated() {
-        SearchResult result = getSearchResult(TITLE, SUMMARY, mIcon);
+        final SearchResult result = getSearchResult(TITLE, SUMMARY, mIcon);
         mHolder.onBind(mFragment, result);
         mHolder.itemView.performClick();
 
@@ -111,7 +113,6 @@
         assertThat(mHolder.breadcrumbView.getVisibility()).isEqualTo(View.GONE);
 
         verify(mFragment).onSearchResultClicked(eq(mHolder), any(SearchResult.class));
-        verify(mFragment).startActivity(any(Intent.class));
     }
 
     @Test
@@ -158,8 +159,8 @@
 
     @Test
     public void testBindElements_placeholderSummary_visibilityIsGone() {
-        String nonBreakingSpace = mContext.getString(R.string.summary_placeholder);
-        SearchResult result = new Builder()
+        final String nonBreakingSpace = mContext.getString(R.string.summary_placeholder);
+        final SearchResult result = new Builder()
                 .setTitle(TITLE)
                 .setSummary(nonBreakingSpace)
                 .setPayload(new ResultPayload(null))
@@ -173,8 +174,8 @@
 
     @Test
     public void testBindElements_dynamicSummary_visibilityIsGone() {
-        String dynamicSummary = "%s";
-        SearchResult result = new Builder()
+        final String dynamicSummary = "%s";
+        final SearchResult result = new Builder()
                 .setTitle(TITLE)
                 .setSummary(dynamicSummary)
                 .setPayload(new ResultPayload(null))
@@ -191,7 +192,7 @@
         when(mPackageManager.getUserBadgedLabel(any(CharSequence.class),
                 eq(new UserHandle(USER_ID)))).thenReturn(BADGED_LABEL);
 
-        SearchResult result = getAppSearchResult(
+        final SearchResult result = getAppSearchResult(
                 TITLE, SUMMARY, mIcon, getApplicationInfo(USER_ID, TITLE, mIcon));
         mHolder.onBind(mFragment, result);
         mHolder.itemView.performClick();
@@ -207,6 +208,38 @@
                 any(Intent.class), eq(new UserHandle(USER_ID)));
     }
 
+    @Test
+    public void testBindViewElements_validSubSettingIntent_shouldLaunch() {
+        final SearchResult result = getSearchResult(TITLE, SUMMARY, mIcon);
+        when(mPackageManager.queryIntentActivities(result.payload.getIntent(), 0 /* flags */))
+                .thenReturn(Arrays.asList(new ResolveInfo()));
+
+        mHolder.onBind(mFragment, result);
+        mHolder.itemView.performClick();
+
+        assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
+        assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
+        assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mFragment).onSearchResultClicked(eq(mHolder), any(SearchResult.class));
+        verify(mFragment).startActivity(result.payload.getIntent());
+    }
+
+    @Test
+    public void testBindViewElements_invalidSubSettingIntent_shouldNotLaunchAnything() {
+        final SearchResult result = getSearchResult(TITLE, SUMMARY, mIcon);
+        when(mPackageManager.queryIntentActivities(result.payload.getIntent(), 0 /* flags */))
+                .thenReturn(null);
+
+        mHolder.onBind(mFragment, result);
+        mHolder.itemView.performClick();
+
+        assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
+        assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
+        assertThat(mHolder.summaryView.getVisibility()).isEqualTo(View.VISIBLE);
+        verify(mFragment).onSearchResultClicked(eq(mHolder), any(SearchResult.class));
+        verify(mFragment, never()).startActivity(any(Intent.class));
+    }
+
     private SearchResult getSearchResult(String title, String summary, Drawable icon) {
         Builder builder = new Builder();
         builder.setStableId(Objects.hash(title, summary, icon))