InputLogic.java revision c8dfaab78398dd88a9657d0ae22077db6c4de2b9
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.latin.inputlogic; 18 19import android.os.SystemClock; 20import android.util.Log; 21import android.view.KeyCharacterMap; 22import android.view.KeyEvent; 23import android.view.inputmethod.EditorInfo; 24 25import com.android.inputmethod.compat.SuggestionSpanUtils; 26import com.android.inputmethod.event.EventInterpreter; 27import com.android.inputmethod.keyboard.Keyboard; 28import com.android.inputmethod.keyboard.KeyboardSwitcher; 29import com.android.inputmethod.latin.Constants; 30import com.android.inputmethod.latin.LastComposedWord; 31import com.android.inputmethod.latin.LatinIME; 32import com.android.inputmethod.latin.LatinImeLogger; 33import com.android.inputmethod.latin.RichInputConnection; 34import com.android.inputmethod.latin.SubtypeSwitcher; 35import com.android.inputmethod.latin.Suggest; 36import com.android.inputmethod.latin.SuggestedWords; 37import com.android.inputmethod.latin.WordComposer; 38import com.android.inputmethod.latin.define.ProductionFlag; 39import com.android.inputmethod.latin.settings.Settings; 40import com.android.inputmethod.latin.settings.SettingsValues; 41import com.android.inputmethod.latin.utils.CollectionUtils; 42import com.android.inputmethod.latin.utils.InputTypeUtils; 43import com.android.inputmethod.latin.utils.LatinImeLoggerUtils; 44import com.android.inputmethod.latin.utils.RecapitalizeStatus; 45import com.android.inputmethod.latin.utils.StringUtils; 46import com.android.inputmethod.research.ResearchLogger; 47 48import java.util.TreeSet; 49 50/** 51 * This class manages the input logic. 52 */ 53public final class InputLogic { 54 private static final String TAG = InputLogic.class.getSimpleName(); 55 56 // TODO : Remove this member when we can. 57 private final LatinIME mLatinIME; 58 59 // TODO : make all these fields private as soon as possible. 60 // Current space state of the input method. This can be any of the above constants. 61 public int mSpaceState; 62 // Never null 63 public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; 64 public Suggest mSuggest; 65 // The event interpreter should never be null. 66 public EventInterpreter mEventInterpreter; 67 68 public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; 69 public final WordComposer mWordComposer; 70 public final RichInputConnection mConnection; 71 public final RecapitalizeStatus mRecapitalizeStatus = new RecapitalizeStatus(); 72 73 // Keep track of the last selection range to decide if we need to show word alternatives 74 public static final int NOT_A_CURSOR_POSITION = -1; 75 public int mLastSelectionStart = NOT_A_CURSOR_POSITION; 76 public int mLastSelectionEnd = NOT_A_CURSOR_POSITION; 77 78 public int mDeleteCount; 79 public long mLastKeyTime; 80 public final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet(); 81 82 // Keeps track of most recently inserted text (multi-character key) for reverting 83 public String mEnteredText; 84 85 // TODO: This boolean is persistent state and causes large side effects at unexpected times. 86 // Find a way to remove it for readability. 87 public boolean mIsAutoCorrectionIndicatorOn; 88 89 public InputLogic(final LatinIME latinIME) { 90 mLatinIME = latinIME; 91 mWordComposer = new WordComposer(); 92 mEventInterpreter = new EventInterpreter(latinIME); 93 mConnection = new RichInputConnection(latinIME); 94 } 95 96 public void startInput(final boolean restarting) { 97 } 98 99 public void finishInput() { 100 } 101 102 public void onCodeInput(final int codePoint, final int x, final int y, 103 // TODO: remove these three arguments 104 final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher, 105 final SubtypeSwitcher subtypeSwitcher) { 106 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 107 ResearchLogger.latinIME_onCodeInput(codePoint, x, y); 108 } 109 final SettingsValues settingsValues = Settings.getInstance().getCurrent(); 110 final long when = SystemClock.uptimeMillis(); 111 if (codePoint != Constants.CODE_DELETE 112 || when > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) { 113 mDeleteCount = 0; 114 } 115 mLastKeyTime = when; 116 mConnection.beginBatchEdit(); 117 final KeyboardSwitcher switcher = keyboardSwitcher; 118 // The space state depends only on the last character pressed and its own previous 119 // state. Here, we revert the space state to neutral if the key is actually modifying 120 // the input contents (any non-shift key), which is what we should do for 121 // all inputs that do not result in a special state. Each character handling is then 122 // free to override the state as they see fit. 123 final int spaceState = mSpaceState; 124 if (!mWordComposer.isComposingWord()) { 125 mIsAutoCorrectionIndicatorOn = false; 126 } 127 128 // TODO: Consolidate the double-space period timer, mLastKeyTime, and the space state. 129 if (codePoint != Constants.CODE_SPACE) { 130 handler.cancelDoubleSpacePeriodTimer(); 131 } 132 133 boolean didAutoCorrect = false; 134 switch (codePoint) { 135 case Constants.CODE_DELETE: 136 mSpaceState = SpaceState.NONE; 137 handleBackspace(settingsValues, spaceState, handler, keyboardSwitcher); 138 LatinImeLogger.logOnDelete(x, y); 139 break; 140 case Constants.CODE_SHIFT: 141 // Note: Calling back to the keyboard on Shift key is handled in 142 // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}. 143 final Keyboard currentKeyboard = switcher.getKeyboard(); 144 if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) { 145 // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for 146 // alphabetic shift and shift while in symbol layout. 147 performRecapitalization(); 148 } 149 break; 150 case Constants.CODE_CAPSLOCK: 151 // Note: Changing keyboard to shift lock state is handled in 152 // {@link KeyboardSwitcher#onCodeInput(int)}. 153 break; 154 case Constants.CODE_SWITCH_ALPHA_SYMBOL: 155 // Note: Calling back to the keyboard on symbol key is handled in 156 // {@link #onPressKey(int,int,boolean)} and {@link #onReleaseKey(int,boolean)}. 157 break; 158 case Constants.CODE_SETTINGS: 159 onSettingsKeyPressed(); 160 break; 161 case Constants.CODE_SHORTCUT: 162 subtypeSwitcher.switchToShortcutIME(mLatinIME); 163 break; 164 case Constants.CODE_ACTION_NEXT: 165 performEditorAction(EditorInfo.IME_ACTION_NEXT); 166 break; 167 case Constants.CODE_ACTION_PREVIOUS: 168 performEditorAction(EditorInfo.IME_ACTION_PREVIOUS); 169 break; 170 case Constants.CODE_LANGUAGE_SWITCH: 171 handleLanguageSwitchKey(); 172 break; 173 case Constants.CODE_EMOJI: 174 // Note: Switching emoji keyboard is being handled in 175 // {@link KeyboardState#onCodeInput(int,int)}. 176 break; 177 case Constants.CODE_ENTER: 178 final EditorInfo editorInfo = getCurrentInputEditorInfo(); 179 final int imeOptionsActionId = 180 InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo); 181 if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) { 182 // Either we have an actionLabel and we should performEditorAction with actionId 183 // regardless of its value. 184 performEditorAction(editorInfo.actionId); 185 } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) { 186 // We didn't have an actionLabel, but we had another action to execute. 187 // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast, 188 // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it 189 // means there should be an action and the app didn't bother to set a specific 190 // code for it - presumably it only handles one. It does not have to be treated 191 // in any specific way: anything that is not IME_ACTION_NONE should be sent to 192 // performEditorAction. 193 performEditorAction(imeOptionsActionId); 194 } else { 195 // No action label, and the action from imeOptions is NONE: this is a regular 196 // enter key that should input a carriage return. 197 didAutoCorrect = handleNonSpecialCharacter(settingsValues, 198 Constants.CODE_ENTER, x, y, spaceState, keyboardSwitcher); 199 } 200 break; 201 case Constants.CODE_SHIFT_ENTER: 202 didAutoCorrect = handleNonSpecialCharacter(settingsValues, 203 Constants.CODE_ENTER, x, y, spaceState, keyboardSwitcher); 204 break; 205 default: 206 didAutoCorrect = handleNonSpecialCharacter(settingsValues, 207 codePoint, x, y, spaceState, keyboardSwitcher); 208 break; 209 } 210 switcher.onCodeInput(codePoint); 211 // Reset after any single keystroke, except shift, capslock, and symbol-shift 212 if (!didAutoCorrect && codePoint != Constants.CODE_SHIFT 213 && codePoint != Constants.CODE_CAPSLOCK 214 && codePoint != Constants.CODE_SWITCH_ALPHA_SYMBOL) 215 mLastComposedWord.deactivate(); 216 if (Constants.CODE_DELETE != codePoint) { 217 mEnteredText = null; 218 } 219 mConnection.endBatchEdit(); 220 } 221 222 /** 223 * Handle inputting a code point to the editor. 224 * 225 * Non-special keys are those that generate a single code point. 226 * This includes all letters, digits, punctuation, separators, emoji. It excludes keys that 227 * manage keyboard-related stuff like shift, language switch, settings, layout switch, or 228 * any key that results in multiple code points like the ".com" key. 229 * 230 * @param codePoint the code point associated with the key. 231 * @param x the x-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable. 232 * @param y the y-coordinate of the key press, or Contants.NOT_A_COORDINATE if not applicable. 233 * @param spaceState the space state at start of the batch input. 234 * @return whether this caused an auto-correction to happen. 235 */ 236 private boolean handleNonSpecialCharacter(final SettingsValues settingsValues, 237 final int codePoint, final int x, final int y, final int spaceState, 238 // TODO: remove this argument. 239 final KeyboardSwitcher keyboardSwitcher) { 240 mSpaceState = SpaceState.NONE; 241 final boolean didAutoCorrect; 242 if (settingsValues.isWordSeparator(codePoint) 243 || Character.getType(codePoint) == Character.OTHER_SYMBOL) { 244 didAutoCorrect = mLatinIME.handleSeparator(codePoint, x, y, spaceState); 245 } else { 246 didAutoCorrect = false; 247 if (SpaceState.PHANTOM == spaceState) { 248 if (settingsValues.mIsInternal) { 249 if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) { 250 LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ", 251 mWordComposer); 252 } 253 } 254 if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { 255 // If we are in the middle of a recorrection, we need to commit the recorrection 256 // first so that we can insert the character at the current cursor position. 257 resetEntireInputState(settingsValues, mLastSelectionStart, mLastSelectionEnd); 258 } else { 259 commitTyped(LastComposedWord.NOT_A_SEPARATOR); 260 } 261 } 262 final int keyX, keyY; 263 final Keyboard keyboard = keyboardSwitcher.getKeyboard(); 264 if (keyboard != null && keyboard.hasProximityCharsCorrection(codePoint)) { 265 keyX = x; 266 keyY = y; 267 } else { 268 keyX = Constants.NOT_A_COORDINATE; 269 keyY = Constants.NOT_A_COORDINATE; 270 } 271 mLatinIME.handleCharacter(codePoint, keyX, keyY, spaceState); 272 } 273 return didAutoCorrect; 274 } 275 276 /** 277 * Handle a press on the backspace key. 278 * @param settingsValues The current settings values. 279 * @param spaceState The space state at start of this batch edit. 280 */ 281 private void handleBackspace(final SettingsValues settingsValues, final int spaceState, 282 // TODO: remove these arguments 283 final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) { 284 mDeleteCount++; 285 286 // In many cases, we may have to put the keyboard in auto-shift state again. However 287 // we want to wait a few milliseconds before doing it to avoid the keyboard flashing 288 // during key repeat. 289 handler.postUpdateShiftState(); 290 291 if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { 292 // If we are in the middle of a recorrection, we need to commit the recorrection 293 // first so that we can remove the character at the current cursor position. 294 resetEntireInputState(settingsValues, mLastSelectionStart, mLastSelectionEnd); 295 // When we exit this if-clause, mWordComposer.isComposingWord() will return false. 296 } 297 if (mWordComposer.isComposingWord()) { 298 if (mWordComposer.isBatchMode()) { 299 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 300 final String word = mWordComposer.getTypedWord(); 301 ResearchLogger.latinIME_handleBackspace_batch(word, 1); 302 } 303 final String rejectedSuggestion = mWordComposer.getTypedWord(); 304 mWordComposer.reset(); 305 mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion); 306 } else { 307 mWordComposer.deleteLast(); 308 } 309 mConnection.setComposingText(getTextWithUnderline(mWordComposer.getTypedWord()), 1); 310 handler.postUpdateSuggestionStrip(); 311 if (!mWordComposer.isComposingWord()) { 312 // If we just removed the last character, auto-caps mode may have changed so we 313 // need to re-evaluate. 314 keyboardSwitcher.updateShiftState(); 315 } 316 } else { 317 if (mLastComposedWord.canRevertCommit()) { 318 if (settingsValues.mIsInternal) { 319 LatinImeLoggerUtils.onAutoCorrectionCancellation(); 320 } 321 mLatinIME.revertCommit(); 322 return; 323 } 324 if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) { 325 // Cancel multi-character input: remove the text we just entered. 326 // This is triggered on backspace after a key that inputs multiple characters, 327 // like the smiley key or the .com key. 328 mConnection.deleteSurroundingText(mEnteredText.length(), 0); 329 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 330 ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText); 331 } 332 mEnteredText = null; 333 // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false. 334 // In addition we know that spaceState is false, and that we should not be 335 // reverting any autocorrect at this point. So we can safely return. 336 return; 337 } 338 if (SpaceState.DOUBLE == spaceState) { 339 handler.cancelDoubleSpacePeriodTimer(); 340 if (mConnection.revertDoubleSpacePeriod()) { 341 // No need to reset mSpaceState, it has already be done (that's why we 342 // receive it as a parameter) 343 return; 344 } 345 } else if (SpaceState.SWAP_PUNCTUATION == spaceState) { 346 if (mConnection.revertSwapPunctuation()) { 347 // Likewise 348 return; 349 } 350 } 351 352 // No cancelling of commit/double space/swap: we have a regular backspace. 353 // We should backspace one char and restart suggestion if at the end of a word. 354 if (mLastSelectionStart != mLastSelectionEnd) { 355 // If there is a selection, remove it. 356 final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart; 357 mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd); 358 // Reset mLastSelectionEnd to mLastSelectionStart. This is what is supposed to 359 // happen, and if it's wrong, the next call to onUpdateSelection will correct it, 360 // but we want to set it right away to avoid it being used with the wrong values 361 // later (typically, in a subsequent press on backspace). 362 mLastSelectionEnd = mLastSelectionStart; 363 mConnection.deleteSurroundingText(numCharsDeleted, 0); 364 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 365 ResearchLogger.latinIME_handleBackspace(numCharsDeleted, 366 false /* shouldUncommitLogUnit */); 367 } 368 } else { 369 // There is no selection, just delete one character. 370 if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) { 371 // This should never happen. 372 Log.e(TAG, "Backspace when we don't know the selection position"); 373 } 374 if (mLatinIME.mAppWorkAroundsUtils.isBeforeJellyBean() || 375 settingsValues.mInputAttributes.isTypeNull()) { 376 // There are two possible reasons to send a key event: either the field has 377 // type TYPE_NULL, in which case the keyboard should send events, or we are 378 // running in backward compatibility mode. Before Jelly bean, the keyboard 379 // would simulate a hardware keyboard event on pressing enter or delete. This 380 // is bad for many reasons (there are race conditions with commits) but some 381 // applications are relying on this behavior so we continue to support it for 382 // older apps, so we retain this behavior if the app has target SDK < JellyBean. 383 sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); 384 if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) { 385 sendDownUpKeyEvent(KeyEvent.KEYCODE_DEL); 386 } 387 } else { 388 final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); 389 if (codePointBeforeCursor == Constants.NOT_A_CODE) { 390 // Nothing to delete before the cursor. 391 return; 392 } 393 final int lengthToDelete = 394 Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1; 395 mConnection.deleteSurroundingText(lengthToDelete, 0); 396 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 397 ResearchLogger.latinIME_handleBackspace(lengthToDelete, 398 true /* shouldUncommitLogUnit */); 399 } 400 if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) { 401 final int codePointBeforeCursorToDeleteAgain = 402 mConnection.getCodePointBeforeCursor(); 403 if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) { 404 final int lengthToDeleteAgain = Character.isSupplementaryCodePoint( 405 codePointBeforeCursorToDeleteAgain) ? 2 : 1; 406 mConnection.deleteSurroundingText(lengthToDeleteAgain, 0); 407 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 408 ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain, 409 true /* shouldUncommitLogUnit */); 410 } 411 } 412 } 413 } 414 } 415 // TODO: move mDisplayOrientation to CurrentSettings. 416 if (settingsValues.isSuggestionsRequested(mLatinIME.mDisplayOrientation) 417 && settingsValues.mCurrentLanguageHasSpaces) { 418 mLatinIME.restartSuggestionsOnWordBeforeCursorIfAtEndOfWord(); 419 } 420 // We just removed a character. We need to update the auto-caps state. 421 keyboardSwitcher.updateShiftState(); 422 } 423 } 424 425 /** 426 * Handle a press on the language switch key (the "globe key") 427 */ 428 private void handleLanguageSwitchKey() { 429 mLatinIME.handleLanguageSwitchKey(); 430 } 431 432 /** 433 * Processes a recapitalize event. 434 */ 435 private void performRecapitalization() { 436 mLatinIME.performRecapitalization(); 437 } 438 439 /** 440 * @return the editor info for the current editor 441 */ 442 private EditorInfo getCurrentInputEditorInfo() { 443 return mLatinIME.getCurrentInputEditorInfo(); 444 } 445 446 /** 447 * @param actionId the action to perform 448 */ 449 private void performEditorAction(final int actionId) { 450 mConnection.performEditorAction(actionId); 451 } 452 453 /** 454 * Handle a press on the settings key. 455 */ 456 private void onSettingsKeyPressed() { 457 mLatinIME.onSettingsKeyPressed(); 458 } 459 460 // This will reset the whole input state to the starting state. It will clear 461 // the composing word, reset the last composed word, tell the inputconnection about it. 462 // TODO: remove all references to this in LatinIME and make this private 463 public void resetEntireInputState(final SettingsValues settingsValues, 464 final int newSelStart, final int newSelEnd) { 465 final boolean shouldFinishComposition = mWordComposer.isComposingWord(); 466 resetComposingState(true /* alsoResetLastComposedWord */); 467 if (settingsValues.mBigramPredictionEnabled) { 468 mLatinIME.clearSuggestionStrip(); 469 } else { 470 mLatinIME.setSuggestedWords(settingsValues.mSuggestPuncList, false); 471 } 472 mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd, 473 shouldFinishComposition); 474 } 475 476 // TODO: remove all references to this in LatinIME and make this private. 477 public void resetComposingState(final boolean alsoResetLastComposedWord) { 478 mWordComposer.reset(); 479 if (alsoResetLastComposedWord) { 480 mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; 481 } 482 } 483 484 // TODO: remove all references to this in LatinIME and make this private. Also, shouldn't 485 // this go in some *Utils class instead? 486 public CharSequence getTextWithUnderline(final String text) { 487 return mIsAutoCorrectionIndicatorOn 488 ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(mLatinIME, text) 489 : text; 490 } 491 492 private void sendDownUpKeyEvent(final int code) { 493 final long eventTime = SystemClock.uptimeMillis(); 494 mConnection.sendKeyEvent(new KeyEvent(eventTime, eventTime, 495 KeyEvent.ACTION_DOWN, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 496 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); 497 mConnection.sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime, 498 KeyEvent.ACTION_UP, code, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 499 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE)); 500 } 501 502 // TODO: remove all references to this in LatinIME and make this private 503 public void sendKeyCodePoint(final int code) { 504 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 505 ResearchLogger.latinIME_sendKeyCodePoint(code); 506 } 507 // TODO: Remove this special handling of digit letters. 508 // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}. 509 if (code >= '0' && code <= '9') { 510 sendDownUpKeyEvent(code - '0' + KeyEvent.KEYCODE_0); 511 return; 512 } 513 514 if (Constants.CODE_ENTER == code && mLatinIME.mAppWorkAroundsUtils.isBeforeJellyBean()) { 515 // Backward compatibility mode. Before Jelly bean, the keyboard would simulate 516 // a hardware keyboard event on pressing enter or delete. This is bad for many 517 // reasons (there are race conditions with commits) but some applications are 518 // relying on this behavior so we continue to support it for older apps. 519 sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER); 520 } else { 521 mConnection.commitText(StringUtils.newSingleCodePointString(code), 1); 522 } 523 } 524 525 // TODO: Make this private 526 public void commitTyped(final String separatorString) { 527 if (!mWordComposer.isComposingWord()) return; 528 final String typedWord = mWordComposer.getTypedWord(); 529 if (typedWord.length() > 0) { 530 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 531 ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode()); 532 } 533 mLatinIME.commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, 534 separatorString); 535 } 536 } 537} 538