зеркало из https://github.com/mozilla/gecko-dev.git
Bug 778588 - Support direct voice input from the location bar r=mhaigh
This commit is contained in:
Родитель
925318d2b4
Коммит
3dd03b9bc1
|
@ -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
|
||||
|
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 874 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 524 B |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.1 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.8 KiB |
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче