Bug 1153904 - Add item in Settings for Voice input. r=liuche
authorKarim Benhmida <kbenhmida@mozilla.com>
Fri, 26 Jun 2015 13:20:44 -0700
changeset 268489 b3f1aedcf9ba93825bc758684e722435cb0d296c
parent 268488 0c42f28d884daa0945e4e2a6de4c58b4f93a2d07
child 268490 d6999339a68ec6bc6af3f46ea55bff5307505ff2
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-esr52@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersliuche
bugs1153904
milestone41.0a1
Bug 1153904 - Add item in Settings for Voice input. r=liuche
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/moz.build
mobile/android/base/preferences/GeckoPreferences.java
mobile/android/base/resources/xml/preferences_display.xml
mobile/android/base/strings.xml.in
mobile/android/base/toolbar/ToolbarEditText.java
mobile/android/base/util/VoiceRecognizerUtils.java
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -245,16 +245,18 @@ size. -->
 <!-- Localization note (pref_font_size_preview_text): This paragraph is used as an example to
     demonstrate the font size setting.  It is meant to be whimsical and fun. -->
 <!ENTITY pref_font_size_preview_text "The quick orange fox jumps over your expectations with more speed, more flexibility and more security. As a non-profit, we\'re free to innovate on your behalf without any pressure to compromise. That means a better experience for you and a brighter future for the Web.">
 
 <!ENTITY pref_media_autoplay_enabled "Allow autoplay">
 <!ENTITY pref_media_autoplay_enabled_summary "Control if websites can autoplay videos and other media content">
 <!ENTITY pref_zoom_force_enabled "Always enable zoom">
 <!ENTITY pref_zoom_force_enabled_summary "Force override so you can zoom any page">
+<!ENTITY pref_voice_input "Voice input">
+<!ENTITY pref_voice_input_summary "Allow voice dictation in the title bar">
 
 <!ENTITY pref_use_master_password "Use master password">
 <!ENTITY pref_sync "Sync">
 <!ENTITY pref_sync_summary2 "Sync your tabs, bookmarks, logins, history">
 <!ENTITY pref_search_suggestions "Show search suggestions">
 <!ENTITY pref_import_android "Import from Android">
 <!ENTITY pref_import_android_summary "Import bookmarks and history from the native browser">
 <!ENTITY pref_private_data_history2 "Browsing history">
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -94,16 +94,17 @@ gujar.sources += [
     'util/NativeJSObject.java',
     'util/NonEvictingLruCache.java',
     'util/PrefUtils.java',
     'util/ProxySelector.java',
     'util/RawResource.java',
     'util/StringUtils.java',
     'util/ThreadUtils.java',
     'util/UIAsyncTask.java',
+    'util/VoiceRecognizerUtils.java',
     'util/WeakReferenceHandler.java',
     'util/WebActivityMapper.java',
     'util/WindowUtils.java',
 ]
 gujar.extra_jars = [
     'constants.jar',
     'gecko-mozglue.jar',
 ]
--- a/mobile/android/base/preferences/GeckoPreferences.java
+++ b/mobile/android/base/preferences/GeckoPreferences.java
@@ -38,16 +38,17 @@ import org.mozilla.gecko.TelemetryContra
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.healthreport.HealthReportConstants;
 import org.mozilla.gecko.db.BrowserContract.SuggestedSites;
 import org.mozilla.gecko.updater.UpdateService;
 import org.mozilla.gecko.updater.UpdateServiceHelper;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.util.VoiceRecognizerUtils;
 import org.mozilla.gecko.widget.FloatingHintEditText;
 
 import android.app.ActionBar;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.Fragment;
 import android.app.FragmentManager;
@@ -123,16 +124,17 @@ OnSharedPreferenceChangeListener
     private static final String PREFS_HEALTHREPORT_LINK = NON_PREF_PREFIX + "healthreport.link";
     private static final String PREFS_DEVTOOLS_REMOTE_ENABLED = "devtools.debugger.remote-enabled";
     private static final String PREFS_DISPLAY_REFLOW_ON_ZOOM = "browser.zoom.reflowOnZoom";
     private static final String PREFS_DISPLAY_TITLEBAR_MODE = "browser.chrome.titlebarMode";
     private static final String PREFS_SYNC = NON_PREF_PREFIX + "sync";
     private static final String PREFS_TRACKING_PROTECTION = "privacy.trackingprotection.enabled";
     private static final String PREFS_TRACKING_PROTECTION_LEARN_MORE = NON_PREF_PREFIX + "trackingprotection.learn_more";
     public static final String PREFS_OPEN_URLS_IN_PRIVATE = NON_PREF_PREFIX + "openExternalURLsPrivately";
+    public static final String PREFS_VOICE_INPUT_ENABLED = NON_PREF_PREFIX + "voice_input_enabled";
 
     private static final String ACTION_STUMBLER_UPLOAD_PREF = AppConstants.ANDROID_PACKAGE_NAME + ".STUMBLER_PREF";
 
     // This isn't a Gecko pref, even if it looks like one.
     private static final String PREFS_BROWSER_LOCALE = "locale";
 
     public static final String PREFS_RESTORE_SESSION = NON_PREF_PREFIX + "restoreSession3";
     public static final String PREFS_SUGGESTED_SITES = NON_PREF_PREFIX + "home_suggested_sites";
@@ -782,16 +784,22 @@ OnSharedPreferenceChangeListener
                         i--;
                         continue;
                     }
                 } else if (!(AppConstants.MOZ_ANDROID_TAB_QUEUE && AppConstants.NIGHTLY_BUILD) && PREFS_TAB_QUEUE.equals(key)) {
                     // Only show tab queue pref on nightly builds with the tab queue build flag.
                     preferences.removePreference(pref);
                     i--;
                     continue;
+                } else if (PREFS_VOICE_INPUT_ENABLED.equals(key) &&
+                           (!AppConstants.NIGHTLY_BUILD || !VoiceRecognizerUtils.supportsVoiceRecognizer(getApplicationContext(), getResources().getString(R.string.voicesearch_prompt)))) {
+                    // Remove UI for voice input on non nightly builds.
+                    preferences.removePreference(pref);
+                    i--;
+                    continue;
                 }
 
                 // Some Preference UI elements are not actually preferences,
                 // but they require a key to work correctly. For example,
                 // "Clear private data" requires a key for its state to be
                 // saved when the orientation changes. It uses the
                 // "android.not_a_preference.privacy.clear" key - which doesn't
                 // exist in Gecko - to satisfy this requirement.
--- a/mobile/android/base/resources/xml/preferences_display.xml
+++ b/mobile/android/base/resources/xml/preferences_display.xml
@@ -28,16 +28,21 @@
     <CheckBoxPreference android:key="media.autoplay.enabled"
                         android:title="@string/pref_media_autoplay_enabled"
                         android:summary="@string/pref_media_autoplay_enabled_summary" />
 
     <CheckBoxPreference android:key="browser.ui.zoom.force-user-scalable"
                         android:title="@string/pref_zoom_force_enabled"
                         android:summary="@string/pref_zoom_force_enabled_summary" />
 
+    <CheckBoxPreference android:key="android.not_a_preference.voice_search_enabled"
+                        android:title="@string/pref_voice_input"
+                        android:summary="@string/pref_voice_input_summary"
+                        android:defaultValue="true"/>
+
     <PreferenceCategory android:title="@string/pref_category_advanced">
 
         <CheckBoxPreference
                         android:key="browser.zoom.reflowOnZoom"
                         android:title="@string/pref_reflow_on_zoom"
                         android:defaultValue="false"
                         android:persistent="false" />
 
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -213,16 +213,18 @@
   <string name="pref_font_size_xlarge">&pref_font_size_xlarge;</string>
   <string name="pref_font_size_set">&pref_font_size_set;</string>
   <string name="pref_font_size_adjust_char">&pref_font_size_adjust_char;</string>
   <string name="pref_font_size_preview_text">&pref_font_size_preview_text;</string>
   <string name="pref_media_autoplay_enabled">&pref_media_autoplay_enabled;</string>
   <string name="pref_media_autoplay_enabled_summary">&pref_media_autoplay_enabled_summary;</string>
   <string name="pref_zoom_force_enabled">&pref_zoom_force_enabled;</string>
   <string name="pref_zoom_force_enabled_summary">&pref_zoom_force_enabled_summary;</string>
+  <string name="pref_voice_input">&pref_voice_input;</string>
+  <string name="pref_voice_input_summary">&pref_voice_input_summary;</string>
   <string name="pref_reflow_on_zoom">&pref_reflow_on_zoom4;</string>
   <string name="pref_restore">&pref_restore;</string>
   <string name="pref_restore_always">&pref_restore_always;</string>
   <string name="pref_restore_quit">&pref_restore_quit;</string>
   <string name="pref_sync">&pref_sync;</string>
   <string name="pref_sync_summary">&pref_sync_summary2;</string>
   <string name="pref_search_suggestions">&pref_search_suggestions;</string>
   <string name="pref_private_data_history2">&pref_private_data_history2;</string>
--- a/mobile/android/base/toolbar/ToolbarEditText.java
+++ b/mobile/android/base/toolbar/ToolbarEditText.java
@@ -7,21 +7,24 @@ package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.ActivityHandlerHelper;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.CustomEditText;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.InputMethods;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.GamepadUtils;
+import org.mozilla.gecko.util.VoiceRecognizerUtils;
 import org.mozilla.gecko.util.StringUtils;
 
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.graphics.Rect;
@@ -39,17 +42,16 @@ import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionWrapper;
 import android.view.inputmethod.InputMethodManager;
