Bug 602818 - Integrate QR code scanner into Fennec. r=liuche
authorKarim Benhmida <kbenhmida@mozilla.com>
Fri, 19 Jun 2015 13:20:31 -0700
changeset 281169 9f307a1aba5c44071d4f7a74a1ac4e48c61d0dba
parent 281168 d6999339a68ec6bc6af3f46ea55bff5307505ff2
child 281170 b0ac036f4c7f10388e8e69ac94b6e1b45543c5ac
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
bugs602818
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 602818 - Integrate QR code scanner into Fennec. r=liuche
mobile/android/base/BrowserApp.java
mobile/android/base/locales/en-US/android_strings.dtd
mobile/android/base/moz.build
mobile/android/base/preferences/GeckoPreferences.java
mobile/android/base/resources/drawable-hdpi/ab_qrcode.png
mobile/android/base/resources/drawable-mdpi/ab_qrcode.png
mobile/android/base/resources/drawable-xhdpi/ab_qrcode.png
mobile/android/base/resources/drawable-xxhdpi/ab_qrcode.png
mobile/android/base/resources/layout/toolbar_edit_layout.xml
mobile/android/base/resources/xml/preferences_display.xml
mobile/android/base/strings.xml.in
mobile/android/base/toolbar/BrowserToolbar.java
mobile/android/base/toolbar/ToolbarEditLayout.java
mobile/android/base/toolbar/ToolbarEditText.java
mobile/android/base/util/InputOptionsUtils.java
mobile/android/base/util/VoiceRecognizerUtils.java
mobile/android/tests/browser/robocop/testSettingsMenuItems.java
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1080,16 +1080,22 @@ public class BrowserApp extends GeckoApp
             mLayerView.getVisibility() != View.VISIBLE){
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     mLayerView.showSurface();
                 }
             });
         }
