diff options
Diffstat (limited to 'src/com/android/inputmethod/latin/LatinIME.java')
-rw-r--r-- | src/com/android/inputmethod/latin/LatinIME.java | 1091 |
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)); + } + +} + + + |