futokb/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
Tadashi G. Takaoka f5443e4ef5 Replace large device keyboard with phone style keyboard (DO NOT MERGE)
This change also fixes copyright comments.

Bug: 4442045
Change-Id: I297a2c64e709fb0d613404e5f1de45d810f9de15
2011-05-20 12:28:26 +09:00

729 lines
29 KiB
Java

/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.inputmethod.voice;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.latin.EditingUtils;
import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinIME.UIHandler;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SharedPreferencesCompat;
import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.SuggestedWords;
import com.android.inputmethod.latin.Utils;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.Browser;
import android.speech.SpeechRecognizer;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class VoiceIMEConnector implements VoiceInput.UiListener {
private static final VoiceIMEConnector sInstance = new VoiceIMEConnector();
public static final boolean VOICE_INSTALLED = true;
private static final boolean ENABLE_VOICE_BUTTON = true;
private static final String PREF_VOICE_MODE = "voice_mode";
// Whether or not the user has used voice input before (and thus, whether to show the
// first-run warning dialog or not).
private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
// Whether or not the user has used voice input from an unsupported locale UI before.
// For example, the user has a Chinese UI but activates voice input.
private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
"has_used_voice_input_unsupported_locale";
private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6;
private static final String TAG = VoiceIMEConnector.class.getSimpleName();
private static final boolean DEBUG = LatinImeLogger.sDBG;
private boolean mAfterVoiceInput;
private boolean mHasUsedVoiceInput;
private boolean mHasUsedVoiceInputUnsupportedLocale;
private boolean mImmediatelyAfterVoiceInput;
private boolean mIsShowingHint;
private boolean mLocaleSupportedForVoiceInput;
private boolean mPasswordText;
private boolean mRecognizing;
private boolean mShowingVoiceSuggestions;
private boolean mVoiceButtonEnabled;
private boolean mVoiceButtonOnPrimary;
private boolean mVoiceInputHighlighted;
private InputMethodManager mImm;
private LatinIME mService;
private AlertDialog mVoiceWarningDialog;
private VoiceInput mVoiceInput;
private final VoiceResults mVoiceResults = new VoiceResults();
private Hints mHints;
private UIHandler mHandler;
private SubtypeSwitcher mSubtypeSwitcher;
// For each word, a list of potential replacements, usually from voice.
private final Map<String, List<CharSequence>> mWordToSuggestions =
new HashMap<String, List<CharSequence>>();
public static VoiceIMEConnector init(LatinIME context, SharedPreferences prefs, UIHandler h) {
sInstance.initInternal(context, prefs, h);
return sInstance;
}
public static VoiceIMEConnector getInstance() {
return sInstance;
}
private void initInternal(LatinIME service, SharedPreferences prefs, UIHandler h) {
mService = service;
mHandler = h;
mImm = (InputMethodManager) service.getSystemService(Context.INPUT_METHOD_SERVICE);
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
if (VOICE_INSTALLED) {
mVoiceInput = new VoiceInput(service, this);
mHints = new Hints(service, prefs, new Hints.Display() {
@Override
public void showHint(int viewResource) {
View view = LayoutInflater.from(mService).inflate(viewResource, null);
mService.setCandidatesView(view);
mService.setCandidatesViewShown(true);
mIsShowingHint = true;
}
});
}
}
private VoiceIMEConnector() {
// Intentional empty constructor for singleton.
}
public void resetVoiceStates(boolean isPasswordText) {
mAfterVoiceInput = false;
mImmediatelyAfterVoiceInput = false;
mShowingVoiceSuggestions = false;
mVoiceInputHighlighted = false;
mPasswordText = isPasswordText;
}
public void flushVoiceInputLogs(boolean configurationChanged) {
if (VOICE_INSTALLED && !configurationChanged) {
if (mAfterVoiceInput) {
mVoiceInput.flushAllTextModificationCounters();
mVoiceInput.logInputEnded();
}
mVoiceInput.flushLogs();
mVoiceInput.cancel();
}
}
public void flushAndLogAllTextModificationCounters(int index, CharSequence suggestion,
String wordSeparators) {
if (mAfterVoiceInput && mShowingVoiceSuggestions) {
mVoiceInput.flushAllTextModificationCounters();
// send this intent AFTER logging any prior aggregated edits.
mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index,
wordSeparators, mService.getCurrentInputConnection());
}
}
private void showVoiceWarningDialog(final boolean swipe, IBinder token) {
if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
return;
}
AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService);
builder.setCancelable(true);
builder.setIcon(R.drawable.ic_mic_dialog);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
mVoiceInput.logKeyboardWarningDialogOk();
reallyStartListening(swipe);
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
mVoiceInput.logKeyboardWarningDialogCancel();
switchToLastInputMethod();
}
});
// When the dialog is dismissed by user's cancellation, switch back to the last input method
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface arg0) {
mVoiceInput.logKeyboardWarningDialogCancel();
switchToLastInputMethod();
}
});
final CharSequence message;
if (mLocaleSupportedForVoiceInput) {
message = TextUtils.concat(
mService.getText(R.string.voice_warning_may_not_understand), "\n\n",
mService.getText(R.string.voice_warning_how_to_turn_off));
} else {
message = TextUtils.concat(
mService.getText(R.string.voice_warning_locale_not_supported), "\n\n",
mService.getText(R.string.voice_warning_may_not_understand), "\n\n",
mService.getText(R.string.voice_warning_how_to_turn_off));
}
builder.setMessage(message);
builder.setTitle(R.string.voice_warning_title);
mVoiceWarningDialog = builder.create();
final Window window = mVoiceWarningDialog.getWindow();
final WindowManager.LayoutParams lp = window.getAttributes();
lp.token = token;
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
window.setAttributes(lp);
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
mVoiceInput.logKeyboardWarningDialogShown();
mVoiceWarningDialog.show();
}
private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder {
private AlertDialog mAlertDialog;
public UrlLinkAlertDialogBuilder(Context context) {
super(context);
}
@Override
public AlertDialog.Builder setMessage(CharSequence message) {
return super.setMessage(replaceURLSpan(message));
}
private Spanned replaceURLSpan(CharSequence message) {
// Replace all spans with the custom span
final SpannableStringBuilder ssb = new SpannableStringBuilder(message);
for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) {
int spanStart = ssb.getSpanStart(span);
int spanEnd = ssb.getSpanEnd(span);
int spanFlags = ssb.getSpanFlags(span);
ssb.removeSpan(span);
ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags);
}
return ssb;
}
@Override
public AlertDialog create() {
final AlertDialog dialog = super.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialogInterface) {
// Make URL in the dialog message click-able.
TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message);
if (textView != null) {
textView.setMovementMethod(LinkMovementMethod.getInstance());
}
}
});
mAlertDialog = dialog;
return dialog;
}
class ClickableSpan extends URLSpan {
public ClickableSpan(String url) {
super(url);
}
@Override
public void onClick(View widget) {
Uri uri = Uri.parse(getURL());
Context context = widget.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
// Add this flag to start an activity from service
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
// Dismiss the warning dialog and go back to the previous IME.
// TODO: If we can find a way to bring the new activity to front while keeping
// the warning dialog, we don't need to dismiss it here.
mAlertDialog.cancel();
context.startActivity(intent);
}
}
}
public void showPunctuationHintIfNecessary() {
InputConnection ic = mService.getCurrentInputConnection();
if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
if (mHints.showPunctuationHintIfNecessary(ic)) {
mVoiceInput.logPunctuationHintDisplayed();
}
}
mImmediatelyAfterVoiceInput = false;
}
public void hideVoiceWindow(boolean configurationChanging) {
if (!configurationChanging) {
if (mAfterVoiceInput)
mVoiceInput.logInputEnded();
if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
mVoiceInput.logKeyboardWarningDialogDismissed();
mVoiceWarningDialog.dismiss();
mVoiceWarningDialog = null;
}
if (VOICE_INSTALLED & mRecognizing) {
mVoiceInput.cancel();
}
}
mWordToSuggestions.clear();
}
public void setCursorAndSelection(int newSelEnd, int newSelStart) {
if (mAfterVoiceInput) {
mVoiceInput.setCursorPos(newSelEnd);
mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
}
}
public void setVoiceInputHighlighted(boolean b) {
mVoiceInputHighlighted = b;
}
public void setShowingVoiceSuggestions(boolean b) {
mShowingVoiceSuggestions = b;
}
public boolean isVoiceButtonEnabled() {
return mVoiceButtonEnabled;
}
public boolean isVoiceButtonOnPrimary() {
return mVoiceButtonOnPrimary;
}
public boolean isVoiceInputHighlighted() {
return mVoiceInputHighlighted;
}
public boolean isRecognizing() {
return mRecognizing;
}
public boolean needsToShowWarningDialog() {
return !mHasUsedVoiceInput
|| (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale);
}
public boolean getAndResetIsShowingHint() {
boolean ret = mIsShowingHint;
mIsShowingHint = false;
return ret;
}
private void revertVoiceInput() {
InputConnection ic = mService.getCurrentInputConnection();
if (ic != null) ic.commitText("", 1);
mService.updateSuggestions();
mVoiceInputHighlighted = false;
}
public void commitVoiceInput() {
if (VOICE_INSTALLED && mVoiceInputHighlighted) {
InputConnection ic = mService.getCurrentInputConnection();
if (ic != null) ic.finishComposingText();
mService.updateSuggestions();
mVoiceInputHighlighted = false;
}
}
public boolean logAndRevertVoiceInput() {
if (VOICE_INSTALLED && mVoiceInputHighlighted) {
mVoiceInput.incrementTextModificationDeleteCount(
mVoiceResults.candidates.get(0).toString().length());
revertVoiceInput();
return true;
} else {
return false;
}
}
public void rememberReplacedWord(CharSequence suggestion,String wordSeparators) {
if (mShowingVoiceSuggestions) {
// Retain the replaced word in the alternatives array.
String wordToBeReplaced = EditingUtils.getWordAtCursor(
mService.getCurrentInputConnection(), wordSeparators);
if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
wordToBeReplaced = wordToBeReplaced.toLowerCase();
}
if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
if (suggestions.contains(suggestion)) {
suggestions.remove(suggestion);
}
suggestions.add(wordToBeReplaced);
mWordToSuggestions.remove(wordToBeReplaced);
mWordToSuggestions.put(suggestion.toString(), suggestions);
}
}
}
/**
* Tries to apply any voice alternatives for the word if this was a spoken word and
* there are voice alternatives.
* @param touching The word that the cursor is touching, with position information
* @return true if an alternative was found, false otherwise.
*/
public boolean applyVoiceAlternatives(EditingUtils.SelectedWord touching) {
// Search for result in spoken word alternatives
String selectedWord = touching.mWord.toString().trim();
if (!mWordToSuggestions.containsKey(selectedWord)) {
selectedWord = selectedWord.toLowerCase();
}
if (mWordToSuggestions.containsKey(selectedWord)) {
mShowingVoiceSuggestions = true;
List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
SuggestedWords.Builder builder = new SuggestedWords.Builder();
// If the first letter of touching is capitalized, make all the suggestions
// start with a capital letter.
if (Character.isUpperCase(touching.mWord.charAt(0))) {
for (CharSequence word : suggestions) {
String str = word.toString();
word = Character.toUpperCase(str.charAt(0)) + str.substring(1);
builder.addWord(word);
}
} else {
builder.addWords(suggestions, null);
}
builder.setTypedWordValid(true).setHasMinimalSuggestion(true);
mService.setSuggestions(builder.build());
mService.setCandidatesViewShown(true);
return true;
}
return false;
}
public void handleBackspace() {
if (mAfterVoiceInput) {
// Don't log delete if the user is pressing delete at
// the beginning of the text box (hence not deleting anything)
if (mVoiceInput.getCursorPos() > 0) {
// If anything was selected before the delete was pressed, increment the
// delete count by the length of the selection
int deleteLen = mVoiceInput.getSelectionSpan() > 0 ?
mVoiceInput.getSelectionSpan() : 1;
mVoiceInput.incrementTextModificationDeleteCount(deleteLen);
}
}
}
public void handleCharacter() {
commitVoiceInput();
if (mAfterVoiceInput) {
// Assume input length is 1. This assumption fails for smiley face insertions.
mVoiceInput.incrementTextModificationInsertCount(1);
}
}
public void handleSeparator() {
commitVoiceInput();
if (mAfterVoiceInput){
// Assume input length is 1. This assumption fails for smiley face insertions.
mVoiceInput.incrementTextModificationInsertPunctuationCount(1);
}
}
public void handleClose() {
if (VOICE_INSTALLED & mRecognizing) {
mVoiceInput.cancel();
}
}
public void handleVoiceResults(boolean capitalizeFirstWord) {
mAfterVoiceInput = true;
mImmediatelyAfterVoiceInput = true;
InputConnection ic = mService.getCurrentInputConnection();
if (!mService.isFullscreenMode()) {
// Start listening for updates to the text from typing, etc.
if (ic != null) {
ExtractedTextRequest req = new ExtractedTextRequest();
ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
}
}
mService.vibrate();
final List<CharSequence> nBest = new ArrayList<CharSequence>();
for (String c : mVoiceResults.candidates) {
if (capitalizeFirstWord) {
c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
}
nBest.add(c);
}
if (nBest.size() == 0) {
return;
}
String bestResult = nBest.get(0).toString();
mVoiceInput.logVoiceInputDelivered(bestResult.length());
mHints.registerVoiceResult(bestResult);
if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
mService.commitTyped(ic);
EditingUtils.appendText(ic, bestResult);
if (ic != null) ic.endBatchEdit();
mVoiceInputHighlighted = true;
mWordToSuggestions.putAll(mVoiceResults.alternatives);
onCancelVoice();
}
public void switchToRecognitionStatusView(final Configuration configuration) {
mHandler.post(new Runnable() {
@Override
public void run() {
mService.setCandidatesViewShown(false);
mRecognizing = true;
mVoiceInput.newView();
View v = mVoiceInput.getView();
ViewParent p = v.getParent();
if (p != null && p instanceof ViewGroup) {
((ViewGroup) p).removeView(v);
}
View keyboardView = KeyboardSwitcher.getInstance().getInputView();
// The full height of the keyboard is difficult to calculate
// as the dimension is expressed in "mm" and not in "pixel"
// As we add mm, we don't know how the rounding is going to work
// thus we may end up with few pixels extra (or less).
if (keyboardView != null) {
View popupLayout = v.findViewById(R.id.popup_layout);
final int displayHeight =
mService.getResources().getDisplayMetrics().heightPixels;
final int currentHeight = popupLayout.getLayoutParams().height;
final int keyboardHeight = keyboardView.getHeight();
if (keyboardHeight > currentHeight || keyboardHeight
> (displayHeight / RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO)) {
popupLayout.getLayoutParams().height = keyboardHeight;
}
}
mService.setInputView(v);
mService.updateInputViewShown();
if (configuration != null) {
mVoiceInput.onConfigurationChanged(configuration);
}
}});
}
private void switchToLastInputMethod() {
final IBinder token = mService.getWindow().getWindow().getAttributes().token;
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
return mImm.switchToLastInputMethod(token);
}
@Override
protected void onPostExecute(Boolean result) {
if (!result) {
if (DEBUG) {
Log.d(TAG, "Couldn't switch back to last IME.");
}
// Needs to reset here because LatinIME failed to back to any IME and
// the same voice subtype will be triggered in the next time.
mVoiceInput.reset();
mService.requestHideSelf(0);
}
}
}.execute();
}
private void reallyStartListening(boolean swipe) {
if (!VOICE_INSTALLED) {
return;
}
if (!mHasUsedVoiceInput) {
// The user has started a voice input, so remember that in the
// future (so we don't show the warning dialog after the first run).
SharedPreferences.Editor editor =
PreferenceManager.getDefaultSharedPreferences(mService).edit();
editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
SharedPreferencesCompat.apply(editor);
mHasUsedVoiceInput = true;
}
if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) {
// The user has started a voice input from an unsupported locale, so remember that
// in the future (so we don't show the warning dialog the next time they do this).
SharedPreferences.Editor editor =
PreferenceManager.getDefaultSharedPreferences(mService).edit();
editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
SharedPreferencesCompat.apply(editor);
mHasUsedVoiceInputUnsupportedLocale = true;
}
// Clear N-best suggestions
mService.clearSuggestions();
FieldContext context = makeFieldContext();
mVoiceInput.startListening(context, swipe);
switchToRecognitionStatusView(null);
}
public void startListening(final boolean swipe, IBinder token) {
// TODO: remove swipe which is no longer used.
if (VOICE_INSTALLED) {
if (needsToShowWarningDialog()) {
// Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
showVoiceWarningDialog(swipe, token);
} else {
reallyStartListening(swipe);
}
}
}
private boolean fieldCanDoVoice(FieldContext fieldContext) {
return !mPasswordText
&& mVoiceInput != null
&& !mVoiceInput.isBlacklistedField(fieldContext);
}
private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
final boolean noMic = Utils.inPrivateImeOptions(null,
LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute)
|| Utils.inPrivateImeOptions(mService.getPackageName(),
LatinIME.IME_OPTION_NO_MICROPHONE, attribute);
return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic
&& SpeechRecognizer.isRecognitionAvailable(mService);
}
public void loadSettings(EditorInfo attribute, SharedPreferences sp) {
mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
mHasUsedVoiceInputUnsupportedLocale =
sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
mLocaleSupportedForVoiceInput = SubtypeSwitcher.getInstance().isVoiceSupported(
SubtypeSwitcher.getInstance().getInputLocaleStr());
if (VOICE_INSTALLED) {
final String voiceMode = sp.getString(PREF_VOICE_MODE,
mService.getString(R.string.voice_mode_main));
mVoiceButtonEnabled = !voiceMode.equals(mService.getString(R.string.voice_mode_off))
&& shouldShowVoiceButton(makeFieldContext(), attribute);
mVoiceButtonOnPrimary = voiceMode.equals(mService.getString(R.string.voice_mode_main));
}
}
public void destroy() {
if (VOICE_INSTALLED && mVoiceInput != null) {
mVoiceInput.destroy();
}
}
public void onStartInputView(IBinder keyboardViewToken) {
// If keyboardViewToken is null, keyboardView is not attached but voiceView is attached.
IBinder windowToken = keyboardViewToken != null ? keyboardViewToken
: mVoiceInput.getView().getWindowToken();
// If IME is in voice mode, but still needs to show the voice warning dialog,
// keep showing the warning.
if (mSubtypeSwitcher.isVoiceMode() && windowToken != null) {
// Close keyboard view if it is been shown.
if (KeyboardSwitcher.getInstance().isInputViewShown())
KeyboardSwitcher.getInstance().getInputView().purgeKeyboardAndClosing();
startListening(false, windowToken);
}
// If we have no token, onAttachedToWindow will take care of showing dialog and start
// listening.
}
public void onAttachedToWindow() {
// After onAttachedToWindow, we can show the voice warning dialog. See startListening()
// above.
mSubtypeSwitcher.setVoiceInput(mVoiceInput);
}
public void onConfigurationChanged(Configuration configuration) {
if (mRecognizing) {
switchToRecognitionStatusView(configuration);
}
}
@Override
public void onCancelVoice() {
if (mRecognizing) {
if (mSubtypeSwitcher.isVoiceMode()) {
// If voice mode is being canceled within LatinIME (i.e. time-out or user
// cancellation etc.), onCancelVoice() will be called first. LatinIME thinks it's
// still in voice mode. LatinIME needs to call switchToLastInputMethod().
// Note that onCancelVoice() will be called again from SubtypeSwitcher.
switchToLastInputMethod();
} else if (mSubtypeSwitcher.isKeyboardMode()) {
// If voice mode is being canceled out of LatinIME (i.e. by user's IME switching or
// as a result of switchToLastInputMethod() etc.),
// onCurrentInputMethodSubtypeChanged() will be called first. LatinIME will know
// that it's in keyboard mode and SubtypeSwitcher will call onCancelVoice().
mRecognizing = false;
mService.switchToKeyboardView();
}
}
}
@Override
public void onVoiceResults(List<String> candidates,
Map<String, List<CharSequence>> alternatives) {
if (!mRecognizing) {
return;
}
mVoiceResults.candidates = candidates;
mVoiceResults.alternatives = alternatives;
mHandler.updateVoiceResults();
}
private FieldContext makeFieldContext() {
SubtypeSwitcher switcher = SubtypeSwitcher.getInstance();
return new FieldContext(mService.getCurrentInputConnection(),
mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(),
switcher.getEnabledLanguages());
}
private class VoiceResults {
List<String> candidates;
Map<String, List<CharSequence>> alternatives;
}
}