-import android.view.ViewParent;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
 
 import java.util.List;
 
 /**
 * {@code ToolbarEditText} is the text entry used when the toolbar
 * is in edit state. It handles all the necessary input method machinery.
@@ -61,16 +63,17 @@ public class ToolbarEditText extends Cus
     private static final String LOGTAG = "GeckoToolbarEditText";
     private static final NoCopySpan AUTOCOMPLETE_SPAN = new NoCopySpan.Concrete();
 
     private final Context mContext;
 
     private OnCommitListener mCommitListener;
     private OnDismissListener mDismissListener;
     private OnFilterListener mFilterListener;
+    private VoiceSearchOnTouchListener mVoiceOnTouchListener;
 
     private ToolbarPrefs mPrefs;
 
     // The previous autocomplete result returned to us
     private String mAutoCompleteResult = "";
     // Length of the user-typed portion of the result
     private int mAutoCompletePrefixLength;
     // If text change is due to us setting autocomplete
@@ -98,25 +101,25 @@ public class ToolbarEditText extends Cus
     }
 
     @Override
     public void onAttachedToWindow() {
         setOnKeyListener(new KeyListener());
         setOnKeyPreImeListener(new KeyPreImeListener());
         setOnSelectionChangedListener(new SelectionChangeListener());
         addTextChangedListener(new TextChangeListener());
-        configureCompoundDrawables();
     }
 
     @Override
     public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
 
         if (gainFocus) {
             resetAutocompleteState();
+            configureCompoundDrawables();
             return;
         }
 
         removeAutocomplete(getText());
 
         final InputMethodManager imm = InputMethods.getInputMethodManager(mContext);
         try {
             imm.restartInput(this);
@@ -471,39 +474,42 @@ public class ToolbarEditText extends Cus
     }
 
     /**
      * Detect if we are able to enable the 'buttons' made from compound drawables.
      *
      * Currently, only voice input.
      */
     private void configureCompoundDrawables() {
-        if (!AppConstants.NIGHTLY_BUILD || !supportsVoiceRecognizer()) {
-            // Remove the mic button if we can't support the voice recognizer.
+        if (AppConstants.NIGHTLY_BUILD && voiceIsEnabled()) {
+            final Drawable micImg = getContext().getResources().getDrawable(R.drawable.ab_mic);
+            setCompoundDrawablesWithIntrinsicBounds(null, null, micImg, null);
+
+            if (mVoiceOnTouchListener == null) {
+                mVoiceOnTouchListener = new VoiceSearchOnTouchListener();
+            }
+            setOnTouchListener(mVoiceOnTouchListener);
+        } else {
+            // Remove the mic button
             setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
-            return;
+            setOnTouchListener(null);
         }
-        setOnTouchListener(new VoiceSearchOnTouchListener());
     }
 
-    private boolean supportsVoiceRecognizer() {
-        final Intent intent = createVoiceRecognizerIntent();
-        return intent.resolveActivity(getContext().getPackageManager()) != null;
-    }
-
-    private Intent createVoiceRecognizerIntent() {
-        final Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
-        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
-        intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
-        intent.putExtra(RecognizerIntent.EXTRA_PROMPT, getResources().getString(R.string.voicesearch_prompt));
-        return intent;
+    private boolean voiceIsEnabled() {
+        final boolean voiceIsSupported = VoiceRecognizerUtils.supportsVoiceRecognizer(getContext(), getResources().getString(R.string.voicesearch_prompt));
+        if (!voiceIsSupported) {
+            return false;
+        }
+        return GeckoSharedPrefs.forApp(getContext())
+                .getBoolean(GeckoPreferences.PREFS_VOICE_INPUT_ENABLED, true);
     }
 
     private void launchVoiceRecognizer() {
-        final Intent intent = createVoiceRecognizerIntent();
+        final Intent intent = VoiceRecognizerUtils.createVoiceRecognizerIntent(getResources().getString(R.string.voicesearch_prompt));
 
         Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
         ActivityHandlerHelper.startIntentForActivity(activity, intent, new ActivityResultHandler() {
             @Override
             public void onActivityResult(int resultCode, Intent data) {
                 switch (resultCode) {
                     case RecognizerIntent.RESULT_CLIENT_ERROR:
                     case RecognizerIntent.RESULT_NETWORK_ERROR:
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/util/VoiceRecognizerUtils.java
@@ -0,0 +1,25 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.speech.RecognizerIntent;
+
+public class VoiceRecognizerUtils {
+    public static boolean supportsVoiceRecognizer(Context context, String prompt) {
+        final Intent intent = createVoiceRecognizerIntent(prompt);
+        return intent.resolveActivity(context.getPackageManager()) != null;
+    }
+
+    public static Intent createVoiceRecognizerIntent(String prompt) {
+        final Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
+        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
+        intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1);
+        intent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
+        return intent;
+    }
+}