+
+        // Sending a message to the toolbar when the browser window gains focus
+        // This is needed for qr code input
+        if (hasFocus) {
+            mBrowserToolbar.onParentFocus();
+        }
     }
 
     private void setBrowserToolbarListeners() {
         mBrowserToolbar.setOnActivateListener(new BrowserToolbar.OnActivateListener() {
             @Override
             public void onActivate() {
                 enterEditingMode();
             }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -76,16 +76,17 @@
 <!ENTITY new_private_tab_opened "New private tab opened">
 <!-- Localization note (switch_button_message): This string should be as short
      as possible because it's shown as a label in a toast.  Ideally, this string
      is upper-case, to match Google and Android's convention. -->
 <!ENTITY switch_button_message "SWITCH">
 
 <!ENTITY settings "Settings">
 <!ENTITY settings_title "Settings">
+<!ENTITY pref_category_input_options "Input options">
 <!ENTITY pref_category_advanced "Advanced">
 <!ENTITY pref_category_customize "Customize">
 <!ENTITY pref_category_customize_summary "Home, search, tabs, import">
 <!ENTITY pref_category_customize_alt_summary "Home, search, tabs, open later, import">
 
 <!-- Localization note (pref_category_language) : This is the preferences
      section in which the user picks the locale in which to display Firefox
      UI. The locale includes both language and region concepts. -->
@@ -247,16 +248,18 @@ size. -->
 <!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_qrcode_enabled "QR code reader">
+<!ENTITY pref_qrcode_enabled_summary "Allow QR scanner 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
@@ -81,30 +81,30 @@ gujar.sources += [
     'util/GamepadUtils.java',
     'util/GeckoBackgroundThread.java',
     'util/GeckoEventListener.java',
     'util/GeckoJarReader.java',
     'util/GeckoRequest.java',
     'util/HardwareUtils.java',
     'util/INIParser.java',
     'util/INISection.java',
+    'util/InputOptionsUtils.java',
     'util/IOUtils.java',
     'util/JSONUtils.java',
     'util/MenuUtils.java',
     'util/NativeEventListener.java',
     'util/NativeJSContainer.java',
     '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
@@ -21,38 +21,36 @@ import org.mozilla.gecko.BrowserLocaleMa
 import org.mozilla.gecko.DataReportingNotification;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoActivityStatus;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.GuestSession;
 import org.mozilla.gecko.LocaleManager;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.RestrictedProfiles;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.TelemetryContract.Method;
 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.util.InputOptionsUtils;
 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;
 import android.app.NotificationManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -125,16 +123,17 @@ OnSharedPreferenceChangeListener
     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";
+    public static final String PREFS_QRCODE_ENABLED = NON_PREF_PREFIX + "qrcode_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";
@@ -682,16 +681,24 @@ OnSharedPreferenceChangeListener
                     }
                 } else if (pref instanceof PanelsPreferenceCategory) {
                     mPanelsPreferenceCategory = (PanelsPreferenceCategory) pref;
                 }
                 if((AppConstants.MOZ_ANDROID_TAB_QUEUE && AppConstants.NIGHTLY_BUILD) && (PREFS_CUSTOMIZE_SCREEN.equals(key))) {
                     // Only change the customize pref screen summary on nightly builds with the tab queue build flag.
                     pref.setSummary(getString(R.string.pref_category_customize_alt_summary));
                 }
+                if (getResources().getString(R.string.pref_category_input_options).equals(key)) {
+                    if (!AppConstants.NIGHTLY_BUILD || (!InputOptionsUtils.supportsVoiceRecognizer(getApplicationContext(), getResources().getString(R.string.voicesearch_prompt)) &&
+                            !InputOptionsUtils.supportsQrCodeReader(getApplicationContext()))) {
+                        preferences.removePreference(pref);
+                        i--;
+                        continue;
+                    }
+                }
                 setupPreferences((PreferenceGroup) pref, prefs);
             } else {
                 pref.setOnPreferenceChangeListener(this);
                 if (!AppConstants.MOZ_UPDATER &&
                     PREFS_UPDATER_AUTODOWNLOAD.equals(key)) {
                     preferences.removePreference(pref);
                     i--;
                     continue;
@@ -785,21 +792,27 @@ OnSharedPreferenceChangeListener
                         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)))) {
+                           (!AppConstants.NIGHTLY_BUILD || !InputOptionsUtils.supportsVoiceRecognizer(getApplicationContext(), getResources().getString(R.string.voicesearch_prompt)))) {
                     // Remove UI for voice input on non nightly builds.
                     preferences.removePreference(pref);
                     i--;
                     continue;
+                } else if (PREFS_QRCODE_ENABLED.equals(key) &&
+                         (!AppConstants.NIGHTLY_BUILD || !InputOptionsUtils.supportsQrCodeReader(getApplicationContext()))) {
+                    // Remove UI for qr code 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.
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..727201a2c79de586618bcd1a541500d8813a5e8c
GIT binary patch
literal 169
zc%17D@N?(olHy`uVBq!ia0vp^5+KaN3?zjj6;1;w{s5m4S0KG^!{+rHHm}>bY5m5{
zch+vu0Sd8{1o;IsY}vZ)R7s5xknig0;us<^H94TLurP&zN#X602TV!|GU9CFY!(Ww
zkD1sM`h=Pmb4+X#oFSv~yW?4cDeuW3)sW{Cp2}x5%xE|h;Mme9%)p*16}|6dtTWI+
N22WQ%mvv4FO#rYtG{*n{
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..30c0059a707792340ae7d71df3bd8ab79347ea1b
GIT binary patch
literal 113
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`7M?DSAr_~TBlZ`5t}i*$z?=};
z?a!=e;K6&~nSac~=Bq_cjUID0Z`hHdBIU3}=IBKWpGiilHzgTf<b7RuxVrl_&<qAo
LS3j3^P6<r_6tX5N
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d0d4c3a0cabb193569d91906b561bfa35223a76a
GIT binary patch
literal 177
zc%17D@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@o&cW^*Yz7Vuiv<N-G<FT0Zwk#
zMj*vf666=W_5T)z<$UeQKz@Lyi(^OyW3mA6+5i9lCueXtXJq{O|3BW@xsj)S0mlTv
zfAT!*gidm>GATGq9I`m};lKTf1ONXAN-SJB#n`~NEwYN`9nT~g{_jRg0xpV5iVTfC
V2id1y@CP}C!PC{xWt~$(69A{gIYa;e
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..8ad7859749f34d31e8c52f5ad1abd3e16786790c
GIT binary patch
literal 230
zc%17D@N?(olHy`uVBq!ia0vp^1|ZDB3?!H8JlO)IL<4+6T-R*aux`WV^&2;@U%v?`
zwtmB=b?Y|HnP}+&<gk|n`2~AD{_n}~$V2vrB1liAr;B4q1!HnT2orOnxJJV#R*4n`
z9@iNxJ?9s+#5!@R3Gz%faP`=cz~~(6#K|czNg(K`n&OfZ2Q=8G1vJil$1=@8B#mLx
z3!wyu2&OFFyv{&QBd6;D64@@C4?1osxCRBEEI1`EAkY@a@Ic3?K-_3U5{FtM$5Up8
YmDhiU{%73#4Cn|3Pgg&ebxsLQ0E>`JTmS$7
--- a/mobile/android/base/resources/layout/toolbar_edit_layout.xml
+++ b/mobile/android/base/resources/layout/toolbar_edit_layout.xml
@@ -16,14 +16,25 @@
           style="@style/UrlBar.Title"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           android:layout_weight="1.0"
           android:inputType="textUri|textNoSuggestions"
           android:imeOptions="actionGo|flagNoExtractUi|flagNoFullscreen"
           android:selectAllOnFocus="true"
           android:contentDescription="@string/url_bar_default_text"
-          android:drawableRight="@drawable/ab_mic"
           android:drawablePadding="12dp"
           android:paddingRight="8dp"
           gecko:autoUpdateTheme="false"/>
 
+    <ImageButton android:id="@+id/qrcode"
+                 android:layout_width="@dimen/page_action_button_width"
+                 android:layout_height="match_parent"
+                 android:src="@drawable/ab_qrcode"
+                 android:background="@android:color/transparent"/>
+
+    <ImageButton android:id="@+id/mic"
+                 android:layout_width="@dimen/page_action_button_width"
+                 android:layout_height="match_parent"
+                 android:src="@drawable/ab_mic"
+                 android:background="@android:color/transparent"/>
+
 </merge>
--- a/mobile/android/base/resources/xml/preferences_display.xml
+++ b/mobile/android/base/resources/xml/preferences_display.xml
@@ -28,20 +28,30 @@
     <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_input_options"
+                        android:key="@string/pref_category_input_options">
+
+        <CheckBoxPreference android:key="android.not_a_preference.voice_input_enabled"
+                            android:title="@string/pref_voice_input"
+                            android:summary="@string/pref_voice_input_summary"
+                            android:defaultValue="true"/>
+
+        <CheckBoxPreference android:key="android.not_a_preference.qrcode_enabled"
+                            android:title="@string/pref_qrcode_enabled"
+                            android:summary="@string/pref_qrcode_enabled_summary"
+                            android:defaultValue="true"/>
+
+    </PreferenceCategory>
 
     <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
@@ -119,16 +119,17 @@
   <string name="overlay_share_send_tab_btn_label">&overlay_share_send_tab_btn_label;</string>
   <string name="overlay_share_no_url">&overlay_share_no_url;</string>
   <string name="overlay_share_select_device">&overlay_share_select_device;</string>
   <string name="overlay_no_synced_devices">&overlay_no_synced_devices;</string>
   <string name="overlay_share_tab_not_sent">&overlay_share_tab_not_sent;</string>
 
   <string name="settings">&settings;</string>
   <string name="settings_title">&settings_title;</string>
+  <string name="pref_category_input_options">&pref_category_input_options;</string>
   <string name="pref_category_advanced">&pref_category_advanced;</string>
   <string name="pref_category_customize">&pref_category_customize;</string>
   <string name="pref_category_customize_summary">&pref_category_customize_summary;</string>
   <string name="pref_category_customize_alt_summary">&pref_category_customize_alt_summary;</string>
 
   <string name="pref_category_search">&pref_category_search3;</string>
   <string name="pref_category_search_summary">&pref_category_search_summary;</string>
   <string name="pref_category_display">&pref_category_display;</string>
@@ -215,16 +216,18 @@
   <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_qrcode_enabled">&pref_qrcode_enabled;</string>
+  <string name="pref_qrcode_enabled_summary">&pref_qrcode_enabled_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/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -346,16 +346,20 @@ public abstract class BrowserToolbar ext
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
 
         final int height = getHeight();
         canvas.drawRect(0, height - shadowSize, getWidth(), height, shadowPaint);
     }
 
+    public void onParentFocus() {
+        urlEditLayout.onParentFocus();
+    }
+
     public void setProgressBar(ToolbarProgressView progressBar) {
         this.progressBar = progressBar;
     }
 
     public void setTabHistoryController(TabHistoryController tabHistoryController) {
         this.tabHistoryController = tabHistoryController;
     }
 
--- a/mobile/android/base/toolbar/ToolbarEditLayout.java
+++ b/mobile/android/base/toolbar/ToolbarEditLayout.java
@@ -1,62 +1,115 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.toolbar;
 
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.speech.RecognizerIntent;
+import android.widget.Button;
+import android.widget.ImageButton;
+import org.mozilla.gecko.ActivityHandlerHelper;
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
+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.toolbar.BrowserToolbar.TabEditingState;
+import org.mozilla.gecko.util.ActivityResultHandler;
+import org.mozilla.gecko.util.StringUtils;
+import org.mozilla.gecko.util.InputOptionsUtils;
 import org.mozilla.gecko.widget.ThemedLinearLayout;
 
 import android.content.Context;
 import android.util.AttributeSet;
-import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 
+import java.util.List;
+
 /**
 * {@code ToolbarEditLayout} is the UI for when the toolbar is in
 * edit state. It controls a text entry ({@code ToolbarEditText})
 * and its matching 'go' button which changes depending on the
 * current type of text in the entry.
 */
 public class ToolbarEditLayout extends ThemedLinearLayout {
 
     private final ToolbarEditText mEditText;
 
+    private final ImageButton mVoiceInput;
+    private final ImageButton mQrCode;
+
     private OnFocusChangeListener mFocusChangeListener;
 
+    private boolean showKeyboardOnFocus = false; // Indicates if we need to show the keyboard after the app resumes
+
     public ToolbarEditLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         setOrientation(HORIZONTAL);
 
         LayoutInflater.from(context).inflate(R.layout.toolbar_edit_layout, this);
         mEditText = (ToolbarEditText) findViewById(R.id.url_edit_text);
+
+        mVoiceInput = (ImageButton) findViewById(R.id.mic);
+        mQrCode = (ImageButton) findViewById(R.id.qrcode);
     }
 
     @Override
     public void onAttachedToWindow() {
         mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
                 if (mFocusChangeListener != null) {
                     mFocusChangeListener.onFocusChange(ToolbarEditLayout.this, hasFocus);
+
+                    // Checking if voice and QR code input are enabled each time the user taps on the title bar
+                    if (hasFocus) {
+                        if (voiceIsEnabled(getContext(), getResources().getString(R.string.voicesearch_prompt))) {
+                            mVoiceInput.setVisibility(View.VISIBLE);
+                        } else {
+                            mVoiceInput.setVisibility(View.GONE);
+                        }
+
+                        if (qrCodeIsEnabled(getContext())) {
+                            mQrCode.setVisibility(View.VISIBLE);
+                        } else {
+                            mQrCode.setVisibility(View.GONE);
+                        }
+                    }
                 }
             }
         });
+
+        mVoiceInput.setOnClickListener(new Button.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                launchVoiceRecognizer();
+            }
+        });
+
+        mQrCode.setOnClickListener(new Button.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                launchQRCodeReader();
+            }
+        });
     }
 
     @Override
     public void setOnFocusChangeListener(OnFocusChangeListener listener) {
         mFocusChangeListener = listener;
     }
 
     @Override
