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 281167 b3f1aedcf9ba93825bc758684e722435cb0d296c
parent 281166 0c42f28d884daa0945e4e2a6de4c58b4f93a2d07
child 281168 d6999339a68ec6bc6af3f46ea55bff5307505ff2
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersliuche
bugs1153904
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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;
+    }
+}