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