@@ -66,16 +119,40 @@ public class ToolbarEditLayout extends T
     }
 
     @Override
     public void setPrivateMode(boolean isPrivate) {
         super.setPrivateMode(isPrivate);
         mEditText.setPrivateMode(isPrivate);
     }
 
+    /**
+     * Called when the parent gains focus (on app launch and resume)
+     */
+    public void onParentFocus() {
+        if (showKeyboardOnFocus) {
+            showKeyboardOnFocus = false;
+
+            Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
+            activity.runOnUiThread(new Runnable() {
+                public void run() {
+                    mEditText.requestFocus();
+                    showSoftInput();
+                }
+            });
+        }
+
+        // Checking if qr code is supported after resuming the app
+        if (qrCodeIsEnabled(getContext())) {
+            mQrCode.setVisibility(View.VISIBLE);
+        } else {
+            mQrCode.setVisibility(View.GONE);
+        }
+    }
+
     void setToolbarPrefs(final ToolbarPrefs prefs) {
         mEditText.setToolbarPrefs(prefs);
     }
 
     private void showSoftInput() {
         InputMethodManager imm =
                (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
         imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
@@ -134,9 +211,130 @@ public class ToolbarEditLayout extends T
         editingState.selectionStart = mEditText.getSelectionStart();
         editingState.selectionEnd = mEditText.getSelectionEnd();
    }
 
     protected void restoreTabEditingState(final TabEditingState editingState) {
         mEditText.setText(editingState.lastEditingText);
         mEditText.setSelection(editingState.selectionStart, editingState.selectionEnd);
     }
+
+    private boolean voiceIsEnabled(Context context, String prompt) {
+        // Voice input is enabled for nightly only
+        if(!AppConstants.NIGHTLY_BUILD) {
+            return false;
+        }
+        final boolean voiceIsSupported = InputOptionsUtils.supportsVoiceRecognizer(context, prompt);
+        if (!voiceIsSupported) {
+            return false;
+        }
+        return GeckoSharedPrefs.forApp(context)
+                .getBoolean(GeckoPreferences.PREFS_VOICE_INPUT_ENABLED, true);
+    }
+
+    private void launchVoiceRecognizer() {
+        final Intent intent = InputOptionsUtils.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:
+                    case RecognizerIntent.RESULT_SERVER_ERROR:
+                        // We have an temporarily unrecoverable error.
+                        handleVoiceSearchError(false);
+                        break;
+                    case RecognizerIntent.RESULT_AUDIO_ERROR:
+                    case RecognizerIntent.RESULT_NO_MATCH:
+                        // Maybe the user can say it differently?
+                        handleVoiceSearchError(true);
+                        break;
+                    case Activity.RESULT_CANCELED:
+                        break;
+                }
+
+                if (resultCode != Activity.RESULT_OK) {
+                    return;
+                }
+
+                // We have RESULT_OK, not RESULT_NO_MATCH so it should be safe to assume that
+                // we have at least one match. We only need one: this will be
+                // used for showing the user search engines with this search term in it.
+                List<String> voiceStrings = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
+                String text = voiceStrings.get(0);
+                mEditText.setText(text);
+                mEditText.setSelection(0, text.length());
+
+                final InputMethodManager imm =
+                        (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+                imm.showSoftInput(mEditText, InputMethodManager.SHOW_IMPLICIT);
+            }
+        });
+    }
+
+    private void handleVoiceSearchError(boolean offerRetry) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
+                .setTitle(R.string.voicesearch_failed_title)
+                .setIcon(R.drawable.icon).setNeutralButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        dialog.dismiss();
+                    }
+                });
+
+        if (offerRetry) {
+            builder.setMessage(R.string.voicesearch_failed_message_recoverable)
+                    .setNegativeButton(R.string.voicesearch_failed_retry, new DialogInterface.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            launchVoiceRecognizer();
+                        }
+                    });
+        } else {
+            builder.setMessage(R.string.voicesearch_failed_message);
+        }
+
+        AlertDialog dialog = builder.create();
+
+        dialog.show();
+    }
+
+    private boolean qrCodeIsEnabled(Context context) {
+        // QR code is enabled for nightly only
+        if(!AppConstants.NIGHTLY_BUILD) {
+            return false;
+        }
+        final boolean qrCodeIsSupported = InputOptionsUtils.supportsQrCodeReader(context);
+        if (!qrCodeIsSupported) {
+            return false;
+        }
+        return GeckoSharedPrefs.forApp(context)
+                .getBoolean(GeckoPreferences.PREFS_QRCODE_ENABLED, true);
+    }
+
+    private void launchQRCodeReader() {
+        final Intent intent = InputOptionsUtils.createQRCodeReaderIntent();
+
+        Activity activity = GeckoAppShell.getGeckoInterface().getActivity();
+        ActivityHandlerHelper.startIntentForActivity(activity, intent, new ActivityResultHandler() {
+            @Override
+            public void onActivityResult(int resultCode, Intent intent) {
+                if (resultCode == Activity.RESULT_OK) {
+                    String text = intent.getStringExtra("SCAN_RESULT");
+                    if (!StringUtils.isSearchQuery(text, false)) {
+                        mEditText.setText(text);
+                        mEditText.selectAll();
+
+                        // Queuing up the keyboard show action.
+                        // At this point the app has not resumed yet, and trying to show
+                        // the keyboard will fail.
+                        showKeyboardOnFocus = true;
+                    }
+                }
+                // We can get the SCAN_RESULT_FORMAT, SCAN_RESULT_BYTES,
+                // SCAN_RESULT_ORIENTATION and SCAN_RESULT_ERROR_CORRECTION_LEVEL
+                // as well as the actual result, which may hold a URL.
+            }
+        });
+    }
 }
