Hide voice search button if intent cannot be handled

If the Google Search app is disabled (this only seems to be possible
on Nexus 4), the Dialer crashes if the voice search button is pressed.
This CL hides/shows the voice search button every time the dialer
is launched/resumed after checking to see if there exists an activity
that can handle the ACTION_RECOGNIZE_SPEECH intent.

A string to indicate that voice search is unavailable is also added
just in case the user ends up in a weird state where the button is
showing but clicking on it would throw an ActivityNotFoundException.

Bug: 12015318
Change-Id: Idd7ec2da422425dd95ae0060ebc9b85a2cf35fb0
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9b534da..a0376a6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -520,6 +520,9 @@
     <!-- Message displayed when there is no application available to handle the add contact menu option. [CHAR LIMIT=NONE] -->
     <string name="add_contact_not_available">Re-enable the People application to use this feature.</string>
 
+    <!-- Message displayed when there is no application available to handle voice search. [CHAR LIMIT=NONE] -->
+    <string name="voice_search_not_available">Voice search is not available.</string>
+
     <!-- Hint displayed in dialer search box when there is no query that is currently typed.
          [CHAR LIMIT=30] -->
     <string name="dialer_hint_find_contact">Type a name or phone number</string>
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 1ab4601..0c0fcda 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -27,6 +27,8 @@
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
@@ -48,6 +50,7 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.AbsListView.OnScrollListener;
@@ -76,6 +79,7 @@
 import com.android.internal.telephony.ITelephony;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * The dialer tab's title is 'phone', a more common name (see strings.xml).
@@ -330,7 +334,7 @@
             hideDialpadFragment(false, true);
             mInCallDialpadUp = false;
         }
-
+        prepareVoiceSearchButton();
         mFirstLaunch = false;
         mDialerDatabaseHelper.startSmartDialUpdateThread();
     }
@@ -447,8 +451,13 @@
                 }
                 break;
             case R.id.voice_search_button:
-                final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-                startActivityForResult(voiceIntent, ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
+                try {
+                    startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
+                            ACTIVITY_REQUEST_CODE_VOICE_SEARCH);
+                } catch (ActivityNotFoundException e) {
+                    Toast.makeText(DialtactsActivity.this, R.string.voice_search_not_available,
+                            Toast.LENGTH_SHORT).show();
+                }
                 break;
             default: {
                 Log.wtf(TAG, "Unexpected onClick event from " + view);
@@ -507,8 +516,7 @@
         mSearchViewContainer = findViewById(R.id.search_view_container);
         mSearchViewCloseButton = findViewById(R.id.search_close_button);
         mSearchViewCloseButton.setOnClickListener(this);
-        mVoiceSearchButton = findViewById(R.id.voice_search_button);
-        mVoiceSearchButton.setOnClickListener(this);
+
         mSearchView = (EditText) findViewById(R.id.search_view);
         mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
 
@@ -524,6 +532,19 @@
         ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
 
         mSearchView.setHint(ssb);
+
+        prepareVoiceSearchButton();
+    }
+
+    private void prepareVoiceSearchButton() {
+        mVoiceSearchButton = findViewById(R.id.voice_search_button);
+        final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+        if (canIntentBeHandled(voiceIntent)) {
+            mVoiceSearchButton.setVisibility(View.VISIBLE);
+            mVoiceSearchButton.setOnClickListener(this);
+        } else {
+            mVoiceSearchButton.setVisibility(View.GONE);
+        }
     }
 
     final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@@ -949,4 +970,11 @@
         intent.putExtra(Intents.Insert.NAME, text);
         return intent;
     }
+
+    private boolean canIntentBeHandled(Intent intent) {
+        final PackageManager packageManager = getPackageManager();
+        final List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        return resolveInfo != null && resolveInfo.size() > 0;
+    }
 }