Bug 778588 - Support direct voice input from the location bar r=mhaigh

This commit is contained in:
James Hugman 2015-02-10 13:46:30 -05:00
parent 5a34a7b6bf
commit ea2e29aa06
8 changed files with 156 additions and 0 deletions

View File

@ -561,6 +561,13 @@ just addresses the organization to follow, e.g. "This site is run by " -->
<!ENTITY actionbar_menu "Menu">
<!ENTITY actionbar_done "Done">
<!-- Voice search in the awesome bar -->
<!ENTITY voicesearch_prompt "Speak now">
<!ENTITY voicesearch_failed_title "&brandShortName; Voice Search">
<!ENTITY voicesearch_failed_message "There is a problem with voice search right now. Please try later.">
<!ENTITY voicesearch_failed_message_recoverable "Sorry! We could not recognize your words. Please try again.">
<!ENTITY voicesearch_failed_retry "Try again">
<!-- Localization note (remote_tabs_last_synced): the variable is replaced by a
"relative time span string" produced by Android. This string describes the
time the tabs were last synced relative to the current time; examples

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -16,6 +16,7 @@
android:imeOptions="actionGo|flagNoExtractUi|flagNoFullscreen"
android:selectAllOnFocus="true"
android:contentDescription="@string/url_bar_default_text"
android:drawableRight="@drawable/ab_mic"
gecko:autoUpdateTheme="false"/>
</merge>

View File

@ -484,6 +484,13 @@
<string name="actionbar_menu">&actionbar_menu;</string>
<string name="actionbar_done">&actionbar_done;</string>
<!-- Voice search from the Awesome Bar -->
<string name="voicesearch_prompt">&voicesearch_prompt;</string>
<string name="voicesearch_failed_title">&voicesearch_failed_title;</string>
<string name="voicesearch_failed_message">&voicesearch_failed_message;</string>
<string name="voicesearch_failed_message_recoverable">&voicesearch_failed_message_recoverable;</string>
<string name="voicesearch_failed_retry">&voicesearch_failed_retry;</string>
<!-- Miscellaneous -->
<string name="ellipsis">&ellipsis;</string>

View File

@ -5,17 +5,28 @@
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.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.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;
@ -26,6 +37,7 @@ 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;
@ -36,6 +48,8 @@ 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.
@ -89,6 +103,7 @@ public class ToolbarEditText extends CustomEditText
setOnKeyPreImeListener(new KeyPreImeListener());
setOnSelectionChangedListener(new SelectionChangeListener());
addTextChangedListener(new TextChangeListener());
configureCompoundDrawables();
}
@Override
@ -455,6 +470,98 @@ public class ToolbarEditText extends CustomEditText
};
}
/**
* 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.
setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
return;
}
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 void launchVoiceRecognizer() {
final Intent intent = createVoiceRecognizerIntent();
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());
}
});
}
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) {
@ -598,4 +705,38 @@ public class ToolbarEditText extends CustomEditText
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;
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) {
launchVoiceRecognizer();
}
return tapped;
}
}
}