--- a/mobile/android/base/toolbar/ToolbarEditText.java
+++ b/mobile/android/base/toolbar/ToolbarEditText.java
@@ -1,79 +1,61 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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.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;
-import android.graphics.drawable.Drawable;
-import android.speech.RecognizerIntent;
 import android.text.Editable;
 import android.text.NoCopySpan;
 import android.text.Selection;
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.text.style.BackgroundColorSpan;
 import android.util.AttributeSet;
 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.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.
 * It's meant to be owned by {@code ToolbarEditLayout}.
 */
 public class ToolbarEditText extends CustomEditText
                              implements AutocompleteHandler {
 
     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
@@ -109,17 +91,16 @@ public class ToolbarEditText extends Cus
     }
 
     @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);
@@ -468,115 +449,16 @@ public class ToolbarEditText extends Cus
                 if (removeAutocompleteOnComposing(text)) {
                     return false;
                 }
                 return super.setComposingText(text, newCursorPosition);
             }
         };
     }
 
-    /**
-     * Detect if we are able to enable the 'buttons' made from compound drawables.
-     *
-     * Currently, only voice input.
-     */
-    private void configureCompoundDrawables() {
-        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);
-            setOnTouchListener(null);
-        }
-    }
-
-    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 = 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:
-                    case RecognizerIntent.RESULT_SERVER_ERROR:
-                        // We have an temporarily unrecoverable error.
-                        handleVoiceSearchError(false);
-                        break;
-                    case RecognizerIntent.RESULT_AUDIO_ERROR:
-                    case RecognizerIntent.RESULT_NO_MATCH:
-                        // Maybe the user can say it differently?
-                        handleVoiceSearchError(true);
-                        break;
-                    case Activity.RESULT_CANCELED:
-                        break;
-                }
-
-                if (resultCode != Activity.RESULT_OK) {
-                    return;
-                }
-
-                // We have RESULT_OK, not RESULT_NO_MATCH so it should be safe to assume that
-                // we have at least one match. We only need one: this will be
-                // used for showing the user search engines with this search term in it.
-                List<String> voiceStrings = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
-                String text = voiceStrings.get(0);
-                setText(text);
-                setSelection(0, text.length());
-
-                final InputMethodManager imm =
-                        (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-                imm.showSoftInput(ToolbarEditText.this, InputMethodManager.SHOW_IMPLICIT);
-            }
-        });
-    }
-
-    private void handleVoiceSearchError(boolean offerRetry) {
-        AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
-                .setTitle(R.string.voicesearch_failed_title)
-                .setIcon(R.drawable.icon).setNeutralButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        dialog.dismiss();
-                    }
-                });
-
-        if (offerRetry) {
-            builder.setMessage(R.string.voicesearch_failed_message_recoverable)
-                   .setNegativeButton(R.string.voicesearch_failed_retry, new DialogInterface.OnClickListener() {
-                       @Override
-                       public void onClick(DialogInterface dialog, int which) {
-                           launchVoiceRecognizer();
-                       }
-                   });
-        } else {
-            builder.setMessage(R.string.voicesearch_failed_message);
-        }
-
-        AlertDialog dialog = builder.create();
-
-        dialog.show();
-    }
-
     private class SelectionChangeListener implements OnSelectionChangedListener {
         @Override
         public void onSelectionChanged(final int selStart, final int selEnd) {
             // The user has repositioned the cursor somewhere. We need to adjust
             // the autocomplete text depending on where the new cursor is.
 
             final Editable text = getText();
             final int start = text.getSpanStart(AUTOCOMPLETE_SPAN);
@@ -710,45 +592,9 @@ public class ToolbarEditText extends Cus
                 removeAutocomplete(getText())) {
                 // Delete autocomplete text when backspacing or forward deleting.
                 return true;
             }
 
             return false;
         }
     }
