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.java1091
1 files changed, 1091 insertions, 0 deletions
diff --git a/src/com/android/inputmethod/latin/LatinIME.java b/src/com/android/inputmethod/latin/LatinIME.java
new file mode 100644
index 000000000..8671bf2e5
--- /dev/null
+++ b/src/com/android/inputmethod/latin/LatinIME.java
@@ -0,0 +1,1091 @@
+/*
+ * 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
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+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.inputmethodservice.InputMethodService;
+import android.inputmethodservice.Keyboard;
+import android.inputmethodservice.KeyboardView;
+import android.media.AudioManager;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.preference.PreferenceManager;
+import android.text.ClipboardManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.view.KeyEvent;
+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.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Input method implementation for Qwerty'ish keyboard.
+ */
+public class LatinIME extends InputMethodService
+ implements KeyboardView.OnKeyboardActionListener {
+ static final boolean DEBUG = false;
+ static final boolean TRACE = false;
+
+ private static final String PREF_VIBRATE_ON = "vibrate_on";
+ private static final String PREF_SOUND_ON = "sound_on";
+ private static final String PREF_PROXIMITY_CORRECTION = "hit_correction";
+ private static final String PREF_PREDICTION = "prediction_mode";
+ private static final String PREF_PREDICTION_LANDSCAPE = "prediction_landscape";
+ private static final String PREF_AUTO_CAP = "auto_cap";
+ static final String PREF_TUTORIAL_RUN = "tutorial_run";
+
+ private static final int MSG_UPDATE_SUGGESTIONS = 0;
+ private static final int MSG_CHECK_TUTORIAL = 1;
+
+ // 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.
+ private static final int QUICK_PRESS = 200;
+
+ private static final int KEYCODE_ENTER = 10;
+ private 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 KeyboardSwitcher mKeyboardSwitcher;
+
+ private UserDictionary mUserDictionary;
+
+ private String mLocale;
+
+ private StringBuilder mComposing = new StringBuilder();
+ private WordComposer mWord = new WordComposer();
+ private int mCommittedLength;
+ private boolean mPredicting;
+ private CharSequence mBestWord;
+ private boolean mPredictionOn;
+ private boolean mCompletionOn;
+ private boolean mPasswordMode;
+ private boolean mAutoSpace;
+ private boolean mAutoCorrectOn;
+ private boolean mCapsLock;
+ private long mLastShiftTime;
+ private boolean mVibrateOn;
+ private boolean mSoundOn;
+ private boolean mProximityCorrection;
+ private int mCorrectionMode;
+ private boolean mAutoCap;
+ private boolean mAutoPunctuate;
+ private boolean mTutorialShownBefore;
+ // Indicates whether the suggestion strip is to be on in landscape
+ private boolean mShowSuggestInLand;
+ private boolean mJustAccepted;
+ private CharSequence mJustRevertedSeparator;
+ private int mDeleteCount;
+ private long mLastKeyTime;
+
+ private Tutorial mTutorial;
+
+ private Vibrator mVibrator;
+ private long mVibrateDuration;
+
+ private AudioManager mAudioManager;
+ private final float FX_VOLUME = 1.0f;
+ private boolean mSilentMode;
+
+ private String mWordSeparators;
+ private String mSentenceSeparators;
+
+ Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE_SUGGESTIONS:
+ updateSuggestions();
+ break;
+ case MSG_CHECK_TUTORIAL:
+ if (!mTutorialShownBefore) {
+ mTutorial = new Tutorial(mInputView);
+ mTutorial.start();
+ }
+ break;
+ }
+ }
+ };
+
+ @Override public void onCreate() {
+ super.onCreate();
+ //setStatusIcon(R.drawable.ime_qwerty);
+ mKeyboardSwitcher = new KeyboardSwitcher(this);
+ initSuggest(getResources().getConfiguration().locale.toString());
+
+ mVibrateDuration = getResources().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);
+ }
+
+ private void initSuggest(String locale) {
+ mLocale = locale;
+ mSuggest = new Suggest(this, R.raw.main);
+ mSuggest.setCorrectionMode(mCorrectionMode);
+ mUserDictionary = new UserDictionary(this);
+ mSuggest.setUserDictionary(mUserDictionary);
+ mWordSeparators = getResources().getString(R.string.word_separators);
+ mSentenceSeparators = getResources().getString(R.string.sentence_separators);
+ }
+
+ @Override public void onDestroy() {
+ mUserDictionary.close();
+ unregisterReceiver(mReceiver);
+ super.onDestroy();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration conf) {
+ if (!TextUtils.equals(conf.locale.toString(), mLocale)) {
+ initSuggest(conf.locale.toString());
+ }
+ if (!mTutorialShownBefore && mTutorial != null) {
+ mTutorial.close(false);
+ }
+ super.onConfigurationChanged(conf);
+ }
+
+ @Override
+ public View onCreateInputView() {
+ mInputView = (LatinKeyboardView) getLayoutInflater().inflate(
+ R.layout.input, null);
+ mKeyboardSwitcher.setInputView(mInputView);
+ mKeyboardSwitcher.makeKeyboards();
+ mInputView.setOnKeyboardActionListener(this);
+ mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 0);
+ return mInputView;
+ }
+
+ @Override
+ public View onCreateCandidatesView() {
+ mKeyboardSwitcher.makeKeyboards();
+ mCandidateViewContainer = (CandidateViewContainer) getLayoutInflater().inflate(
+ R.layout.candidates, null);
+ mCandidateViewContainer.initViews();
+ mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates);
+ mCandidateView.setService(this);
+ setCandidatesViewShown(true);
+ return mCandidateViewContainer;
+ }
+
+ @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;
+ }
+
+ mKeyboardSwitcher.makeKeyboards();
+
+ TextEntryState.newSession(this);
+
+ mPredictionOn = false;
+ mCompletionOn = false;
+ mCompletions = null;
+ mCapsLock = false;
+ switch (attribute.inputType&EditorInfo.TYPE_MASK_CLASS) {
+ case EditorInfo.TYPE_CLASS_NUMBER:
+ case EditorInfo.TYPE_CLASS_DATETIME:
+ mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
+ attribute.imeOptions);
+ mKeyboardSwitcher.toggleSymbols();
+ break;
+ case EditorInfo.TYPE_CLASS_PHONE:
+ mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_PHONE,
+ attribute.imeOptions);
+ break;
+ case EditorInfo.TYPE_CLASS_TEXT:
+ mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
+ attribute.imeOptions);
+ //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 ) {
+ mPasswordMode = true;
+ mPredictionOn = false;
+ } else {
+ mPasswordMode = false;
+ }
+ if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
+ mAutoSpace = false;
+ } else {
+ mAutoSpace = true;
+ }
+ if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
+ mPredictionOn = false;
+ mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_EMAIL,
+ attribute.imeOptions);
+ } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
+ mPredictionOn = false;
+ mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_URL,
+ attribute.imeOptions);
+ } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
+ mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_IM,
+ attribute.imeOptions);
+ } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
+ mPredictionOn = false;
+ }
+ if ((attribute.inputType&EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
+ mPredictionOn = false;
+ mCompletionOn = true && isFullscreenMode();
+ }
+ updateShiftKeyState(attribute);
+ break;
+ default:
+ mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT,
+ attribute.imeOptions);
+ updateShiftKeyState(attribute);
+ }
+ mInputView.closing();
+ mComposing.setLength(0);
+ mPredicting = false;
+ mDeleteCount = 0;
+ setCandidatesViewShown(false);
+ if (mCandidateView != null) mCandidateView.setSuggestions(null, false, false, false);
+ loadSettings();
+ mInputView.setProximityCorrectionEnabled(mProximityCorrection);
+ if (mSuggest != null) {
+ mSuggest.setCorrectionMode(mCorrectionMode);
+ }
+ if (!mTutorialShownBefore && mTutorial == null) {
+ mHandler.sendEmptyMessageDelayed(MSG_CHECK_TUTORIAL, 1000);
+ }
+ mPredictionOn = mPredictionOn && mCorrectionMode > 0;
+ if (TRACE) Debug.startMethodTracing("latinime");
+ }
+
+ @Override
+ public void onFinishInput() {
+ super.onFinishInput();
+
+ if (mInputView != null) {
+ mInputView.closing();
+ }
+ if (!mTutorialShownBefore && mTutorial != null) {
+ mTutorial.close(false);
+ }
+ }
+
+ @Override
+ public void onUpdateSelection(int oldSelStart, int oldSelEnd,
+ int newSelStart, int newSelEnd,
+ int candidatesStart, int candidatesEnd) {
+ super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
+ candidatesStart, candidatesEnd);
+ // 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)) {
+ mComposing.setLength(0);
+ mPredicting = false;
+ updateSuggestions();
+ TextEntryState.reset();
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.finishComposingText();
+ }
+ } else if (!mPredicting && !mJustAccepted
+ && TextEntryState.getState() == TextEntryState.STATE_ACCEPTED_DEFAULT) {
+ TextEntryState.reset();
+ }
+ mJustAccepted = false;
+ }
+
+ @Override
+ public void hideWindow() {
+ if (TRACE) Debug.stopMethodTracing();
+ super.hideWindow();
+ TextEntryState.endSession();
+ }
+
+ @Override
+ public void onDisplayCompletions(CompletionInfo[] completions) {
+ if (false) {
+ Log.i("foo", "Received completions:");
+ for (int i=0; i<(completions != null ? completions.length : 0); i++) {
+ Log.i("foo", " #" + i + ": " + completions[i]);
+ }
+ }
+ if (mCompletionOn) {
+ mCompletions = completions;
+ if (completions == null) {
+ mCandidateView.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);
+ mBestWord = null;
+ setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
+ }
+ }
+
+ @Override
+ public void setCandidatesViewShown(boolean shown) {
+ // TODO: Remove this if we support candidates with hard keyboard
+ if (onEvaluateInputViewShown()) {
+ super.setCandidatesViewShown(shown);
+ }
+ }
+
+ @Override
+ public void onComputeInsets(InputMethodService.Insets outInsets) {
+ super.onComputeInsets(outInsets);
+ outInsets.contentTopInsets = outInsets.visibleTopInsets;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ if (event.getRepeatCount() == 0 && mInputView != null) {
+ if (mInputView.handleBack()) {
+ return true;
+ } else if (!mTutorialShownBefore && mTutorial != null) {
+ mTutorial.close(true);
+ }
+ }
+ break;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ // Enable shift key and DPAD to do selections
+ if (mInputView != null && mInputView.isShown() && mInputView.isShifted()) {
+ 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);
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) ic.sendKeyEvent(event);
+ return true;
+ }
+ break;
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ private void commitTyped(InputConnection inputConnection) {
+ if (mPredicting) {
+ mPredicting = false;
+ if (mComposing.length() > 0) {
+ if (inputConnection != null) {
+ inputConnection.commitText(mComposing, 1);
+ }
+ mCommittedLength = mComposing.length();
+ TextEntryState.acceptedTyped(mComposing);
+ }
+ updateSuggestions();
+ }
+ }
+
+ public void updateShiftKeyState(EditorInfo attr) {
+ 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);
+ }
+ }
+
+ private void swapPunctuationAndSpace() {
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+ CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
+ if (lastTwo != null && lastTwo.length() == 2
+ && lastTwo.charAt(0) == KEYCODE_SPACE && isSentenceSeparator(lastTwo.charAt(1))) {
+ ic.beginBatchEdit();
+ ic.deleteSurroundingText(2, 0);
+ ic.commitText(lastTwo.charAt(1) + " ", 1);
+ ic.endBatchEdit();
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ }
+ }
+
+ private void doubleSpace() {
+ //if (!mAutoPunctuate) return;
+ if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+ CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
+ if (lastThree != null && lastThree.length() == 3
+ && Character.isLetterOrDigit(lastThree.charAt(0))
+ && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) {
+ ic.beginBatchEdit();
+ ic.deleteSurroundingText(2, 0);
+ ic.commitText(". ", 1);
+ ic.endBatchEdit();
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ }
+ }
+
+ public boolean addWordToDictionary(String word) {
+ mUserDictionary.addWord(word, 128);
+ return true;
+ }
+
+ private boolean isAlphabet(int code) {
+ if (Character.isLetter(code)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Implementation of KeyboardViewListener
+
+ public void onKey(int primaryCode, int[] keyCodes) {
+ long when = SystemClock.uptimeMillis();
+ if (primaryCode != Keyboard.KEYCODE_DELETE ||
+ when > mLastKeyTime + QUICK_PRESS) {
+ mDeleteCount = 0;
+ }
+ mLastKeyTime = when;
+ switch (primaryCode) {
+ case Keyboard.KEYCODE_DELETE:
+ handleBackspace();
+ mDeleteCount++;
+ break;
+ case Keyboard.KEYCODE_SHIFT:
+ handleShift();
+ break;
+ case Keyboard.KEYCODE_CANCEL:
+ if (mOptionsDialog == null || !mOptionsDialog.isShowing()) {
+ handleClose();
+ }
+ break;
+ case LatinKeyboardView.KEYCODE_OPTIONS:
+ showOptionsMenu();
+ break;
+ case LatinKeyboardView.KEYCODE_SHIFT_LONGPRESS:
+ if (mCapsLock) {
+ handleShift();
+ } else {
+ toggleCapsLock();
+ }
+ break;
+ case Keyboard.KEYCODE_MODE_CHANGE:
+ changeKeyboardMode();
+ break;
+ default:
+ if (isWordSeparator(primaryCode)) {
+ handleSeparator(primaryCode);
+ } else {
+ handleCharacter(primaryCode, keyCodes);
+ }
+ // Cancel the just reverted state
+ mJustRevertedSeparator = null;
+ }
+ }
+
+ public void onText(CharSequence text) {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+ ic.beginBatchEdit();
+ if (mPredicting) {
+ commitTyped(ic);
+ }
+ ic.commitText(text, 1);
+ ic.endBatchEdit();
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ mJustRevertedSeparator = null;
+ }
+
+ private void handleBackspace() {
+ boolean deleteChar = false;
+ InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+ if (mPredicting) {
+ final int length = mComposing.length();
+ if (length > 0) {
+ mComposing.delete(length - 1, length);
+ mWord.deleteLast();
+ ic.setComposingText(mComposing, 1);
+ if (mComposing.length() == 0) {
+ mPredicting = false;
+ }
+ postUpdateSuggestions();
+ } else {
+ ic.deleteSurroundingText(1, 0);
+ }
+ } else {
+ //getCurrentInputConnection().deleteSurroundingText(1, 0);
+ deleteChar = true;
+ //sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ }
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ TextEntryState.backspace();
+ if (TextEntryState.getState() == TextEntryState.STATE_UNDO_COMMIT) {
+ revertLastWord(deleteChar);
+ return;
+ } else if (deleteChar) {
+ sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ if (mDeleteCount > DELETE_ACCELERATE_AT) {
+ sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ }
+ }
+ mJustRevertedSeparator = null;
+ }
+
+ private void handleShift() {
+ Keyboard currentKeyboard = mInputView.getKeyboard();
+ if (mKeyboardSwitcher.isAlphabetMode()) {
+ // Alphabet keyboard
+ checkToggleCapsLock();
+ mInputView.setShifted(mCapsLock || !mInputView.isShifted());
+ } else {
+ mKeyboardSwitcher.toggleShift();
+ }
+ }
+
+ private void handleCharacter(int primaryCode, int[] keyCodes) {
+ if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
+ if (!mPredicting) {
+ mPredicting = true;
+ mComposing.setLength(0);
+ mWord.reset();
+ }
+ }
+ if (mInputView.isShifted()) {
+ primaryCode = Character.toUpperCase(primaryCode);
+ }
+ if (mPredicting) {
+ if (mInputView.isShifted() && mComposing.length() == 0) {
+ mWord.setCapitalized(true);
+ }
+ mComposing.append((char) primaryCode);
+ mWord.add(primaryCode, keyCodes);
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.setComposingText(mComposing, 1);
+ }
+ postUpdateSuggestions();
+ } else {
+ sendKeyChar((char)primaryCode);
+ }
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ measureCps();
+ TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode));
+ }
+
+ private void handleSeparator(int primaryCode) {
+ boolean pickedDefault = false;
+ // Handle separator
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.beginBatchEdit();
+ }
+ 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,
+ // 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
+ || mJustRevertedSeparator.charAt(0) != primaryCode)) {
+ pickDefaultSuggestion();
+ pickedDefault = true;
+ } else {
+ commitTyped(ic);
+ }
+ }
+ sendKeyChar((char)primaryCode);
+ TextEntryState.typedCharacter((char) primaryCode, true);
+ if (TextEntryState.getState() == TextEntryState.STATE_PUNCTUATION_AFTER_ACCEPTED
+ && primaryCode != KEYCODE_ENTER) {
+ swapPunctuationAndSpace();
+ } else if (isPredictionOn() && primaryCode == ' ') {
+ //else if (TextEntryState.STATE_SPACE_AFTER_ACCEPTED) {
+ doubleSpace();
+ }
+ if (pickedDefault && mBestWord != null) {
+ TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
+ }
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ if (ic != null) {
+ ic.endBatchEdit();
+ }
+ }
+
+ private void handleClose() {
+ commitTyped(getCurrentInputConnection());
+ if (!mTutorialShownBefore && mTutorial != null) {
+ mTutorial.close(true);
+ }
+ requestHideSelf(0);
+ mInputView.closing();
+ TextEntryState.endSession();
+ }
+
+ private void checkToggleCapsLock() {
+ if (mInputView.getKeyboard().isShifted()) {
+ toggleCapsLock();
+ }
+ }
+
+ private void toggleCapsLock() {
+ mCapsLock = !mCapsLock;
+ if (mKeyboardSwitcher.isAlphabetMode()) {
+ ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock);
+ }
+ }
+
+ private void postUpdateSuggestions() {
+ 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() {
+ boolean visible = isPredictionOn() &&
+ (!isFullscreenMode() ||
+ mCorrectionMode == Suggest.CORRECTION_FULL ||
+ mShowSuggestInLand);
+ return visible;
+ }
+
+ private void updateSuggestions() {
+ // Check if we have a suggestion engine attached.
+ if (mSuggest == null || !isPredictionOn()) {
+ return;
+ }
+
+ if (!mPredicting) {
+ mCandidateView.setSuggestions(null, false, false, false);
+ return;
+ }
+
+ List<CharSequence> stringList = mSuggest.getSuggestions(mInputView, mWord, false);
+ boolean correctionAvailable = mSuggest.hasMinimalCorrection();
+ //|| mCorrectionMode == mSuggest.CORRECTION_FULL;
+ CharSequence typedWord = mWord.getTypedWord();
+ // If we're in basic correct
+ boolean typedWordValid = mSuggest.isValidWord(typedWord);
+ if (mCorrectionMode == Suggest.CORRECTION_FULL) {
+ correctionAvailable |= typedWordValid;
+ }
+
+ mCandidateView.setSuggestions(stringList, false, typedWordValid, correctionAvailable);
+ if (stringList.size() > 0) {
+ if (correctionAvailable && !typedWordValid && stringList.size() > 1) {
+ mBestWord = stringList.get(1);
+ } else {
+ mBestWord = typedWord;
+ }
+ } else {
+ mBestWord = null;
+ }
+ setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
+ }
+
+ private void pickDefaultSuggestion() {
+ // Complete any pending candidate query first
+ if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) {
+ mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
+ updateSuggestions();
+ }
+ if (mBestWord != null) {
+ TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
+ mJustAccepted = true;
+ pickSuggestion(mBestWord);
+ }
+ }
+
+ public void pickSuggestionManually(int index, CharSequence suggestion) {
+ if (mCompletionOn && mCompletions != null && index >= 0
+ && index < mCompletions.length) {
+ CompletionInfo ci = mCompletions[index];
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.commitCompletion(ci);
+ }
+ mCommittedLength = suggestion.length();
+ if (mCandidateView != null) {
+ mCandidateView.clear();
+ }
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ return;
+ }
+ pickSuggestion(suggestion);
+ TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
+ // Follow it with a space
+ if (mAutoSpace) {
+ sendSpace();
+ }
+ // 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()
+ || (mKeyboardSwitcher.isAlphabetMode() && mInputView.isShifted())) {
+ suggestion = Character.toUpperCase(suggestion.charAt(0))
+ + suggestion.subSequence(1, suggestion.length()).toString();
+ }
+ InputConnection ic = getCurrentInputConnection();
+ if (ic != null) {
+ ic.commitText(suggestion, 1);
+ }
+ mPredicting = false;
+ mCommittedLength = suggestion.length();
+ if (mCandidateView != null) {
+ mCandidateView.setSuggestions(null, false, false, false);
+ }
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ }
+
+ private boolean isCursorTouchingWord() {
+ InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return false;
+ CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
+ CharSequence toRight = ic.getTextAfterCursor(1, 0);
+ if (!TextUtils.isEmpty(toLeft)
+ && !isWordSeparator(toLeft.charAt(0))) {
+ return true;
+ }
+ 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) {
+ final InputConnection ic = getCurrentInputConnection();
+ mPredicting = true;
+ ic.beginBatchEdit();
+ mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0);
+ if (deleteChar) ic.deleteSurroundingText(1, 0);
+ int toDelete = mCommittedLength;
+ CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
+ if (toTheLeft != null && toTheLeft.length() > 0
+ && isWordSeparator(toTheLeft.charAt(0))) {
+ toDelete--;
+ }
+ ic.deleteSurroundingText(toDelete, 0);
+ ic.setComposingText(mComposing, 1);
+ TextEntryState.backspace();
+ ic.endBatchEdit();
+ postUpdateSuggestions();
+ } else {
+ sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
+ mJustRevertedSeparator = null;
+ }
+ }
+
+ protected String getWordSeparators() {
+ return mWordSeparators;
+ }
+
+ public boolean isWordSeparator(int code) {
+ String separators = getWordSeparators();
+ return separators.contains(String.valueOf((char)code));
+ }
+
+ public boolean isSentenceSeparator(int code) {
+ return mSentenceSeparators.contains(String.valueOf((char)code));
+ }
+
+ private void sendSpace() {
+ sendKeyChar((char)KEYCODE_SPACE);
+ updateShiftKeyState(getCurrentInputEditorInfo());
+ //onKey(KEY_SPACE[0], KEY_SPACE);
+ }
+
+ public boolean preferCapitalization() {
+ return mWord.isCapitalized();
+ }
+
+ public void swipeRight() {
+ if (LatinKeyboardView.DEBUG_AUTO_PLAY) {
+ ClipboardManager cm = ((ClipboardManager)getSystemService(CLIPBOARD_SERVICE));
+ CharSequence text = cm.getText();
+ if (!TextUtils.isEmpty(text)) {
+ mInputView.startPlaying(text.toString());
+ }
+ }
+// if (mAutoCorrectOn) {
+// commitTyped(getCurrentInputConnection());
+// } else if (mPredicting) {
+// pickDefaultSuggestion();
+// }
+// if (mAutoSpace) {
+// sendSpace();
+// }
+ }
+
+ public void swipeLeft() {
+ //handleBackspace();
+ }
+
+ public void swipeDown() {
+ //handleClose();
+ }
+
+ public void swipeUp() {
+ //launchSettings();
+ }
+
+ public void onPress(int primaryCode) {
+ vibrate();
+ playKeyClick(primaryCode);
+ }
+
+ public void onRelease(int primaryCode) {
+ //vibrate();
+ }
+
+ // receive ringer mode changes to detect silent mode
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateRingerMode();
+ }
+ };
+
+ // update flags for silent mode
+ private void updateRingerMode() {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ }
+ if (mAudioManager != null) {
+ mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
+ }
+ }
+
+ private void playKeyClick(int primaryCode) {
+ // if mAudioManager is null, we don't have the ringer state yet
+ // mAudioManager will be set by updateRingerMode
+ if (mAudioManager == null) {
+ if (mInputView != null) {
+ updateRingerMode();
+ }
+ }
+ if (mSoundOn && !mSilentMode) {
+ // FIXME: Volume and enable should come from UI settings
+ // FIXME: These should be triggered after auto-repeat logic
+ int sound = AudioManager.FX_KEYPRESS_STANDARD;
+ switch (primaryCode) {
+ case Keyboard.KEYCODE_DELETE:
+ sound = AudioManager.FX_KEYPRESS_DELETE;
+ break;
+ case KEYCODE_ENTER:
+ sound = AudioManager.FX_KEYPRESS_RETURN;
+ break;
+ case KEYCODE_SPACE:
+ sound = AudioManager.FX_KEYPRESS_SPACEBAR;
+ break;
+ }
+ mAudioManager.playSoundEffect(sound, FX_VOLUME);
+ }
+ }
+
+ private void vibrate() {
+ if (!mVibrateOn) {
+ return;
+ }
+ if (mVibrator == null) {
+ mVibrator = new Vibrator();
+ }
+ mVibrator.vibrate(mVibrateDuration);
+ }
+
+ private void launchSettings() {
+ handleClose();
+ Intent intent = new Intent();
+ intent.setClass(LatinIME.this, LatinIMESettings.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
+
+ private void loadSettings() {
+ // Get the settings preferences
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
+ mProximityCorrection = sp.getBoolean(PREF_PROXIMITY_CORRECTION, true);
+ mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, true);
+ mSoundOn = sp.getBoolean(PREF_SOUND_ON, false);
+ String predictionBasic = getString(R.string.prediction_basic);
+ String mode = sp.getString(PREF_PREDICTION, predictionBasic);
+ if (mode.equals(getString(R.string.prediction_full))) {
+ mCorrectionMode = 2;
+ } else if (mode.equals(predictionBasic)) {
+ mCorrectionMode = 1;
+ } else {
+ mCorrectionMode = 0;
+ }
+ mAutoCorrectOn = mSuggest != null && mCorrectionMode > 0;
+
+ mAutoCap = sp.getBoolean(PREF_AUTO_CAP, true);
+ //mAutoPunctuate = sp.getBoolean(PREF_AUTO_PUNCTUATE, mCorrectionMode > 0);
+ mShowSuggestInLand = !sp.getBoolean(PREF_PREDICTION_LANDSCAPE, false);
+ mTutorialShownBefore = sp.getBoolean(PREF_TUTORIAL_RUN, false);
+ }
+
+ private void showOptionsMenu() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setCancelable(true);
+ 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);
+ builder.setItems(new CharSequence[] {
+ itemSettings, itemInputMethod},
+ new DialogInterface.OnClickListener() {
+
+ public void onClick(DialogInterface di, int position) {
+ di.dismiss();
+ switch (position) {
+ case POS_SETTINGS:
+ launchSettings();
+ break;
+ case POS_METHOD:
+ InputMethodManager.getInstance(LatinIME.this).showInputMethodPicker();
+ break;
+ }
+ }
+ });
+ builder.setTitle(getResources().getString(R.string.english_ime_name));
+ mOptionsDialog = builder.create();
+ Window window = mOptionsDialog.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);
+ mOptionsDialog.show();
+ }
+
+ private void changeKeyboardMode() {
+ mKeyboardSwitcher.toggleSymbols();
+ if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) {
+ ((LatinKeyboard) mInputView.getKeyboard()).setShiftLocked(mCapsLock);
+ }
+
+ 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());
+ p.println(" mCapsLock=" + mCapsLock);
+ p.println(" mComposing=" + mComposing.toString());
+ p.println(" mPredictionOn=" + mPredictionOn);
+ p.println(" mCorrectionMode=" + mCorrectionMode);
+ p.println(" mPredicting=" + mPredicting);
+ p.println(" mAutoCorrectOn=" + mAutoCorrectOn);
+ p.println(" mAutoSpace=" + mAutoSpace);
+ p.println(" mCompletionOn=" + mCompletionOn);
+ p.println(" TextEntryState.state=" + TextEntryState.getState());
+ p.println(" mSoundOn=" + mSoundOn);
+ p.println(" mVibrateOn=" + mVibrateOn);
+ }
+
+
+ private static final int[] KEY_SPACE = { KEYCODE_SPACE };
+
+
+ // 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 void measureCps() {
+ if (!LatinIME.PERF_DEBUG) return;
+ long now = System.currentTimeMillis();
+ if (mLastCpsTime == 0) mLastCpsTime = now - 100; // Initial
+ mCpsIntervals[mCpsIndex] = now - mLastCpsTime;
+ mLastCpsTime = now;
+ mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE;
+ long total = 0;
+ for (int i = 0; i < CPS_BUFFER_SIZE; i++) total += mCpsIntervals[i];
+ System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
+ }
+
+}
+
+
+