aboutsummaryrefslogtreecommitdiffstats
path: root/src/com/android/inputmethod/latin/LatinIME.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/inputmethod/latin/LatinIME.java')
-rw-r--r--src/com/android/inputmethod/latin/LatinIME.java956
1 files changed, 833 insertions, 123 deletions
diff --git a/src/com/android/inputmethod/latin/LatinIME.java b/src/com/android/inputmethod/latin/LatinIME.java
index 8b76dbd39..8b9c0cac7 100644
--- a/src/com/android/inputmethod/latin/LatinIME.java
+++ b/src/com/android/inputmethod/latin/LatinIME.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2008-2009 Google Inc.
- *
+ *
* 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
@@ -16,14 +16,20 @@
package com.android.inputmethod.latin;
+import com.google.android.collect.Lists;
+
import android.app.AlertDialog;
+import android.backup.BackupManager;
import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
@@ -41,38 +47,98 @@ import android.util.Log;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import com.android.inputmethod.voice.EditingUtil;
+import com.android.inputmethod.voice.FieldContext;
+import com.android.inputmethod.voice.SettingsUtil;
+import com.android.inputmethod.voice.VoiceInput;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
/**
* Input method implementation for Qwerty'ish keyboard.
*/
-public class LatinIME extends InputMethodService
- implements KeyboardView.OnKeyboardActionListener {
+public class LatinIME extends InputMethodService
+ implements KeyboardView.OnKeyboardActionListener,
+ VoiceInput.UiListener,
+ SharedPreferences.OnSharedPreferenceChangeListener {
+ private static final String TAG = "LatinIME";
static final boolean DEBUG = false;
static final boolean TRACE = false;
-
+ static final boolean VOICE_INSTALLED = true;
+ static final boolean ENABLE_VOICE_BUTTON = true;
+
private static final String PREF_VIBRATE_ON = "vibrate_on";
private static final String PREF_SOUND_ON = "sound_on";
private static final String PREF_AUTO_CAP = "auto_cap";
private static final String PREF_QUICK_FIXES = "quick_fixes";
private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
private static final String PREF_AUTO_COMPLETE = "auto_complete";
+ private static final String PREF_ENABLE_VOICE = "enable_voice_input";
+ private static final String PREF_VOICE_SERVER_URL = "voice_server_url";
+ private static final String PREF_VOICE_MAIN = "voice_on_main";
+
+ // 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";
+
+ // A list of locales which are supported by default for voice input, unless we get a
+ // different list from Gservices.
+ public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
+ "en " +
+ "en_US " +
+ "en_GB " +
+ "en_AU " +
+ "en_CA " +
+ "en_IE " +
+ "en_IN " +
+ "en_NZ " +
+ "en_SG " +
+ "en_ZA ";
+
+ // The private IME option used to indicate that no microphone should be shown for a
+ // given text field. For instance this is specified by the search dialog when the
+ // dialog is already showing a voice search button.
+ private static final String IME_OPTION_NO_MICROPHONE = "nm";
+
+ public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
+ public static final String PREF_INPUT_LANGUAGE = "input_language";
private static final int MSG_UPDATE_SUGGESTIONS = 0;
private static final int MSG_START_TUTORIAL = 1;
private static final int MSG_UPDATE_SHIFT_STATE = 2;
-
+ private static final int MSG_VOICE_RESULTS = 3;
+ private static final int MSG_START_LISTENING_AFTER_SWIPE = 4;
+
+ // If we detect a swipe gesture within N ms of typing, then swipe is
+ // ignored, since it may in fact be two key presses in quick succession.
+ private static final long MIN_MILLIS_AFTER_TYPING_BEFORE_SWIPE = 1000;
+
+ // If we detect a swipe gesture, and the user types N ms later, cancel the
+ // swipe since it was probably a false trigger.
+ private static final long MIN_MILLIS_AFTER_SWIPE_TO_WAIT_FOR_TYPING = 500;
+
// How many continuous deletes at which to start deleting at a higher speed.
private static final int DELETE_ACCELERATE_AT = 20;
// Key events coming any faster than this are long-presses.
@@ -84,54 +150,79 @@ public class LatinIME extends InputMethodService
// A word that is frequently typed and get's promoted to the user dictionary, uses this
// frequency.
static final int FREQUENCY_FOR_AUTO_ADD = 250;
-
+
static final int KEYCODE_ENTER = '\n';
static final int KEYCODE_SPACE = ' ';
// Contextual menu positions
private static final int POS_SETTINGS = 0;
private static final int POS_METHOD = 1;
-
+
private LatinKeyboardView mInputView;
private CandidateViewContainer mCandidateViewContainer;
private CandidateView mCandidateView;
private Suggest mSuggest;
private CompletionInfo[] mCompletions;
-
+
private AlertDialog mOptionsDialog;
-
+ private AlertDialog mVoiceWarningDialog;
+
KeyboardSwitcher mKeyboardSwitcher;
-
+
private UserDictionary mUserDictionary;
private ContactsDictionary mContactsDictionary;
private ExpandableDictionary mAutoDictionary;
-
+
+ private Hints mHints;
+
+ Resources mResources;
+
private String mLocale;
+ private LanguageSwitcher mLanguageSwitcher;
private StringBuilder mComposing = new StringBuilder();
private WordComposer mWord = new WordComposer();
private int mCommittedLength;
private boolean mPredicting;
+ private boolean mRecognizing;
+ private boolean mAfterVoiceInput;
+ private boolean mImmediatelyAfterVoiceInput;
+ private boolean mShowingVoiceSuggestions;
+ private boolean mImmediatelyAfterVoiceSuggestions;
+ private boolean mVoiceInputHighlighted;
+ private boolean mEnableVoiceButton;
private CharSequence mBestWord;
private boolean mPredictionOn;
private boolean mCompletionOn;
+ private boolean mHasDictionary;
private boolean mAutoSpace;
+ private boolean mAutoCorrectEnabled;
private boolean mAutoCorrectOn;
private boolean mCapsLock;
+ private boolean mPasswordText;
+ private boolean mEmailText;
private boolean mVibrateOn;
private boolean mSoundOn;
private boolean mAutoCap;
private boolean mQuickFixes;
+ private boolean mHasUsedVoiceInput;
+ private boolean mHasUsedVoiceInputUnsupportedLocale;
+ private boolean mLocaleSupportedForVoiceInput;
private boolean mShowSuggestions;
+ private boolean mSuggestionShouldReplaceCurrentWord;
+ private boolean mIsShowingHint;
private int mCorrectionMode;
+ private boolean mEnableVoice = true;
+ private boolean mVoiceOnPrimary;
private int mOrientation;
+ private List<CharSequence> mSuggestPuncList;
// Indicates whether the suggestion strip is to be on in landscape
private boolean mJustAccepted;
private CharSequence mJustRevertedSeparator;
private int mDeleteCount;
private long mLastKeyTime;
-
+
private Tutorial mTutorial;
private Vibrator mVibrator;
@@ -144,7 +235,19 @@ public class LatinIME extends InputMethodService
private String mWordSeparators;
private String mSentenceSeparators;
-
+ private VoiceInput mVoiceInput;
+ private VoiceResults mVoiceResults = new VoiceResults();
+ private long mSwipeTriggerTimeMillis;
+
+ // For each word, a list of potential replacements, usually from voice.
+ private Map<String, List<CharSequence>> mWordToSuggestions = new HashMap();
+
+ private class VoiceResults {
+ List<String> candidates;
+ Map<String, List<CharSequence>> alternatives;
+ }
+ private boolean mRefreshKeyboardRequired;
+
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -166,6 +269,13 @@ public class LatinIME extends InputMethodService
case MSG_UPDATE_SHIFT_STATE:
updateShiftKeyState(getCurrentInputEditorInfo());
break;
+ case MSG_VOICE_RESULTS:
+ handleVoiceResults();
+ break;
+ case MSG_START_LISTENING_AFTER_SWIPE:
+ if (mLastKeyTime < mSwipeTriggerTimeMillis) {
+ startListening(true);
+ }
}
}
};
@@ -173,36 +283,83 @@ public class LatinIME extends InputMethodService
@Override public void onCreate() {
super.onCreate();
//setStatusIcon(R.drawable.ime_qwerty);
- mKeyboardSwitcher = new KeyboardSwitcher(this);
- final Configuration conf = getResources().getConfiguration();
- initSuggest(conf.locale.toString());
+ mResources = getResources();
+ final Configuration conf = mResources.getConfiguration();
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ mLanguageSwitcher = new LanguageSwitcher(this);
+ mLanguageSwitcher.loadLocales(prefs);
+ mKeyboardSwitcher = new KeyboardSwitcher(this, this);
+ mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
+ boolean enableMultipleLanguages = mLanguageSwitcher.getLocaleCount() > 0;
+ String inputLanguage = mLanguageSwitcher.getInputLanguage();
+ if (inputLanguage == null) {
+ inputLanguage = conf.locale.toString();
+ }
+ initSuggest(inputLanguage);
mOrientation = conf.orientation;
+ initSuggestPuncList();
- mVibrateDuration = getResources().getInteger(R.integer.vibrate_duration_ms);
+ mVibrateDuration = mResources.getInteger(R.integer.vibrate_duration_ms);
// register to receive ringer mode changes for silent mode
IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION);
registerReceiver(mReceiver, filter);
+ if (VOICE_INSTALLED) {
+ mVoiceInput = new VoiceInput(this, this);
+ mHints = new Hints(this, new Hints.Display() {
+ public void showHint(int viewResource) {
+ LayoutInflater inflater = (LayoutInflater) getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ View view = inflater.inflate(viewResource, null);
+ setCandidatesView(view);
+ setCandidatesViewShown(true);
+ mIsShowingHint = true;
+ }
+ });
+ }
+ prefs.registerOnSharedPreferenceChangeListener(this);
}
-
+
private void initSuggest(String locale) {
mLocale = locale;
+
+ Resources orig = getResources();
+ Configuration conf = orig.getConfiguration();
+ Locale saveLocale = conf.locale;
+ conf.locale = new Locale(locale);
+ orig.updateConfiguration(conf, orig.getDisplayMetrics());
+ if (mSuggest != null) {
+ mSuggest.close();
+ }
mSuggest = new Suggest(this, R.raw.main);
- mSuggest.setCorrectionMode(mCorrectionMode);
+ if (mUserDictionary != null) mUserDictionary.close();
mUserDictionary = new UserDictionary(this);
- mContactsDictionary = new ContactsDictionary(this);
- mAutoDictionary = new AutoDictionary(this);
+ if (mContactsDictionary == null) {
+ mContactsDictionary = new ContactsDictionary(this);
+ }
+ // TODO: Save and restore the dictionary for the current input language.
+ if (mAutoDictionary == null) {
+ mAutoDictionary = new AutoDictionary(this);
+ }
mSuggest.setUserDictionary(mUserDictionary);
mSuggest.setContactsDictionary(mContactsDictionary);
mSuggest.setAutoDictionary(mAutoDictionary);
- mWordSeparators = getResources().getString(R.string.word_separators);
- mSentenceSeparators = getResources().getString(R.string.sentence_separators);
+ updateCorrectionMode();
+ mWordSeparators = mResources.getString(R.string.word_separators);
+ mSentenceSeparators = mResources.getString(R.string.sentence_separators);
+
+ conf.locale = saveLocale;
+ orig.updateConfiguration(conf, orig.getDisplayMetrics());
}
-
- @Override public void onDestroy() {
+
+ @Override
+ public void onDestroy() {
mUserDictionary.close();
mContactsDictionary.close();
unregisterReceiver(mReceiver);
+ if (VOICE_INSTALLED) {
+ mVoiceInput.destroy();
+ }
super.onDestroy();
}
@@ -213,13 +370,12 @@ public class LatinIME extends InputMethodService
}
// If orientation changed while predicting, commit the change
if (conf.orientation != mOrientation) {
- commitTyped(getCurrentInputConnection());
+ InputConnection ic = getCurrentInputConnection();
+ commitTyped(ic);
+ if (ic != null) ic.finishComposingText(); // For voice input
mOrientation = conf.orientation;
}
- if (mKeyboardSwitcher == null) {
- mKeyboardSwitcher = new KeyboardSwitcher(this);
- }
- mKeyboardSwitcher.makeKeyboards(true);
+ reloadKeyboards();
super.onConfigurationChanged(conf);
}
@@ -230,11 +386,28 @@ public class LatinIME extends InputMethodService
mKeyboardSwitcher.setInputView(mInputView);
mKeyboardSwitcher.makeKeyboards(true);
mInputView.setOnKeyboardActionListener(this);
- mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 0);
+ mKeyboardSwitcher.setKeyboardMode(
+ KeyboardSwitcher.MODE_TEXT, 0,
+ shouldShowVoiceButton(makeFieldContext(), getCurrentInputEditorInfo()));
return mInputView;
}
@Override
+ public void onInitializeInterface() {
+ // Create a new view associated with voice input if the old
+ // view is stuck in another layout (e.g. if switching from
+ // portrait to landscape while speaking)
+ // NOTE: This must be done here because for some reason
+ // onCreateInputView isn't called after an orientation change while
+ // speech rec is in progress.
+ if (mVoiceInput != null && mVoiceInput.getView().getParent() != null) {
+ mVoiceInput.newView();
+ }
+
+ super.onInitializeInterface();
+ }
+
+ @Override
public View onCreateCandidatesView() {
mKeyboardSwitcher.makeKeyboards(true);
mCandidateViewContainer = (CandidateViewContainer) getLayoutInflater().inflate(
@@ -246,43 +419,71 @@ public class LatinIME extends InputMethodService
return mCandidateViewContainer;
}
- @Override
+ @Override
public void onStartInputView(EditorInfo attribute, boolean restarting) {
// In landscape mode, this method gets called without the input view being created.
if (mInputView == null) {
return;
}
+ if (mRefreshKeyboardRequired) {
+ mRefreshKeyboardRequired = false;
+ toggleLanguage(true, true);
+ }
+
mKeyboardSwitcher.makeKeyboards(false);
TextEntryState.newSession(this);
+ // Most such things we decide below in the switch statement, but we need to know
+ // now whether this is a password text field, because we need to know now (before
+ // the switch statement) whether we want to enable the voice button.
+ mPasswordText = false;
+ int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
+ if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD ||
+ variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
+ mPasswordText = true;
+ }
+
+ mEnableVoiceButton = shouldShowVoiceButton(makeFieldContext(), attribute);
+ final boolean enableVoiceButton = mEnableVoiceButton && mEnableVoice;
+
+ mAfterVoiceInput = false;
+ mImmediatelyAfterVoiceInput = false;
+ mShowingVoiceSuggestions = false;
+ mImmediatelyAfterVoiceSuggestions = false;
+ mVoiceInputHighlighted = false;
boolean disableAutoCorrect = false;
+ mWordToSuggestions.clear();
+ mInputTypeNoAutoCorrect = false;
mPredictionOn = false;
mCompletionOn = false;
mCompletions = null;
mCapsLock = false;
- switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) {
+ mEmailText = false;
+ switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) {
case EditorInfo.TYPE_CLASS_NUMBER:
case EditorInfo.TYPE_CLASS_DATETIME:
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_SYMBOLS,
- attribute.imeOptions);
+ attribute.imeOptions, enableVoiceButton);
break;
case EditorInfo.TYPE_CLASS_PHONE:
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE,
- attribute.imeOptions);
+ attribute.imeOptions, enableVoiceButton);
break;
case EditorInfo.TYPE_CLASS_TEXT:
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
- attribute.imeOptions);
+ attribute.imeOptions, enableVoiceButton);
//startPrediction();
mPredictionOn = true;
// Make sure that passwords are not displayed in candidate view
- int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD ||
variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ) {
mPredictionOn = false;
}
+ if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
+ mEmailText = true;
+ }
if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|| variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
mAutoSpace = false;
@@ -292,33 +493,35 @@ public class LatinIME extends InputMethodService
if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
mPredictionOn = false;
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL,
- attribute.imeOptions);
+ attribute.imeOptions, enableVoiceButton);
} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
mPredictionOn = false;
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL,
- attribute.imeOptions);
+ attribute.imeOptions, enableVoiceButton);
} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM,
- attribute.imeOptions);
+ attribute.imeOptions, enableVoiceButton);
} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
mPredictionOn = false;
} else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
+ mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_WEB,
+ attribute.imeOptions, enableVoiceButton);
// If it's a browser edit field and auto correct is not ON explicitly, then
// disable auto correction, but keep suggestions on.
if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
- disableAutoCorrect = true;
+ mInputTypeNoAutoCorrect = true;
}
}
// If NO_SUGGESTIONS is set, don't do prediction.
if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
mPredictionOn = false;
- disableAutoCorrect = true;
+ mInputTypeNoAutoCorrect = true;
}
// If it's not multiline and the autoCorrect flag is not set, then don't correct
if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 &&
(attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
- disableAutoCorrect = true;
+ mInputTypeNoAutoCorrect = true;
}
if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
mPredictionOn = false;
@@ -328,16 +531,18 @@ public class LatinIME extends InputMethodService
break;
default:
mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
- attribute.imeOptions);
+ attribute.imeOptions, enableVoiceButton);
updateShiftKeyState(attribute);
}
mInputView.closing();
mComposing.setLength(0);
mPredicting = false;
mDeleteCount = 0;
- setCandidatesViewShown(false);
- if (mCandidateView != null) mCandidateView.setSuggestions(null, false, false, false);
loadSettings();
+
+ setCandidatesViewShown(false);
+ setSuggestions(null, false, false, false);
+
// Override auto correct
if (disableAutoCorrect) {
mAutoCorrectOn = false;
@@ -345,10 +550,12 @@ public class LatinIME extends InputMethodService
mCorrectionMode = Suggest.CORRECTION_BASIC;
}
}
+ // If the dictionary is not big enough, don't auto correct
+ mHasDictionary = mSuggest.hasMainDictionary();
+
+ updateCorrectionMode();
+
mInputView.setProximityCorrectionEnabled(true);
- if (mSuggest != null) {
- mSuggest.setCorrectionMode(mCorrectionMode);
- }
mPredictionOn = mPredictionOn && mCorrectionMode > 0;
checkTutorial(attribute.privateImeOptions);
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
@@ -358,9 +565,34 @@ public class LatinIME extends InputMethodService
public void onFinishInput() {
super.onFinishInput();
+ if (VOICE_INSTALLED && mAfterVoiceInput) {
+ mVoiceInput.logInputEnded();
+ }
+
+ if (VOICE_INSTALLED) {
+ mVoiceInput.flushLogs();
+ }
+
if (mInputView != null) {
mInputView.closing();
}
+ if (VOICE_INSTALLED && mRecognizing) {
+ mVoiceInput.cancel();
+ }
+ }
+
+ @Override
+ public void onUpdateExtractedText(int token, ExtractedText text) {
+ super.onUpdateExtractedText(token, text);
+ InputConnection ic = getCurrentInputConnection();
+ if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
+ mVoiceInput.logTextModified();
+
+ if (mHints.showPunctuationHintIfNecessary(ic)) {
+ mVoiceInput.logPunctuationHintDisplayed();
+ }
+ }
+ mImmediatelyAfterVoiceInput = false;
}
@Override
@@ -369,10 +601,22 @@ public class LatinIME extends InputMethodService
int candidatesStart, int candidatesEnd) {
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
candidatesStart, candidatesEnd);
+
+ if (DEBUG) {
+ Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
+ + ", ose=" + oldSelEnd
+ + ", nss=" + newSelStart
+ + ", nse=" + newSelEnd
+ + ", cs=" + candidatesStart
+ + ", ce=" + candidatesEnd);
+ }
+
+ mSuggestionShouldReplaceCurrentWord = false;
// If the current selection in the text view changes, we should
// clear whatever candidate text we have.
- if (mComposing.length() > 0 && mPredicting && (newSelStart != candidatesEnd
- || newSelEnd != candidatesEnd)) {
+ if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
+ && (newSelStart != candidatesEnd
+ || newSelEnd != candidatesEnd))) {
mComposing.setLength(0);
mPredicting = false;
updateSuggestions();
@@ -381,25 +625,58 @@ public class LatinIME extends InputMethodService
if (ic != null) {
ic.finishComposingText();
}
+ mVoiceInputHighlighted = false;
} else if (!mPredicting && !mJustAccepted
&& TextEntryState.getState() == TextEntryState.STATE_ACCEPTED_DEFAULT) {
TextEntryState.reset();
}
mJustAccepted = false;
postUpdateShiftKeyState();
+
+ if (VOICE_INSTALLED) {
+ if (mShowingVoiceSuggestions) {
+ if (mImmediatelyAfterVoiceSuggestions) {
+ mImmediatelyAfterVoiceSuggestions = false;
+ } else {
+ updateSuggestions();
+ mShowingVoiceSuggestions = false;
+ }
+ }
+ if (VoiceInput.ENABLE_WORD_CORRECTIONS) {
+ // If we have alternatives for the current word, then show them.
+ String word = EditingUtil.getWordAtCursor(
+ getCurrentInputConnection(), getWordSeparators());
+ if (word != null && mWordToSuggestions.containsKey(word.trim())) {
+ mSuggestionShouldReplaceCurrentWord = true;
+ final List<CharSequence> suggestions = mWordToSuggestions.get(word.trim());
+
+ setSuggestions(suggestions, false, true, true);
+ setCandidatesViewShown(true);
+ }
+ }
+ }
}
@Override
public void hideWindow() {
+ if (mAfterVoiceInput) mVoiceInput.logInputEnded();
if (TRACE) Debug.stopMethodTracing();
if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
mOptionsDialog.dismiss();
mOptionsDialog = null;
}
+ if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
+ mVoiceInput.logKeyboardWarningDialogDismissed();
+ mVoiceWarningDialog.dismiss();
+ mVoiceWarningDialog = null;
+ }
if (mTutorial != null) {
mTutorial.close();
mTutorial = null;
}
+ if (VOICE_INSTALLED & mRecognizing) {
+ mVoiceInput.cancel();
+ }
super.hideWindow();
TextEntryState.endSession();
}
@@ -415,17 +692,17 @@ public class LatinIME extends InputMethodService
if (mCompletionOn) {
mCompletions = completions;
if (completions == null) {
- mCandidateView.setSuggestions(null, false, false, false);
+ setSuggestions(null, false, false, false);
return;
}
-
+
List<CharSequence> stringList = new ArrayList<CharSequence>();
for (int i=0; i<(completions != null ? completions.length : 0); i++) {
CompletionInfo ci = completions[i];
if (ci != null) stringList.add(ci.getText());
}
//CharSequence typedWord = mWord.getTypedWord();
- mCandidateView.setSuggestions(stringList, true, true, true);
+ setSuggestions(stringList, true, true, true);
mBestWord = null;
setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
}
@@ -438,7 +715,7 @@ public class LatinIME extends InputMethodService
super.setCandidatesViewShown(shown);
}
}
-
+
@Override
public void onComputeInsets(InputMethodService.Insets outInsets) {
super.onComputeInsets(outInsets);
@@ -446,7 +723,7 @@ public class LatinIME extends InputMethodService
outInsets.contentTopInsets = outInsets.visibleTopInsets;
}
}
-
+
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
@@ -486,7 +763,7 @@ public class LatinIME extends InputMethodService
}
// Enable shift key and DPAD to do selections
if (mInputView != null && mInputView.isShown() && mInputView.isShifted()) {
- event = new KeyEvent(event.getDownTime(), event.getEventTime(),
+ event = new KeyEvent(event.getDownTime(), event.getEventTime(),
event.getAction(), event.getKeyCode(), event.getRepeatCount(),
event.getDeviceId(), event.getScanCode(),
KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
@@ -499,6 +776,31 @@ public class LatinIME extends InputMethodService
return super.onKeyUp(keyCode, event);
}
+ private void revertVoiceInput() {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) ic.commitText("", 1);
+ updateSuggestions();
+ mVoiceInputHighlighted = false;
+ }
+
+ private void commitVoiceInput() {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) ic.finishComposingText();
+ updateSuggestions();
+ mVoiceInputHighlighted = false;
+ }
+
+ private void reloadKeyboards() {
+ if (mKeyboardSwitcher == null) {
+ mKeyboardSwitcher = new KeyboardSwitcher(this, this);
+ }
+ mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
+ if (mInputView != null) {
+ mKeyboardSwitcher.setVoiceMode(mEnableVoice, mVoiceOnPrimary);
+ }
+ mKeyboardSwitcher.makeKeyboards(true);
+ }
+
private void commitTyped(InputConnection inputConnection) {
if (mPredicting) {
mPredicting = false;
@@ -523,15 +825,19 @@ public class LatinIME extends InputMethodService
InputConnection ic = getCurrentInputConnection();
if (attr != null && mInputView != null && mKeyboardSwitcher.isAlphabetMode()
&& ic != null) {
- int caps = 0;
- EditorInfo ei = getCurrentInputEditorInfo();
- if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
- caps = ic.getCursorCapsMode(attr.inputType);
- }
- mInputView.setShifted(mCapsLock || caps != 0);
+ mInputView.setShifted(mCapsLock || getCursorCapsMode(ic, attr) != 0);
}
}
-
+
+ private int getCursorCapsMode(InputConnection ic, EditorInfo attr) {
+ int caps = 0;
+ EditorInfo ei = getCurrentInputEditorInfo();
+ if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
+ caps = ic.getCursorCapsMode(attr.inputType);
+ }
+ return caps;
+ }
+
private void swapPunctuationAndSpace() {
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
@@ -545,7 +851,7 @@ public class LatinIME extends InputMethodService
updateShiftKeyState(getCurrentInputEditorInfo());
}
}
-
+
private void doubleSpace() {
//if (!mAutoPunctuate) return;
if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
@@ -562,7 +868,20 @@ public class LatinIME extends InputMethodService
updateShiftKeyState(getCurrentInputEditorInfo());
}
}
-
+
+ private void maybeRemovePreviousPeriod(CharSequence text) {
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+
+ // When the text's first character is '.', remove the previous period
+ // if there is one.
+ CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
+ if (lastOne != null && lastOne.length() == 1 && lastOne.charAt(0) == '.'
+ && text.charAt(0) == '.') {
+ ic.deleteSurroundingText(1, 0);
+ }
+ }
+
public boolean addWordToDictionary(String word) {
mUserDictionary.addWord(word, 128);
return true;
@@ -575,12 +894,12 @@ public class LatinIME extends InputMethodService
return false;
}
}
-
+
// Implementation of KeyboardViewListener
public void onKey(int primaryCode, int[] keyCodes) {
long when = SystemClock.uptimeMillis();
- if (primaryCode != Keyboard.KEYCODE_DELETE ||
+ if (primaryCode != Keyboard.KEYCODE_DELETE ||
when > mLastKeyTime + QUICK_PRESS) {
mDeleteCount = 0;
}
@@ -601,6 +920,12 @@ public class LatinIME extends InputMethodService
case LatinKeyboardView.KEYCODE_OPTIONS:
showOptionsMenu();
break;
+ case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE:
+ toggleLanguage(false, true);
+ break;
+ case LatinKeyboardView.KEYCODE_PREV_LANGUAGE:
+ toggleLanguage(false, false);
+ break;
case LatinKeyboardView.KEYCODE_SHIFT_LONGPRESS:
if (mCapsLock) {
handleShift();
@@ -611,6 +936,14 @@ public class LatinIME extends InputMethodService
case Keyboard.KEYCODE_MODE_CHANGE:
changeKeyboardMode();
break;
+ case LatinKeyboardView.KEYCODE_VOICE:
+ if (VOICE_INSTALLED) {
+ startListening(false /* was a button press, was not a swipe */);
+ }
+ break;
+ case 9 /*Tab*/:
+ sendKeyChar((char) primaryCode);
+ break;
default:
if (isWordSeparator(primaryCode)) {
handleSeparator(primaryCode);
@@ -624,7 +957,7 @@ public class LatinIME extends InputMethodService
changeKeyboardMode();
}
}
-
+
public void onText(CharSequence text) {
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
@@ -632,6 +965,7 @@ public class LatinIME extends InputMethodService
if (mPredicting) {
commitTyped(ic);
}
+ maybeRemovePreviousPeriod(text);
ic.commitText(text, 1);
ic.endBatchEdit();
updateShiftKeyState(getCurrentInputEditorInfo());
@@ -639,6 +973,10 @@ public class LatinIME extends InputMethodService
}
private void handleBackspace() {
+ if (VOICE_INSTALLED && mVoiceInputHighlighted) {
+ revertVoiceInput();
+ return;
+ }
boolean deleteChar = false;
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
@@ -673,7 +1011,7 @@ public class LatinIME extends InputMethodService
}
private void handleShift() {
- Keyboard currentKeyboard = mInputView.getKeyboard();
+ mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
if (mKeyboardSwitcher.isAlphabetMode()) {
// Alphabet keyboard
checkToggleCapsLock();
@@ -682,8 +1020,11 @@ public class LatinIME extends InputMethodService
mKeyboardSwitcher.toggleShift();
}
}
-
+
private void handleCharacter(int primaryCode, int[] keyCodes) {
+ if (VOICE_INSTALLED && mVoiceInputHighlighted) {
+ commitVoiceInput();
+ }
if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
if (!mPredicting) {
mPredicting = true;
@@ -707,6 +1048,11 @@ public class LatinIME extends InputMethodService
mWord.add(primaryCode, keyCodes);
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
+ // If it's the first letter, make note of auto-caps state
+ if (mWord.size() == 1) {
+ mWord.setAutoCapitalized(
+ getCursorCapsMode(ic, getCurrentInputEditorInfo()) != 0);
+ }
ic.setComposingText(mComposing, 1);
}
postUpdateSuggestions();
@@ -719,6 +1065,9 @@ public class LatinIME extends InputMethodService
}
private void handleSeparator(int primaryCode) {
+ if (VOICE_INSTALLED && mVoiceInputHighlighted) {
+ commitVoiceInput();
+ }
boolean pickedDefault = false;
// Handle separator
InputConnection ic = getCurrentInputConnection();
@@ -727,12 +1076,12 @@ public class LatinIME extends InputMethodService
}
if (mPredicting) {
// In certain languages where single quote is a separator, it's better
- // not to auto correct, but accept the typed word. For instance,
+ // not to auto correct, but accept the typed word. For instance,
// in Italian dov' should not be expanded to dove' because the elision
// requires the last vowel to be removed.
- if (mAutoCorrectOn && primaryCode != '\'' &&
- (mJustRevertedSeparator == null
- || mJustRevertedSeparator.length() == 0
+ if (mAutoCorrectOn && primaryCode != '\'' &&
+ (mJustRevertedSeparator == null
+ || mJustRevertedSeparator.length() == 0
|| mJustRevertedSeparator.charAt(0) != primaryCode)) {
pickDefaultSuggestion();
pickedDefault = true;
@@ -742,10 +1091,10 @@ public class LatinIME extends InputMethodService
}
sendKeyChar((char)primaryCode);
TextEntryState.typedCharacter((char) primaryCode, true);
- if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED
+ if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED
&& primaryCode != KEYCODE_ENTER) {
swapPunctuationAndSpace();
- } else if (isPredictionOn() && primaryCode == ' ') {
+ } else if (isPredictionOn() && primaryCode == ' ') {
//else if (TextEntryState.STATE_SPACE_AFTER_ACCEPTED) {
doubleSpace();
}
@@ -757,9 +1106,12 @@ public class LatinIME extends InputMethodService
ic.endBatchEdit();
}
}
-
+
private void handleClose() {
commitTyped(getCurrentInputConnection());
+ if (VOICE_INSTALLED & mRecognizing) {
+ mVoiceInput.cancel();
+ }
requestHideSelf(0);
mInputView.closing();
TextEntryState.endSession();
@@ -770,7 +1122,7 @@ public class LatinIME extends InputMethodService
toggleCapsLock();
}
}
-
+
private void toggleCapsLock() {
mCapsLock = !mCapsLock;
if (mKeyboardSwitcher.isAlphabetMode()) {
@@ -782,25 +1134,219 @@ public class LatinIME extends InputMethodService
mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100);
}
-
+
private boolean isPredictionOn() {
boolean predictionOn = mPredictionOn;
//if (isFullscreenMode()) predictionOn &= mPredictionLandscape;
return predictionOn;
}
-
+
private boolean isCandidateStripVisible() {
return isPredictionOn() && mShowSuggestions;
}
+ public void onCancelVoice() {
+ if (mRecognizing) {
+ switchToKeyboardView();
+ }
+ }
+
+ private void switchToKeyboardView() {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mRecognizing = false;
+ if (mInputView != null) {
+ setInputView(mInputView);
+ }
+ updateInputViewShown();
+ }});
+ }
+
+ private void switchToRecognitionStatusView() {
+ mHandler.post(new Runnable() {
+ public void run() {
+ mRecognizing = true;
+ setInputView(mVoiceInput.getView());
+ updateInputViewShown();
+ }});
+ }
+
+ private void startListening(boolean swipe) {
+ if (!mHasUsedVoiceInput ||
+ (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) {
+ // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel.
+ showVoiceWarningDialog(swipe);
+ } else {
+ reallyStartListening(swipe);
+ }
+ }
+
+ private void reallyStartListening(boolean swipe) {
+ 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(this).edit();
+ editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
+ editor.commit();
+ 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(this).edit();
+ editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
+ editor.commit();
+ mHasUsedVoiceInputUnsupportedLocale = true;
+ }
+
+ // Clear N-best suggestions
+ setSuggestions(null, false, false, true);
+
+ FieldContext context = new FieldContext(
+ getCurrentInputConnection(),
+ getCurrentInputEditorInfo(),
+ mLanguageSwitcher.getInputLanguage(),
+ mLanguageSwitcher.getEnabledLanguages());
+ mVoiceInput.startListening(context, swipe);
+ switchToRecognitionStatusView();
+ }
+
+ private void showVoiceWarningDialog(final boolean swipe) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setCancelable(true);
+ builder.setIcon(R.drawable.ic_mic_dialog);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mVoiceInput.logKeyboardWarningDialogOk();
+ reallyStartListening(swipe);
+ }
+ });
+ builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton) {
+ mVoiceInput.logKeyboardWarningDialogCancel();
+ }
+ });
+
+ if (mLocaleSupportedForVoiceInput) {
+ String message = getString(R.string.voice_warning_may_not_understand) + "\n\n" +
+ getString(R.string.voice_warning_how_to_turn_off);
+ builder.setMessage(message);
+ } else {
+ String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n" +
+ getString(R.string.voice_warning_may_not_understand) + "\n\n" +
+ getString(R.string.voice_warning_how_to_turn_off);
+ builder.setMessage(message);
+ }
+
+ builder.setTitle(R.string.voice_warning_title);
+ mVoiceWarningDialog = builder.create();
+
+ Window window = mVoiceWarningDialog.getWindow();
+ WindowManager.LayoutParams lp = window.getAttributes();
+ lp.token = mInputView.getWindowToken();
+ lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+ window.setAttributes(lp);
+ window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+ mVoiceInput.logKeyboardWarningDialogShown();
+ mVoiceWarningDialog.show();
+ }
+
+ public void onVoiceResults(List<String> candidates,
+ Map<String, List<CharSequence>> alternatives) {
+ if (!mRecognizing) {
+ return;
+ }
+ mVoiceResults.candidates = candidates;
+ mVoiceResults.alternatives = alternatives;
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_VOICE_RESULTS));
+ }
+
+ private void handleVoiceResults() {
+ mAfterVoiceInput = true;
+ mImmediatelyAfterVoiceInput = true;
+
+ InputConnection ic = getCurrentInputConnection();
+ if (!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);
+ }
+ }
+
+ vibrate();
+ switchToKeyboardView();
+
+ final List<CharSequence> nBest = new ArrayList<CharSequence>();
+ boolean capitalizeFirstWord = preferCapitalization()
+ || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted());
+ 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();
+
+ mHints.registerVoiceResult(bestResult);
+
+ if (ic != null) ic.beginBatchEdit(); // To avoid extra updates on committing older text
+
+ commitTyped(ic);
+ EditingUtil.appendText(ic, bestResult);
+
+ if (ic != null) ic.endBatchEdit();
+
+ // Show N-Best alternates, if there is more than one choice.
+ if (nBest.size() > 1) {
+ mImmediatelyAfterVoiceSuggestions = true;
+ mShowingVoiceSuggestions = true;
+ setSuggestions(nBest.subList(1, nBest.size()), false, true, true);
+ setCandidatesViewShown(true);
+ }
+ mVoiceInputHighlighted = true;
+ mWordToSuggestions.putAll(mVoiceResults.alternatives);
+
+ }
+
+ private void setSuggestions(
+ List<CharSequence> suggestions,
+ boolean completions,
+
+ boolean typedWordValid,
+ boolean haveMinimalSuggestion) {
+
+ if (mIsShowingHint) {
+ setCandidatesView(mCandidateViewContainer);
+ mIsShowingHint = false;
+ }
+
+ if (mCandidateView != null) {
+ mCandidateView.setSuggestions(
+ suggestions, completions, typedWordValid, haveMinimalSuggestion);
+ }
+ }
+
private void updateSuggestions() {
+ mSuggestionShouldReplaceCurrentWord = false;
+
// Check if we have a suggestion engine attached.
- if (mSuggest == null || !isPredictionOn()) {
+ if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) {
return;
}
-
+
if (!mPredicting) {
- mCandidateView.setSuggestions(null, false, false, false);
+ setNextSuggestions();
return;
}
@@ -809,14 +1355,15 @@ public class LatinIME extends InputMethodService
//|| mCorrectionMode == mSuggest.CORRECTION_FULL;
CharSequence typedWord = mWord.getTypedWord();
// If we're in basic correct
- boolean typedWordValid = mSuggest.isValidWord(typedWord);
+ boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
+ (preferCapitalization() && mSuggest.isValidWord(typedWord.toString().toLowerCase()));
if (mCorrectionMode == Suggest.CORRECTION_FULL) {
correctionAvailable |= typedWordValid;
}
// Don't auto-correct words with multiple capital letter
correctionAvailable &= !mWord.isMostlyCaps();
- mCandidateView.setSuggestions(stringList, false, typedWordValid, correctionAvailable);
+ setSuggestions(stringList, false, typedWordValid, correctionAvailable);
if (stringList.size() > 0) {
if (correctionAvailable && !typedWordValid && stringList.size() > 1) {
mBestWord = stringList.get(1);
@@ -843,6 +1390,8 @@ public class LatinIME extends InputMethodService
}
public void pickSuggestionManually(int index, CharSequence suggestion) {
+ if (mAfterVoiceInput && mShowingVoiceSuggestions) mVoiceInput.logNBestChoose(index);
+
if (mCompletionOn && mCompletions != null && index >= 0
&& index < mCompletions.length) {
CompletionInfo ci = mCompletions[index];
@@ -857,6 +1406,12 @@ public class LatinIME extends InputMethodService
updateShiftKeyState(getCurrentInputEditorInfo());
return;
}
+
+ // If this is a punctuation, apply it through the normal key press
+ if (suggestion.length() == 1 && isWordSeparator(suggestion.charAt(0))) {
+ onKey(suggestion.charAt(0), null);
+ return;
+ }
pickSuggestion(suggestion);
TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
// Follow it with a space
@@ -866,18 +1421,23 @@ public class LatinIME extends InputMethodService
// Fool the state watcher so that a subsequent backspace will not do a revert
TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
}
-
+
private void pickSuggestion(CharSequence suggestion) {
if (mCapsLock) {
suggestion = suggestion.toString().toUpperCase();
- } else if (preferCapitalization()
+ } else if (preferCapitalization()
|| (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted())) {
suggestion = suggestion.toString().toUpperCase().charAt(0)
+ suggestion.subSequence(1, suggestion.length()).toString();
}
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
- ic.commitText(suggestion, 1);
+ if (mSuggestionShouldReplaceCurrentWord) {
+ EditingUtil.deleteWordAtCursor(ic, getWordSeparators());
+ }
+ if (!VoiceInput.DELETE_SYMBOL.equals(suggestion)) {
+ ic.commitText(suggestion, 1);
+ }
}
// Add the word to the auto dictionary if it's not a known word
if (mAutoDictionary.isValidWord(suggestion) || !mSuggest.isValidWord(suggestion)) {
@@ -885,12 +1445,14 @@ public class LatinIME extends InputMethodService
}
mPredicting = false;
mCommittedLength = suggestion.length();
- if (mCandidateView != null) {
- mCandidateView.setSuggestions(null, false, false, false);
- }
+ setNextSuggestions();
updateShiftKeyState(getCurrentInputEditorInfo());
}
+ private void setNextSuggestions() {
+ setSuggestions(mSuggestPuncList, false, false, false);
+ }
+
private boolean isCursorTouchingWord() {
InputConnection ic = getCurrentInputConnection();
if (ic == null) return false;
@@ -900,13 +1462,13 @@ public class LatinIME extends InputMethodService
&& !isWordSeparator(toLeft.charAt(0))) {
return true;
}
- if (!TextUtils.isEmpty(toRight)
+ if (!TextUtils.isEmpty(toRight)
&& !isWordSeparator(toRight.charAt(0))) {
return true;
}
return false;
}
-
+
public void revertLastWord(boolean deleteChar) {
final int length = mComposing.length();
if (!mPredicting && length > 0) {
@@ -917,7 +1479,7 @@ public class LatinIME extends InputMethodService
if (deleteChar) ic.deleteSurroundingText(1, 0);
int toDelete = mCommittedLength;
CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
- if (toTheLeft != null && toTheLeft.length() > 0
+ if (toTheLeft != null && toTheLeft.length() > 0
&& isWordSeparator(toTheLeft.charAt(0))) {
toDelete--;
}
@@ -935,7 +1497,7 @@ public class LatinIME extends InputMethodService
protected String getWordSeparators() {
return mWordSeparators;
}
-
+
public boolean isWordSeparator(int code) {
String separators = getWordSeparators();
return separators.contains(String.valueOf((char)code));
@@ -956,6 +1518,11 @@ public class LatinIME extends InputMethodService
}
public void swipeRight() {
+ if (userHasNotTypedRecently() && VOICE_INSTALLED && mEnableVoice &&
+ fieldCanDoVoice(makeFieldContext())) {
+ startListening(true /* was a swipe */);
+ }
+
if (LatinKeyboardView.DEBUG_AUTO_PLAY) {
ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE));
CharSequence text = cm.getText();
@@ -964,9 +1531,36 @@ public class LatinIME extends InputMethodService
}
}
}
-
+
+ private void toggleLanguage(boolean reset, boolean next) {
+ if (reset) {
+ mLanguageSwitcher.reset();
+ } else {
+ if (next) {
+ mLanguageSwitcher.next();
+ } else {
+ mLanguageSwitcher.prev();
+ }
+ }
+ int currentKeyboardMode = mKeyboardSwitcher.getKeyboardMode();
+ reloadKeyboards();
+ mKeyboardSwitcher.makeKeyboards(true);
+ mKeyboardSwitcher.setKeyboardMode(currentKeyboardMode, 0,
+ mEnableVoiceButton && mEnableVoice);
+ initSuggest(mLanguageSwitcher.getInputLanguage());
+ mLanguageSwitcher.persist();
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ }
+
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ String key) {
+ if (PREF_SELECTED_LANGUAGES.equals(key)) {
+ mLanguageSwitcher.loadLocales(sharedPreferences);
+ mRefreshKeyboardRequired = true;
+ }
+ }
+
public void swipeLeft() {
- //handleBackspace();
}
public void swipeDown() {
@@ -983,9 +1577,37 @@ public class LatinIME extends InputMethodService
}
public void onRelease(int primaryCode) {
+ // Reset any drag flags in the keyboard
+ ((LatinKeyboard) mInputView.getKeyboard()).keyReleased();
//vibrate();
}
+ private FieldContext makeFieldContext() {
+ return new FieldContext(
+ getCurrentInputConnection(),
+ getCurrentInputEditorInfo(),
+ mLanguageSwitcher.getInputLanguage(),
+ mLanguageSwitcher.getEnabledLanguages());
+ }
+
+ private boolean fieldCanDoVoice(FieldContext fieldContext) {
+ return !mPasswordText
+ && mVoiceInput != null
+ && !mVoiceInput.isBlacklistedField(fieldContext);
+ }
+
+ private boolean fieldIsRecommendedForVoice(FieldContext fieldContext) {
+ // TODO: Move this logic into the VoiceInput method.
+ return !mPasswordText && !mEmailText && mVoiceInput.isRecommendedField(fieldContext);
+ }
+
+ private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
+ return ENABLE_VOICE_BUTTON
+ && fieldCanDoVoice(fieldContext)
+ && !(attribute != null && attribute.privateImeOptions != null
+ && attribute.privateImeOptions.equals(IME_OPTION_NO_MICROPHONE));
+ }
+
// receive ringer mode changes to detect silent mode
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
@@ -1004,6 +1626,26 @@ public class LatinIME extends InputMethodService
}
}
+ private boolean userHasNotTypedRecently() {
+ return (SystemClock.uptimeMillis() - mLastKeyTime)
+ > MIN_MILLIS_AFTER_TYPING_BEFORE_SWIPE;
+ }
+
+ /*
+ * Only trigger a swipe action if the user hasn't typed X millis before
+ * now, and if they don't type Y millis after the swipe is detected. This
+ * delays the onset of the swipe action by Y millis.
+ */
+ private void conservativelyTriggerSwipeAction(final Runnable action) {
+ if (userHasNotTypedRecently()) {
+ mSwipeTriggerTimeMillis = System.currentTimeMillis();
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_START_LISTENING_AFTER_SWIPE),
+ MIN_MILLIS_AFTER_SWIPE_TO_WAIT_FOR_TYPING);
+ }
+ }
+
+
private void playKeyClick(int primaryCode) {
// if mAudioManager is null, we don't have the ringer state yet
// mAudioManager will be set by updateRingerMode
@@ -1053,7 +1695,7 @@ public class LatinIME extends InputMethodService
}
}
}
-
+
private void startTutorial() {
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_START_TUTORIAL), 500);
}
@@ -1067,10 +1709,26 @@ public class LatinIME extends InputMethodService
mUserDictionary.addWord(word, frequency);
}
- private void launchSettings() {
+ private void updateCorrectionMode() {
+ mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
+ mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes)
+ && !mInputTypeNoAutoCorrect && mHasDictionary;
+ mCorrectionMode = mAutoCorrectOn
+ ? Suggest.CORRECTION_FULL
+ : (mQuickFixes ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
+ if (mSuggest != null) {
+ mSuggest.setCorrectionMode(mCorrectionMode);
+ }
+ }
+
+ protected void launchSettings() {
+ launchSettings(LatinIMESettings.class);
+ }
+
+ protected void launchSettings(Class settingsClass) {
handleClose();
Intent intent = new Intent();
- intent.setClass(LatinIME.this, LatinIMESettings.class);
+ intent.setClass(LatinIME.this, settingsClass);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
@@ -1082,16 +1740,64 @@ public class LatinIME extends InputMethodService
mSoundOn = sp.getBoolean(PREF_SOUND_ON, false);
mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true);
mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
+ mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
+ mHasUsedVoiceInputUnsupportedLocale =
+ sp.getBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
+
+ // Get the current list of supported locales and check the current locale against that
+ // list. We cache this value so as not to check it every time the user starts a voice
+ // input. Because this method is called by onStartInputView, this should mean that as
+ // long as the locale doesn't change while the user is keeping the IME open, the
+ // value should never be stale.
+ String supportedLocalesString = SettingsUtil.getSettingsString(
+ getContentResolver(),
+ SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
+ DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
+ ArrayList<String> voiceInputSupportedLocales =
+ Lists.newArrayList(supportedLocalesString.split("\\s+"));
+
+ mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mLocale);
+
// If there is no auto text data, then quickfix is forced to "on", so that the other options
// will continue to work
+
if (AutoText.getSize(mInputView) < 1) mQuickFixes = true;
mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS, true) & mQuickFixes;
- boolean autoComplete = sp.getBoolean(PREF_AUTO_COMPLETE,
- getResources().getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions;
- mAutoCorrectOn = mSuggest != null && (autoComplete || mQuickFixes);
- mCorrectionMode = autoComplete
- ? Suggest.CORRECTION_FULL
- : (mQuickFixes ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
+
+ if (VOICE_INSTALLED) {
+ boolean enableVoice = sp.getBoolean(PREF_ENABLE_VOICE, true);
+ boolean voiceOnPrimary = sp.getBoolean(PREF_VOICE_MAIN, true);
+ if (mKeyboardSwitcher != null &&
+ (enableVoice != mEnableVoice || voiceOnPrimary != mVoiceOnPrimary)) {
+ mKeyboardSwitcher.setVoiceMode(enableVoice, voiceOnPrimary);
+ }
+ mEnableVoice = enableVoice;
+ mVoiceOnPrimary = voiceOnPrimary;
+ }
+ mAutoCorrectEnabled = sp.getBoolean(PREF_AUTO_COMPLETE,
+ mResources.getBoolean(R.bool.enable_autocorrect)) & mShowSuggestions;
+ updateCorrectionMode();
+ mLanguageSwitcher.loadLocales(sp);
+ }
+
+ private String getPersistedInputLanguage() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+ return sp.getString(PREF_INPUT_LANGUAGE, null);
+ }
+
+ private String getSelectedInputLanguages() {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+ return sp.getString(PREF_SELECTED_LANGUAGES, null);
+ }
+
+ private void initSuggestPuncList() {
+ mSuggestPuncList = new ArrayList<CharSequence>();
+ String suggestPuncs = mResources.getString(R.string.suggested_punctuations);
+ if (suggestPuncs != null) {
+ for (int i = 0; i < suggestPuncs.length(); i++) {
+ mSuggestPuncList.add(suggestPuncs.subSequence(i, i + 1));
+ }
+ }
}
private void showOptionsMenu() {
@@ -1100,7 +1806,7 @@ public class LatinIME extends InputMethodService
builder.setIcon(R.drawable.ic_dialog_keyboard);
builder.setNegativeButton(android.R.string.cancel, null);
CharSequence itemSettings = getString(R.string.english_ime_settings);
- CharSequence itemInputMethod = getString(com.android.internal.R.string.inputMethod);
+ CharSequence itemInputMethod = getString(R.string.inputMethod);
builder.setItems(new CharSequence[] {
itemSettings, itemInputMethod},
new DialogInterface.OnClickListener() {
@@ -1118,7 +1824,7 @@ public class LatinIME extends InputMethodService
}
}
});
- builder.setTitle(getResources().getString(R.string.english_ime_name));
+ builder.setTitle(mResources.getString(R.string.english_ime_name));
mOptionsDialog = builder.create();
Window window = mOptionsDialog.getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
@@ -1137,10 +1843,10 @@ public class LatinIME extends InputMethodService
updateShiftKeyState(getCurrentInputEditorInfo());
}
-
+
@Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
super.dump(fd, fout, args);
-
+
final Printer p = new PrintWriterPrinter(fout);
p.println("LatinIME state :");
p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
@@ -1158,13 +1864,14 @@ public class LatinIME extends InputMethodService
}
// Characters per second measurement
-
+
private static final boolean PERF_DEBUG = false;
private long mLastCpsTime;
private static final int CPS_BUFFER_SIZE = 16;
private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
private int mCpsIndex;
-
+ private boolean mInputTypeNoAutoCorrect;
+
private void measureCps() {
if (!LatinIME.PERF_DEBUG) return;
long now = System.currentTimeMillis();
@@ -1181,7 +1888,7 @@ public class LatinIME extends InputMethodService
// If the user touches a typed word 2 times or more, it will become valid.
private static final int VALIDITY_THRESHOLD = 2 * FREQUENCY_FOR_PICKED;
// If the user touches a typed word 5 times or more, it will be added to the user dict.
- private static final int PROMOTION_THRESHOLD = 5 * FREQUENCY_FOR_PICKED;
+ private static final int PROMOTION_THRESHOLD = 4 * FREQUENCY_FOR_PICKED;
public AutoDictionary(Context context) {
super(context);
@@ -1190,7 +1897,7 @@ public class LatinIME extends InputMethodService
@Override
public boolean isValidWord(CharSequence word) {
final int frequency = getWordFrequency(word);
- return frequency > VALIDITY_THRESHOLD;
+ return frequency >= VALIDITY_THRESHOLD;
}
@Override
@@ -1198,14 +1905,17 @@ public class LatinIME extends InputMethodService
final int length = word.length();
// Don't add very short or very long words.
if (length < 2 || length > getMaxWordLength()) return;
- super.addWord(word, addFrequency);
- final int freq = getWordFrequency(word);
- if (freq > PROMOTION_THRESHOLD) {
+ if (mWord.isAutoCapitalized()) {
+ // Remove caps before adding
+ word = Character.toLowerCase(word.charAt(0))
+ + word.substring(1);
+ }
+ int freq = getWordFrequency(word);
+ freq = freq < 0 ? addFrequency : freq + addFrequency;
+ super.addWord(word, freq);
+ if (freq >= PROMOTION_THRESHOLD) {
LatinIME.this.promoteToUserDictionary(word, FREQUENCY_FOR_AUTO_ADD);
}
}
}
}
-
-
-