-
-    private class VoiceSearchOnTouchListener implements View.OnTouchListener {
-        private int mVoiceSearchIconIndex = -1;
-        private Drawable mVoiceSearchIcon;
-
-        public VoiceSearchOnTouchListener() {
-            Drawable[] drawables = getCompoundDrawables();
-            for (int i = 0; i < drawables.length; i++) {
-                if (drawables[i] != null) {
-                    mVoiceSearchIcon = drawables[i];
-                    mVoiceSearchIconIndex = i;
-                }
-            }
-        }
-
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            boolean tapped;
-            final int action = event.getActionMasked();
-
-            switch (mVoiceSearchIconIndex) {
-                case 0:
-                    tapped = event.getX() < (getPaddingLeft() + mVoiceSearchIcon.getIntrinsicWidth());
-                    break;
-                case 2:
-                    tapped = event.getX() > (getWidth() - getPaddingRight() - mVoiceSearchIcon.getIntrinsicWidth());
-                    break;
-                default:
-                    tapped = false;
-            }
-            if (tapped && (action == MotionEvent.ACTION_DOWN)) {
-                launchVoiceRecognizer();
-            }
-            return tapped;
-        }
-    }
 }
rename from mobile/android/base/util/VoiceRecognizerUtils.java
rename to mobile/android/base/util/InputOptionsUtils.java
--- a/mobile/android/base/util/VoiceRecognizerUtils.java
+++ b/mobile/android/base/util/InputOptionsUtils.java
@@ -4,22 +4,39 @@
  * 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 class InputOptionsUtils {
     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;
     }
+
+    public static boolean supportsIntent(Intent intent, Context context) {
+        return intent.resolveActivity(context.getPackageManager()) != null;
+    }
+
+    public static boolean supportsQrCodeReader(Context context) {
+        final Intent intent = createQRCodeReaderIntent();
+        return supportsIntent(intent, context);
+    }
+
+    public static Intent createQRCodeReaderIntent() {
+        // Bug 602818 enables QR code input if you have the particular app below installed in your device
+        Intent intent = new Intent("com.google.zxing.client.android.SCAN");
+        intent.putExtra("SCAN_MODE", "QR_CODE_MODE");
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        return intent;
+    }
 }
--- a/mobile/android/tests/browser/robocop/testSettingsMenuItems.java
+++ b/mobile/android/tests/browser/robocop/testSettingsMenuItems.java
@@ -2,19 +2,21 @@ package org.mozilla.gecko.tests;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Actions;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.util.HardwareUtils;
-import org.mozilla.gecko.util.VoiceRecognizerUtils;
+import org.mozilla.gecko.util.InputOptionsUtils;
 
 /** This patch tests the Sections present in the Settings Menu and the
  *  default values for them
  */
 public class testSettingsMenuItems extends PixelTest {
     // Customize menu items.
     String[] PATH_CUSTOMIZE;
     String[][] OPTIONS_CUSTOMIZE;
@@ -223,17 +225,17 @@ public class testSettingsMenuItems exten
         }
 
         // Tablet: we don't allow a page title option.
         if (HardwareUtils.isTablet()) {
             settingsMap.get(PATH_DISPLAY).remove(TITLE_BAR_LABEL_ARR);
         }
 
         // Voice input
-        if (AppConstants.NIGHTLY_BUILD && VoiceRecognizerUtils.supportsVoiceRecognizer(this.getActivity().getApplicationContext(), this.getActivity().getResources())) {
+        if (AppConstants.NIGHTLY_BUILD && InputOptionsUtils.supportsVoiceRecognizer(this.getActivity().getApplicationContext(), this.getActivity().getResources().getString(R.string.voicesearch_prompt))) {
             String[] voiceInputUi = { mStringHelper.VOICE_INPUT_TITLE_LABEL, mStringHelper.VOICE_INPUT_SUMMARY_LABEL };
             settingsMap.get(PATH_DISPLAY).add(voiceInputUi);
         }
     }
 
     public void checkMenuHierarchy(Map<String[], List<String[]>> settingsMap) {
         // Check the items within each category.
         String section = null;