From 5475b38328171a0841ae18074bd45380ec567e90 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 8 Jun 2012 19:04:09 +0900 Subject: Make a wrapper for the input connection. The goal is to simplify the code in LatinIME.java as well as having a handy place to put debug calls to see interaction with TextView. Change-Id: I255227e7e7343e0c2f3dcd1f185e5020d6186732 --- .../inputmethod/latin/RichInputConnection.java | 122 +++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 java/src/com/android/inputmethod/latin/RichInputConnection.java (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java') diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java new file mode 100644 index 000000000..449d08e9b --- /dev/null +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * 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.util.Log; +import android.view.KeyEvent; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.InputConnection; + +/** + * Wrapper for InputConnection to simplify interaction + */ +public class RichInputConnection { + private static final String TAG = RichInputConnection.class.getSimpleName(); + private static final boolean DBG = false; + InputConnection mIC; + int mNestLevel; + public RichInputConnection() { + mIC = null; + mNestLevel = 0; + } + + // TODO: remove this method - the whole point of this class is void if mIC is escaping + public InputConnection getInputConnection() { + return mIC; + } + + public void beginBatchEdit(final InputConnection newInputConnection) { + if (++mNestLevel == 1) { + mIC = newInputConnection; + if (null != mIC) mIC.beginBatchEdit(); + } else { + if (DBG) { + throw new RuntimeException("Nest level too deep"); + } else { + Log.e(TAG, "Nest level too deep : " + mNestLevel); + } + } + } + public void endBatchEdit() { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit(); + } + + public void finishComposingText() { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) mIC.finishComposingText(); + } + + public void commitText(final CharSequence text, final int i) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) mIC.commitText(text, i); + } + + public int getCursorCapsMode(final int inputType) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null == mIC) return Constants.TextUtils.CAP_MODE_OFF; + return mIC.getCursorCapsMode(inputType); + } + + public CharSequence getTextBeforeCursor(final int i, final int j) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) return mIC.getTextBeforeCursor(i, j); + return null; + } + + public CharSequence getTextAfterCursor(final int i, final int j) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) return mIC.getTextAfterCursor(i, j); + return null; + } + + public void deleteSurroundingText(final int i, final int j) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) mIC.deleteSurroundingText(i, j); + } + + public void performEditorAction(final int actionId) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) mIC.performEditorAction(actionId); + } + + public void sendKeyEvent(final KeyEvent keyEvent) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) mIC.sendKeyEvent(keyEvent); + } + + public void setComposingText(final CharSequence text, final int i) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) mIC.setComposingText(text, i); + } + + public void setSelection(final int from, final int to) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) mIC.setSelection(from, to); + } + + public void commitCorrection(final CorrectionInfo correctionInfo) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) mIC.commitCorrection(correctionInfo); + } + + public void commitCompletion(final CompletionInfo completionInfo) { + if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + if (null != mIC) mIC.commitCompletion(completionInfo); + } +} -- cgit v1.2.3-83-g751a From 02308bec632a5df23325c916bffec5def16b22b4 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 8 Jun 2012 19:44:38 +0900 Subject: Merge RichInputConnection with EditingUtils Change-Id: I9982ff325bef56694402caef28a77683c52ccd71 --- .../android/inputmethod/latin/EditingUtils.java | 218 --------------------- .../com/android/inputmethod/latin/LatinIME.java | 21 +- .../android/inputmethod/latin/ResearchLogger.java | 5 +- .../inputmethod/latin/RichInputConnection.java | 188 +++++++++++++++++- .../inputmethod/latin/EditingUtilsTests.java | 161 --------------- .../latin/RichInputConnectionTests.java | 178 +++++++++++++++++ 6 files changed, 370 insertions(+), 401 deletions(-) delete mode 100644 java/src/com/android/inputmethod/latin/EditingUtils.java delete mode 100644 tests/src/com/android/inputmethod/latin/EditingUtilsTests.java create mode 100644 tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java') diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java deleted file mode 100644 index 479b3bf5a..000000000 --- a/java/src/com/android/inputmethod/latin/EditingUtils.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * 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.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; - -import java.util.regex.Pattern; - -/** - * Utility methods to deal with editing text through an InputConnection. - */ -public class EditingUtils { - /** - * Number of characters we want to look back in order to identify the previous word - */ - // Provision for a long word pair and a separator - private static final int LOOKBACK_CHARACTER_NUM = BinaryDictionary.MAX_WORD_LENGTH * 2 + 1; - private static final int INVALID_CURSOR_POSITION = -1; - - private EditingUtils() { - // Unintentional empty constructor for singleton. - } - - private static int getCursorPosition(InputConnection connection) { - if (null == connection) return INVALID_CURSOR_POSITION; - final ExtractedText extracted = connection.getExtractedText(new ExtractedTextRequest(), 0); - if (extracted == null) { - return INVALID_CURSOR_POSITION; - } - return extracted.startOffset + extracted.selectionStart; - } - - /** - * @param connection connection to the current text field. - * @param separators characters which may separate words - * @return the word that surrounds the cursor, including up to one trailing - * separator. For example, if the field contains "he|llo world", where | - * represents the cursor, then "hello " will be returned. - */ - public static String getWordAtCursor(InputConnection connection, String separators) { - // getWordRangeAtCursor returns null if the connection is null - Range r = getWordRangeAtCursor(connection, separators, 0); - return (r == null) ? null : r.mWord; - } - - /** - * Represents a range of text, relative to the current cursor position. - */ - public static class Range { - /** Characters before selection start */ - public final int mCharsBefore; - - /** - * Characters after selection start, including one trailing word - * separator. - */ - public final int mCharsAfter; - - /** The actual characters that make up a word */ - public final String mWord; - - public Range(int charsBefore, int charsAfter, String word) { - if (charsBefore < 0 || charsAfter < 0) { - throw new IndexOutOfBoundsException(); - } - this.mCharsBefore = charsBefore; - this.mCharsAfter = charsAfter; - this.mWord = word; - } - } - - /** - * Returns the text surrounding the cursor. - * - * @param connection the InputConnection to the TextView - * @param sep a string of characters that split words. - * @param additionalPrecedingWordsCount the number of words before the current word that should - * be included in the returned range - * @return a range containing the text surrounding the cursor - */ - public static Range getWordRangeAtCursor(InputConnection connection, String sep, - int additionalPrecedingWordsCount) { - if (connection == null || sep == null) { - return null; - } - CharSequence before = connection.getTextBeforeCursor(1000, 0); - CharSequence after = connection.getTextAfterCursor(1000, 0); - if (before == null || after == null) { - return null; - } - - // Going backward, alternate skipping non-separators and separators until enough words - // have been read. - int start = before.length(); - boolean isStoppingAtWhitespace = true; // toggles to indicate what to stop at - while (true) { // see comments below for why this is guaranteed to halt - while (start > 0) { - final int codePoint = Character.codePointBefore(before, start); - if (isStoppingAtWhitespace == isSeparator(codePoint, sep)) { - break; // inner loop - } - --start; - if (Character.isSupplementaryCodePoint(codePoint)) { - --start; - } - } - // isStoppingAtWhitespace is true every other time through the loop, - // so additionalPrecedingWordsCount is guaranteed to become < 0, which - // guarantees outer loop termination - if (isStoppingAtWhitespace && (--additionalPrecedingWordsCount < 0)) { - break; // outer loop - } - isStoppingAtWhitespace = !isStoppingAtWhitespace; - } - - // Find last word separator after the cursor - int end = -1; - while (++end < after.length()) { - final int codePoint = Character.codePointAt(after, end); - if (isSeparator(codePoint, sep)) { - break; - } - if (Character.isSupplementaryCodePoint(codePoint)) { - ++end; - } - } - - int cursor = getCursorPosition(connection); - if (start >= 0 && cursor + end <= after.length() + before.length()) { - String word = before.toString().substring(start, before.length()) - + after.toString().substring(0, end); - return new Range(before.length() - start, end, word); - } - - return null; - } - - private static boolean isSeparator(int code, String sep) { - return sep.indexOf(code) != -1; - } - - private static final Pattern spaceRegex = Pattern.compile("\\s+"); - - public static CharSequence getPreviousWord(InputConnection connection, - String sentenceSeperators) { - //TODO: Should fix this. This could be slow! - if (null == connection) return null; - CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); - return getPreviousWord(prev, sentenceSeperators); - } - - // Get the word before the whitespace preceding the non-whitespace preceding the cursor. - // Also, it won't return words that end in a separator. - // Example : - // "abc def|" -> abc - // "abc def |" -> abc - // "abc def. |" -> abc - // "abc def . |" -> def - // "abc|" -> null - // "abc |" -> null - // "abc. def|" -> null - public static CharSequence getPreviousWord(CharSequence prev, String sentenceSeperators) { - if (prev == null) return null; - String[] w = spaceRegex.split(prev); - - // If we can't find two words, or we found an empty word, return null. - if (w.length < 2 || w[w.length - 2].length() <= 0) return null; - - // If ends in a separator, return null - char lastChar = w[w.length - 2].charAt(w[w.length - 2].length() - 1); - if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; - - return w[w.length - 2]; - } - - public static CharSequence getThisWord(InputConnection connection, String sentenceSeperators) { - if (null == connection) return null; - final CharSequence prev = connection.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); - return getThisWord(prev, sentenceSeperators); - } - - // Get the word immediately before the cursor, even if there is whitespace between it and - // the cursor - but not if there is punctuation. - // Example : - // "abc def|" -> def - // "abc def |" -> def - // "abc def. |" -> null - // "abc def . |" -> null - public static CharSequence getThisWord(CharSequence prev, String sentenceSeperators) { - if (prev == null) return null; - String[] w = spaceRegex.split(prev); - - // No word : return null - if (w.length < 1 || w[w.length - 1].length() <= 0) return null; - - // If ends in a separator, return null - char lastChar = w[w.length - 1].charAt(w[w.length - 1].length() - 1); - if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; - - return w[w.length - 1]; - } -} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 5b499e4be..656f6d7b3 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -783,7 +783,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen ResearchLogger.latinIME_onUpdateSelection(mLastSelectionStart, mLastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart, composingSpanEnd, mExpectingUpdateSelection, - expectingUpdateSelectionFromLogger, getCurrentInputConnection()); + expectingUpdateSelectionFromLogger, mConnection); if (expectingUpdateSelectionFromLogger) { return; } @@ -1760,11 +1760,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // TODO: May need a better way of retrieving previous word - final CharSequence prevWord; - // TODO: move getPreviousWord to AutoInputConnection - prevWord = EditingUtils.getPreviousWord(mConnection.getInputConnection(), - mSettingsValues.mWordSeparators); - + final CharSequence prevWord = mConnection.getPreviousWord(mSettingsValues.mWordSeparators); final CharSequence typedWord = mWordComposer.getTypedWord(); // getSuggestedWords handles gracefully a null value of prevWord final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer, @@ -1993,8 +1989,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final SuggestedWords suggestedWords; if (mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) { - final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(), - mSettingsValues.mWordSeparators); + final CharSequence prevWord = mConnection.getThisWord(mSettingsValues.mWordSeparators); if (!TextUtils.isEmpty(prevWord)) { suggestedWords = mSuggest.getBigramPredictions(prevWord); } else { @@ -2031,10 +2026,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } if (mUserHistoryDictionary != null) { - final CharSequence prevWord; - // TODO: move getPreviousWord to AutoInputConnection - prevWord = EditingUtils.getPreviousWord(mConnection.getInputConnection(), - mSettingsValues.mWordSeparators); + final CharSequence prevWord + = mConnection.getPreviousWord(mSettingsValues.mWordSeparators); final String secondWord; if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) { secondWord = suggestion.toString().toLowerCase( @@ -2093,9 +2086,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe) // Example: " -|" gets rejected here but "e-|" and "e|" are okay - // TODO: move getWordAtCursor inside AutoInputConnection - CharSequence word = EditingUtils.getWordAtCursor(mConnection.getInputConnection(), - mSettingsValues.mWordSeparators); + CharSequence word = mConnection.getWordAtCursor(mSettingsValues.mWordSeparators); // We don't suggest on leading single quotes, so we have to remove them from the word if // it starts with single quotes. while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) { diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java index a7e7738d8..f27d07074 100644 --- a/java/src/com/android/inputmethod/latin/ResearchLogger.java +++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java @@ -612,12 +612,13 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd, final int newSelStart, final int newSelEnd, final int composingSpanStart, final int composingSpanEnd, final boolean expectingUpdateSelection, - final boolean expectingUpdateSelectionFromLogger, final InputConnection connection) { + final boolean expectingUpdateSelectionFromLogger, + final RichInputConnection connection) { final Object[] values = { lastSelectionStart, lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart, composingSpanEnd, expectingUpdateSelection, expectingUpdateSelectionFromLogger, - EditingUtils.getWordRangeAtCursor(connection, WHITESPACE_SEPARATORS, 1).mWord + connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1).mWord }; getInstance().writeEvent(EVENTKEYS_LATINIME_ONUPDATESELECTION, values); } diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 449d08e9b..c9ee09396 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -20,14 +20,23 @@ import android.util.Log; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; +import java.util.regex.Pattern; + /** * Wrapper for InputConnection to simplify interaction */ public class RichInputConnection { private static final String TAG = RichInputConnection.class.getSimpleName(); private static final boolean DBG = false; + // Provision for a long word pair and a separator + private static final int LOOKBACK_CHARACTER_NUM = BinaryDictionary.MAX_WORD_LENGTH * 2 + 1; + private static final Pattern spaceRegex = Pattern.compile("\\s+"); + private static final int INVALID_CURSOR_POSITION = -1; + InputConnection mIC; int mNestLevel; public RichInputConnection() { @@ -35,11 +44,6 @@ public class RichInputConnection { mNestLevel = 0; } - // TODO: remove this method - the whole point of this class is void if mIC is escaping - public InputConnection getInputConnection() { - return mIC; - } - public void beginBatchEdit(final InputConnection newInputConnection) { if (++mNestLevel == 1) { mIC = newInputConnection; @@ -119,4 +123,178 @@ public class RichInputConnection { if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead if (null != mIC) mIC.commitCompletion(completionInfo); } + + public CharSequence getPreviousWord(final String sentenceSeperators) { + //TODO: Should fix this. This could be slow! + if (null == mIC) return null; + CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); + return getPreviousWord(prev, sentenceSeperators); + } + + /** + * Represents a range of text, relative to the current cursor position. + */ + public static class Range { + /** Characters before selection start */ + public final int mCharsBefore; + + /** + * Characters after selection start, including one trailing word + * separator. + */ + public final int mCharsAfter; + + /** The actual characters that make up a word */ + public final String mWord; + + public Range(int charsBefore, int charsAfter, String word) { + if (charsBefore < 0 || charsAfter < 0) { + throw new IndexOutOfBoundsException(); + } + this.mCharsBefore = charsBefore; + this.mCharsAfter = charsAfter; + this.mWord = word; + } + } + + private static boolean isSeparator(int code, String sep) { + return sep.indexOf(code) != -1; + } + + // Get the word before the whitespace preceding the non-whitespace preceding the cursor. + // Also, it won't return words that end in a separator. + // Example : + // "abc def|" -> abc + // "abc def |" -> abc + // "abc def. |" -> abc + // "abc def . |" -> def + // "abc|" -> null + // "abc |" -> null + // "abc. def|" -> null + public static CharSequence getPreviousWord(CharSequence prev, String sentenceSeperators) { + if (prev == null) return null; + String[] w = spaceRegex.split(prev); + + // If we can't find two words, or we found an empty word, return null. + if (w.length < 2 || w[w.length - 2].length() <= 0) return null; + + // If ends in a separator, return null + char lastChar = w[w.length - 2].charAt(w[w.length - 2].length() - 1); + if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; + + return w[w.length - 2]; + } + + public CharSequence getThisWord(String sentenceSeperators) { + if (null == mIC) return null; + final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); + return getThisWord(prev, sentenceSeperators); + } + + // Get the word immediately before the cursor, even if there is whitespace between it and + // the cursor - but not if there is punctuation. + // Example : + // "abc def|" -> def + // "abc def |" -> def + // "abc def. |" -> null + // "abc def . |" -> null + public static CharSequence getThisWord(CharSequence prev, String sentenceSeperators) { + if (prev == null) return null; + String[] w = spaceRegex.split(prev); + + // No word : return null + if (w.length < 1 || w[w.length - 1].length() <= 0) return null; + + // If ends in a separator, return null + char lastChar = w[w.length - 1].charAt(w[w.length - 1].length() - 1); + if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; + + return w[w.length - 1]; + } + + /** + * @param separators characters which may separate words + * @return the word that surrounds the cursor, including up to one trailing + * separator. For example, if the field contains "he|llo world", where | + * represents the cursor, then "hello " will be returned. + */ + public String getWordAtCursor(String separators) { + // getWordRangeAtCursor returns null if the connection is null + Range r = getWordRangeAtCursor(separators, 0); + return (r == null) ? null : r.mWord; + } + + private int getCursorPosition() { + if (null == mIC) return INVALID_CURSOR_POSITION; + final ExtractedText extracted = mIC.getExtractedText(new ExtractedTextRequest(), 0); + if (extracted == null) { + return INVALID_CURSOR_POSITION; + } + return extracted.startOffset + extracted.selectionStart; + } + + /** + * Returns the text surrounding the cursor. + * + * @param sep a string of characters that split words. + * @param additionalPrecedingWordsCount the number of words before the current word that should + * be included in the returned range + * @return a range containing the text surrounding the cursor + */ + public Range getWordRangeAtCursor(String sep, int additionalPrecedingWordsCount) { + if (mIC == null || sep == null) { + return null; + } + CharSequence before = mIC.getTextBeforeCursor(1000, 0); + CharSequence after = mIC.getTextAfterCursor(1000, 0); + if (before == null || after == null) { + return null; + } + + // Going backward, alternate skipping non-separators and separators until enough words + // have been read. + int start = before.length(); + boolean isStoppingAtWhitespace = true; // toggles to indicate what to stop at + while (true) { // see comments below for why this is guaranteed to halt + while (start > 0) { + final int codePoint = Character.codePointBefore(before, start); + if (isStoppingAtWhitespace == isSeparator(codePoint, sep)) { + break; // inner loop + } + --start; + if (Character.isSupplementaryCodePoint(codePoint)) { + --start; + } + } + // isStoppingAtWhitespace is true every other time through the loop, + // so additionalPrecedingWordsCount is guaranteed to become < 0, which + // guarantees outer loop termination + if (isStoppingAtWhitespace && (--additionalPrecedingWordsCount < 0)) { + break; // outer loop + } + isStoppingAtWhitespace = !isStoppingAtWhitespace; + } + + // Find last word separator after the cursor + int end = -1; + while (++end < after.length()) { + final int codePoint = Character.codePointAt(after, end); + if (isSeparator(codePoint, sep)) { + break; + } + if (Character.isSupplementaryCodePoint(codePoint)) { + ++end; + } + } + + int cursor = getCursorPosition(); + if (start >= 0 && cursor + end <= after.length() + before.length()) { + String word = before.toString().substring(start, before.length()) + + after.toString().substring(0, end); + return new Range(before.length() - start, end, word); + } + + return null; + } + } diff --git a/tests/src/com/android/inputmethod/latin/EditingUtilsTests.java b/tests/src/com/android/inputmethod/latin/EditingUtilsTests.java deleted file mode 100644 index c73f8891f..000000000 --- a/tests/src/com/android/inputmethod/latin/EditingUtilsTests.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * 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.test.AndroidTestCase; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputConnection; -import android.view.inputmethod.InputConnectionWrapper; - -import com.android.inputmethod.latin.EditingUtils.Range; - -public class EditingUtilsTests extends AndroidTestCase { - - // The following is meant to be a reasonable default for - // the "word_separators" resource. - private static final String sSeparators = ".,:;!?-"; - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - private class MockConnection extends InputConnectionWrapper { - final String mTextBefore; - final String mTextAfter; - final ExtractedText mExtractedText; - - public MockConnection(String textBefore, String textAfter, ExtractedText extractedText) { - super(null, false); - mTextBefore = textBefore; - mTextAfter = textAfter; - mExtractedText = extractedText; - } - - /* (non-Javadoc) - * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int) - */ - @Override - public CharSequence getTextBeforeCursor(int n, int flags) { - return mTextBefore; - } - - /* (non-Javadoc) - * @see android.view.inputmethod.InputConnectionWrapper#getTextAfterCursor(int, int) - */ - @Override - public CharSequence getTextAfterCursor(int n, int flags) { - return mTextAfter; - } - - /* (non-Javadoc) - * @see android.view.inputmethod.InputConnectionWrapper#getExtractedText(ExtractedTextRequest, int) - */ - @Override - public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { - return mExtractedText; - } - } - - /************************** Tests ************************/ - - /** - * Test for getting previous word (for bigram suggestions) - */ - public void testGetPreviousWord() { - // If one of the following cases breaks, the bigram suggestions won't work. - assertEquals(EditingUtils.getPreviousWord("abc def", sSeparators), "abc"); - assertNull(EditingUtils.getPreviousWord("abc", sSeparators)); - assertNull(EditingUtils.getPreviousWord("abc. def", sSeparators)); - - // The following tests reflect the current behavior of the function - // EditingUtils#getPreviousWord. - // TODO: However at this time, the code does never go - // into such a path, so it should be safe to change the behavior of - // this function if needed - especially since it does not seem very - // logical. These tests are just there to catch any unintentional - // changes in the behavior of the EditingUtils#getPreviousWord method. - assertEquals(EditingUtils.getPreviousWord("abc def ", sSeparators), "abc"); - assertEquals(EditingUtils.getPreviousWord("abc def.", sSeparators), "abc"); - assertEquals(EditingUtils.getPreviousWord("abc def .", sSeparators), "def"); - assertNull(EditingUtils.getPreviousWord("abc ", sSeparators)); - } - - /** - * Test for getting the word before the cursor (for bigram) - */ - public void testGetThisWord() { - assertEquals(EditingUtils.getThisWord("abc def", sSeparators), "def"); - assertEquals(EditingUtils.getThisWord("abc def ", sSeparators), "def"); - assertNull(EditingUtils.getThisWord("abc def.", sSeparators)); - assertNull(EditingUtils.getThisWord("abc def .", sSeparators)); - } - - /** - * Test logic in getting the word range at the cursor. - */ - public void testGetWordRangeAtCursor() { - ExtractedText et = new ExtractedText(); - InputConnection mockConnection; - mockConnection = new MockConnection("word wo", "rd", et); - et.startOffset = 0; - et.selectionStart = 7; - Range r; - - // basic case - r = EditingUtils.getWordRangeAtCursor(mockConnection, " ", 0); - assertEquals("word", r.mWord); - r = null; - - // more than one word - r = EditingUtils.getWordRangeAtCursor(mockConnection, " ", 1); - assertEquals("word word", r.mWord); - r = null; - - // tab character instead of space - mockConnection = new MockConnection("one\tword\two", "rd", et); - r = EditingUtils.getWordRangeAtCursor(mockConnection, "\t", 1); - assertEquals("word\tword", r.mWord); - r = null; - - // only one word doesn't go too far - mockConnection = new MockConnection("one\tword\two", "rd", et); - r = EditingUtils.getWordRangeAtCursor(mockConnection, "\t", 1); - assertEquals("word\tword", r.mWord); - r = null; - - // tab or space - mockConnection = new MockConnection("one word\two", "rd", et); - r = EditingUtils.getWordRangeAtCursor(mockConnection, " \t", 1); - assertEquals("word\tword", r.mWord); - r = null; - - // tab or space multiword - mockConnection = new MockConnection("one word\two", "rd", et); - r = EditingUtils.getWordRangeAtCursor(mockConnection, " \t", 2); - assertEquals("one word\tword", r.mWord); - r = null; - - // splitting on supplementary character - final String supplementaryChar = "\uD840\uDC8A"; - mockConnection = new MockConnection("one word" + supplementaryChar + "wo", "rd", et); - r = EditingUtils.getWordRangeAtCursor(mockConnection, supplementaryChar, 0); - assertEquals("word", r.mWord); - r = null; - } -} diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java new file mode 100644 index 000000000..9ce581df8 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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.test.AndroidTestCase; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnection; +import android.view.inputmethod.InputConnectionWrapper; + +import com.android.inputmethod.latin.RichInputConnection.Range; + +public class RichInputConnectionTests extends AndroidTestCase { + + // The following is meant to be a reasonable default for + // the "word_separators" resource. + private static final String sSeparators = ".,:;!?-"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + private class MockConnection extends InputConnectionWrapper { + final String mTextBefore; + final String mTextAfter; + final ExtractedText mExtractedText; + + public MockConnection(String textBefore, String textAfter, ExtractedText extractedText) { + super(null, false); + mTextBefore = textBefore; + mTextAfter = textAfter; + mExtractedText = extractedText; + } + + /* (non-Javadoc) + * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int) + */ + @Override + public CharSequence getTextBeforeCursor(int n, int flags) { + return mTextBefore; + } + + /* (non-Javadoc) + * @see android.view.inputmethod.InputConnectionWrapper#getTextAfterCursor(int, int) + */ + @Override + public CharSequence getTextAfterCursor(int n, int flags) { + return mTextAfter; + } + + /* (non-Javadoc) + * @see android.view.inputmethod.InputConnectionWrapper#getExtractedText( + * ExtractedTextRequest, int) + */ + @Override + public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { + return mExtractedText; + } + + @Override + public boolean beginBatchEdit() { + return true; + } + + @Override + public boolean endBatchEdit() { + return true; + } + } + + /************************** Tests ************************/ + + /** + * Test for getting previous word (for bigram suggestions) + */ + public void testGetPreviousWord() { + // If one of the following cases breaks, the bigram suggestions won't work. + assertEquals(RichInputConnection.getPreviousWord("abc def", sSeparators), "abc"); + assertNull(RichInputConnection.getPreviousWord("abc", sSeparators)); + assertNull(RichInputConnection.getPreviousWord("abc. def", sSeparators)); + + // The following tests reflect the current behavior of the function + // RichInputConnection#getPreviousWord. + // TODO: However at this time, the code does never go + // into such a path, so it should be safe to change the behavior of + // this function if needed - especially since it does not seem very + // logical. These tests are just there to catch any unintentional + // changes in the behavior of the RichInputConnection#getPreviousWord method. + assertEquals(RichInputConnection.getPreviousWord("abc def ", sSeparators), "abc"); + assertEquals(RichInputConnection.getPreviousWord("abc def.", sSeparators), "abc"); + assertEquals(RichInputConnection.getPreviousWord("abc def .", sSeparators), "def"); + assertNull(RichInputConnection.getPreviousWord("abc ", sSeparators)); + } + + /** + * Test for getting the word before the cursor (for bigram) + */ + public void testGetThisWord() { + assertEquals(RichInputConnection.getThisWord("abc def", sSeparators), "def"); + assertEquals(RichInputConnection.getThisWord("abc def ", sSeparators), "def"); + assertNull(RichInputConnection.getThisWord("abc def.", sSeparators)); + assertNull(RichInputConnection.getThisWord("abc def .", sSeparators)); + } + + /** + * Test logic in getting the word range at the cursor. + */ + public void testGetWordRangeAtCursor() { + ExtractedText et = new ExtractedText(); + final RichInputConnection ic = new RichInputConnection(); + InputConnection mockConnection; + mockConnection = new MockConnection("word wo", "rd", et); + et.startOffset = 0; + et.selectionStart = 7; + Range r; + + ic.beginBatchEdit(mockConnection); + // basic case + r = ic.getWordRangeAtCursor(" ", 0); + assertEquals("word", r.mWord); + + // more than one word + r = ic.getWordRangeAtCursor(" ", 1); + assertEquals("word word", r.mWord); + ic.endBatchEdit(); + + // tab character instead of space + mockConnection = new MockConnection("one\tword\two", "rd", et); + ic.beginBatchEdit(mockConnection); + r = ic.getWordRangeAtCursor("\t", 1); + ic.endBatchEdit(); + assertEquals("word\tword", r.mWord); + + // only one word doesn't go too far + mockConnection = new MockConnection("one\tword\two", "rd", et); + ic.beginBatchEdit(mockConnection); + r = ic.getWordRangeAtCursor("\t", 1); + ic.endBatchEdit(); + assertEquals("word\tword", r.mWord); + + // tab or space + mockConnection = new MockConnection("one word\two", "rd", et); + ic.beginBatchEdit(mockConnection); + r = ic.getWordRangeAtCursor(" \t", 1); + ic.endBatchEdit(); + assertEquals("word\tword", r.mWord); + + // tab or space multiword + mockConnection = new MockConnection("one word\two", "rd", et); + ic.beginBatchEdit(mockConnection); + r = ic.getWordRangeAtCursor(" \t", 2); + ic.endBatchEdit(); + assertEquals("one word\tword", r.mWord); + + // splitting on supplementary character + final String supplementaryChar = "\uD840\uDC8A"; + mockConnection = new MockConnection("one word" + supplementaryChar + "wo", "rd", et); + ic.beginBatchEdit(mockConnection); + r = ic.getWordRangeAtCursor(supplementaryChar, 0); + ic.endBatchEdit(); + assertEquals("word", r.mWord); + } +} -- cgit v1.2.3-83-g751a From bbbdab12be748cdc2158f0e04bbb5478052ecd89 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 8 Jun 2012 20:23:13 +0900 Subject: Move some editing methods to RichInputConnection. Change-Id: I8527776a4bd8236a85bb77f9791f9b76977ad181 --- .../com/android/inputmethod/latin/LatinIME.java | 45 +++++----------------- .../inputmethod/latin/RichInputConnection.java | 29 ++++++++++++++ 2 files changed, 39 insertions(+), 35 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 562753374..1f16f84fa 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1076,7 +1076,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return mConnection.getCursorCapsMode(inputType); } - private void swapSwapperAndSpaceWhileInBatchEdit() { + private void swapSwapperAndSpace() { CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0); // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called. if (lastTwo != null && lastTwo.length() == 2 @@ -1093,7 +1093,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private boolean maybeDoubleSpaceWhileInBatchEdit() { + private boolean maybeDoubleSpace() { if (mCorrectionMode == Suggest.CORRECTION_NONE) return false; final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0); if (lastThree != null && lastThree.length() == 3 @@ -1125,17 +1125,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET; } - private void removeTrailingSpace() { - final CharSequence lastOne = mConnection.getTextBeforeCursor(1, 0); - if (lastOne != null && lastOne.length() == 1 - && lastOne.charAt(0) == Keyboard.CODE_SPACE) { - mConnection.deleteSurroundingText(1, 0); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_deleteSurroundingText(1); - } - } - } - @Override public boolean addWordToDictionary(String word) { mUserDictionary.addWordToUserDictionary(word, 128); @@ -1487,7 +1476,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private boolean maybeStripSpace(final int code, final int spaceState, final boolean isFromSuggestionStrip) { if (Keyboard.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) { - removeTrailingSpace(); + mConnection.removeTrailingSpace(); return false; } else if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState) @@ -1496,7 +1485,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return true; } else { if (mSettingsValues.isWeakSpaceStripper(code)) { - removeTrailingSpace(); + mConnection.removeTrailingSpace(); } return false; } @@ -1523,7 +1512,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // thread here. if (!isComposingWord && (isAlphabet(primaryCode) || mSettingsValues.isSymbolExcludedFromWordSeparators(primaryCode)) - && isSuggestionsRequested() && !isCursorTouchingWord()) { + && isSuggestionsRequested() && !mConnection.isCursorTouchingWord(mSettingsValues)) { // Reset entirely the composing state anyway, then start composing a new word unless // the character is a single quote. The idea here is, single quote is not a // separator and it should be treated as a normal character, except in the first @@ -1553,7 +1542,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen sendKeyCodePoint(primaryCode); if (swapWeakSpace) { - swapSwapperAndSpaceWhileInBatchEdit(); + swapSwapperAndSpace(); mSpaceState = SPACE_STATE_WEAK; } // Some characters are not word separators, yet they don't start a new @@ -1606,7 +1595,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (Keyboard.CODE_SPACE == primaryCode) { if (isSuggestionsRequested()) { - if (maybeDoubleSpaceWhileInBatchEdit()) { + if (maybeDoubleSpace()) { mSpaceState = SPACE_STATE_DOUBLE; } else if (!isShowingPunctuationList()) { mSpaceState = SPACE_STATE_WEAK; @@ -1614,13 +1603,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } mHandler.startDoubleSpacesTimer(); - if (!isCursorTouchingWord()) { + if (!mConnection.isCursorTouchingWord(mSettingsValues)) { mHandler.cancelUpdateSuggestions(); mHandler.postUpdateBigramPredictions(); } } else { if (swapWeakSpace) { - swapSwapperAndSpaceWhileInBatchEdit(); + swapSwapperAndSpace(); mSpaceState = SPACE_STATE_SWAP_PUNCTUATION; } else if (SPACE_STATE_PHANTOM == spaceState) { // If we are in phantom space state, and the user presses a separator, we want to @@ -2033,20 +2022,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return null; } - public boolean isCursorTouchingWord() { - CharSequence before = mConnection.getTextBeforeCursor(1, 0); - CharSequence after = mConnection.getTextAfterCursor(1, 0); - if (!TextUtils.isEmpty(before) && !mSettingsValues.isWordSeparator(before.charAt(0)) - && !mSettingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) { - return true; - } - if (!TextUtils.isEmpty(after) && !mSettingsValues.isWordSeparator(after.charAt(0)) - && !mSettingsValues.isSymbolExcludedFromWordSeparators(after.charAt(0))) { - return true; - } - return false; - } - private boolean sameAsTextBeforeCursor(final CharSequence text) { final CharSequence beforeText = mConnection.getTextBeforeCursor(text.length(), 0); return TextUtils.equals(text, beforeText); @@ -2230,7 +2205,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Since we just changed languages, we should re-evaluate suggestions with whatever word // we are currently composing. If we are not composing anything, we may want to display // predictions or punctuation signs (which is done by updateBigramPredictions anyway). - if (isCursorTouchingWord()) { + if (mConnection.isCursorTouchingWord(mSettingsValues)) { mHandler.postUpdateSuggestions(); } else { mHandler.postUpdateBigramPredictions(); diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index c9ee09396..d7ed36d34 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; @@ -24,6 +25,9 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.define.ProductionFlag; + import java.util.regex.Pattern; /** @@ -297,4 +301,29 @@ public class RichInputConnection { return null; } + public boolean isCursorTouchingWord(final SettingsValues settingsValues) { + CharSequence before = getTextBeforeCursor(1, 0); + CharSequence after = getTextAfterCursor(1, 0); + if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0)) + && !settingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) { + return true; + } + if (!TextUtils.isEmpty(after) && !settingsValues.isWordSeparator(after.charAt(0)) + && !settingsValues.isSymbolExcludedFromWordSeparators(after.charAt(0))) { + return true; + } + return false; + } + + public void removeTrailingSpace() { + final CharSequence lastOne = getTextBeforeCursor(1, 0); + if (lastOne != null && lastOne.length() == 1 + && lastOne.charAt(0) == Keyboard.CODE_SPACE) { + deleteSurroundingText(1, 0); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_deleteSurroundingText(1); + } + } + } + } -- cgit v1.2.3-83-g751a From 747cf0435a7e978dfd43c30bd931b56146c3d852 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 8 Jun 2012 21:45:41 +0900 Subject: Move two editing utility methods to RichInputConnection Change-Id: If8d03bea1b6369cb3043da6321048d052995d664 --- .../com/android/inputmethod/latin/LatinIME.java | 44 ++-------------------- .../inputmethod/latin/RichInputConnection.java | 44 ++++++++++++++++++++++ 2 files changed, 48 insertions(+), 40 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index e7f0eacde..39f14425c 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1352,7 +1352,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // In many cases, we may have to put the keyboard in auto-shift state again. mHandler.postUpdateShiftState(); - if (mEnteredText != null && sameAsTextBeforeCursor(mEnteredText)) { + if (mEnteredText != null && mConnection.sameAsTextBeforeCursor(mEnteredText)) { // Cancel multi-character input: remove the text we just entered. // This is triggered on backspace after a key that inputs multiple characters, // like the smiley key or the .com key. @@ -2000,51 +2000,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return null; } - private boolean sameAsTextBeforeCursor(final CharSequence text) { - final CharSequence beforeText = mConnection.getTextBeforeCursor(text.length(), 0); - return TextUtils.equals(text, beforeText); - } - /** * Check if the cursor is actually at the end of a word. If so, restart suggestions on this * word, else do nothing. */ private void restartSuggestionsOnWordBeforeCursorIfAtEndOfWord() { - // Bail out if the cursor is not at the end of a word (cursor must be preceded by - // non-whitespace, non-separator, non-start-of-text) - // Example ("|" is the cursor here) : "|a" " |a" " | " all get rejected here. - final CharSequence textBeforeCursor = mConnection.getTextBeforeCursor(1, 0); - if (TextUtils.isEmpty(textBeforeCursor) - || mCurrentSettings.isWordSeparator(textBeforeCursor.charAt(0))) return; - - // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace, - // separator or end of line/text) - // Example: "test|" "te|st" get rejected here - final CharSequence textAfterCursor = mConnection.getTextAfterCursor(1, 0); - if (!TextUtils.isEmpty(textAfterCursor) - && !mCurrentSettings.isWordSeparator(textAfterCursor.charAt(0))) return; - - // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe) - // Example: " -|" gets rejected here but "e-|" and "e|" are okay - CharSequence word = mConnection.getWordAtCursor(mCurrentSettings.mWordSeparators); - // We don't suggest on leading single quotes, so we have to remove them from the word if - // it starts with single quotes. - while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) { - word = word.subSequence(1, word.length()); - } - if (TextUtils.isEmpty(word)) return; - final char firstChar = word.charAt(0); // we just tested that word is not empty - if (word.length() == 1 && !Character.isLetter(firstChar)) return; - - // We only suggest on words that start with a letter or a symbol that is excluded from - // word separators (see #handleCharacterWhileInBatchEdit). - if (!(isAlphabet(firstChar) - || mCurrentSettings.isSymbolExcludedFromWordSeparators(firstChar))) { - return; + final CharSequence word = mConnection.getWordBeforeCursorIfAtEndOfWord(mCurrentSettings); + if (null != word) { + restartSuggestionsOnWordBeforeCursor(word); } - - // Okay, we are at the end of a word. Restart suggestions. - restartSuggestionsOnWordBeforeCursor(word); } private void restartSuggestionsOnWordBeforeCursor(final CharSequence word) { diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index d7ed36d34..6f5d91a94 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -326,4 +326,48 @@ public class RichInputConnection { } } + public boolean sameAsTextBeforeCursor(final CharSequence text) { + final CharSequence beforeText = getTextBeforeCursor(text.length(), 0); + return TextUtils.equals(text, beforeText); + } + + /* (non-javadoc) + * Returns the word before the cursor if the cursor is at the end of a word, null otherwise + */ + public CharSequence getWordBeforeCursorIfAtEndOfWord(final SettingsValues settings) { + // Bail out if the cursor is not at the end of a word (cursor must be preceded by + // non-whitespace, non-separator, non-start-of-text) + // Example ("|" is the cursor here) : "|a" " |a" " | " all get rejected here. + final CharSequence textBeforeCursor = getTextBeforeCursor(1, 0); + if (TextUtils.isEmpty(textBeforeCursor) + || settings.isWordSeparator(textBeforeCursor.charAt(0))) return null; + + // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace, + // separator or end of line/text) + // Example: "test|" "te|st" get rejected here + final CharSequence textAfterCursor = getTextAfterCursor(1, 0); + if (!TextUtils.isEmpty(textAfterCursor) + && !settings.isWordSeparator(textAfterCursor.charAt(0))) return null; + + // Bail out if word before cursor is 0-length or a single non letter (like an apostrophe) + // Example: " -|" gets rejected here but "e-|" and "e|" are okay + CharSequence word = getWordAtCursor(settings.mWordSeparators); + // We don't suggest on leading single quotes, so we have to remove them from the word if + // it starts with single quotes. + while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) { + word = word.subSequence(1, word.length()); + } + if (TextUtils.isEmpty(word)) return null; + final char firstChar = word.charAt(0); // we just tested that word is not empty + if (word.length() == 1 && !Character.isLetter(firstChar)) return null; + + // We only suggest on words that start with a letter or a symbol that is excluded from + // word separators (see #handleCharacterWhileInBatchEdit). + if (!(Character.isLetter(firstChar) + || settings.isSymbolExcludedFromWordSeparators(firstChar))) { + return null; + } + + return word; + } } -- cgit v1.2.3-83-g751a From a32eb2721390d5964c83c787ad30fd3f61b936b0 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 8 Jun 2012 21:50:34 +0900 Subject: Move an editing method to the RichInputConnection Change-Id: Ie143ad008ca8deb1c5034cb80ec1cb28a6b0682d --- .../com/android/inputmethod/latin/LatinIME.java | 25 +--------------------- .../inputmethod/latin/RichInputConnection.java | 23 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 24 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index d25577c2c..da091dd05 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1395,7 +1395,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } if (SPACE_STATE_DOUBLE == spaceState) { mHandler.cancelDoubleSpacesTimer(); - if (revertDoubleSpaceWhileInBatchEdit()) { + if (mConnection.revertDoubleSpace()) { // No need to reset mSpaceState, it has already be done (that's why we // receive it as a parameter) return; @@ -2075,29 +2075,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.postUpdateSuggestions(); } - private boolean revertDoubleSpaceWhileInBatchEdit() { - // Here we test whether we indeed have a period and a space before us. This should not - // be needed, but it's there just in case something went wrong. - final CharSequence textBeforeCursor = mConnection.getTextBeforeCursor(2, 0); - if (!". ".equals(textBeforeCursor)) { - // Theoretically we should not be coming here if there isn't ". " before the - // cursor, but the application may be changing the text while we are typing, so - // anything goes. We should not crash. - Log.d(TAG, "Tried to revert double-space combo but we didn't find " - + "\". \" just before the cursor."); - return false; - } - mConnection.deleteSurroundingText(2, 0); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_deleteSurroundingText(2); - } - mConnection.commitText(" ", 1); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit(); - } - return true; - } - private boolean revertSwapPunctuation() { // Here we test whether we indeed have a space and something else before us. This should not // be needed, but it's there just in case something went wrong. diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 6f5d91a94..5ca4a84b9 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -370,4 +370,27 @@ public class RichInputConnection { return word; } + + public boolean revertDoubleSpace() { + // Here we test whether we indeed have a period and a space before us. This should not + // be needed, but it's there just in case something went wrong. + final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0); + if (!". ".equals(textBeforeCursor)) { + // Theoretically we should not be coming here if there isn't ". " before the + // cursor, but the application may be changing the text while we are typing, so + // anything goes. We should not crash. + Log.d(TAG, "Tried to revert double-space combo but we didn't find " + + "\". \" just before the cursor."); + return false; + } + deleteSurroundingText(2, 0); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_deleteSurroundingText(2); + } + commitText(" ", 1); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_revertDoubleSpaceWhileInBatchEdit(); + } + return true; + } } -- cgit v1.2.3-83-g751a From 2010aad741bc1a7266913bcb8b8348d6e401c95b Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 8 Jun 2012 21:56:44 +0900 Subject: Move two methods in a more appropriate place Change-Id: I512b04e23490413a44b1ca0517102fe2d9138df3 --- .../com/android/inputmethod/latin/LatinIME.java | 33 ++-------------------- .../inputmethod/latin/RichInputConnection.java | 26 +++++++++++++++++ .../android/inputmethod/latin/SettingsValues.java | 5 ++++ 3 files changed, 33 insertions(+), 31 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index da091dd05..94e0ac836 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1401,7 +1401,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return; } } else if (SPACE_STATE_SWAP_PUNCTUATION == spaceState) { - if (revertSwapPunctuation()) { + if (mConnection.revertSwapPunctuation()) { // Likewise return; } @@ -1975,10 +1975,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be // adding words in situations where the user or application really didn't // want corrections enabled or learned. - if (!(mCurrentSettings.mCorrectionMode == Suggest.CORRECTION_FULL - || mCurrentSettings.mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) { - return null; - } + if (!mCurrentSettings.isCorrectionOn()) return null; if (mUserHistoryDictionary != null) { final CharSequence prevWord @@ -2075,32 +2072,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.postUpdateSuggestions(); } - private boolean revertSwapPunctuation() { - // Here we test whether we indeed have a space and something else before us. This should not - // be needed, but it's there just in case something went wrong. - final CharSequence textBeforeCursor = mConnection.getTextBeforeCursor(2, 0); - // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to - // enter surrogate pairs this code will have been removed. - if (TextUtils.isEmpty(textBeforeCursor) - || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) { - // We may only come here if the application is changing the text while we are typing. - // This is quite a broken case, but not logically impossible, so we shouldn't crash, - // but some debugging log may be in order. - Log.d(TAG, "Tried to revert a swap of punctuation but we didn't " - + "find a space just before the cursor."); - return false; - } - mConnection.deleteSurroundingText(2, 0); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_deleteSurroundingText(2); - } - mConnection.commitText(" " + textBeforeCursor.subSequence(0, 1), 1); - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_revertSwapPunctuation(); - } - return true; - } - public boolean isWordSeparator(int code) { return mCurrentSettings.isWordSeparator(code); } diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 5ca4a84b9..227990acc 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -393,4 +393,30 @@ public class RichInputConnection { } return true; } + + public boolean revertSwapPunctuation() { + // Here we test whether we indeed have a space and something else before us. This should not + // be needed, but it's there just in case something went wrong. + final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0); + // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to + // enter surrogate pairs this code will have been removed. + if (TextUtils.isEmpty(textBeforeCursor) + || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) { + // We may only come here if the application is changing the text while we are typing. + // This is quite a broken case, but not logically impossible, so we shouldn't crash, + // but some debugging log may be in order. + Log.d(TAG, "Tried to revert a swap of punctuation but we didn't " + + "find a space just before the cursor."); + return false; + } + deleteSurroundingText(2, 0); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_deleteSurroundingText(2); + } + commitText(" " + textBeforeCursor.subSequence(0, 1), 1); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.latinIME_revertSwapPunctuation(); + } + return true; + } } diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 9985d2fb1..10094b5ad 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -226,6 +226,11 @@ public class SettingsValues { res.getBoolean(R.bool.config_default_vibration_enabled)); } + public boolean isCorrectionOn() { + return mCorrectionMode == Suggest.CORRECTION_FULL + || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM; + } + public boolean isSuggestionStripVisibleInOrientation(final int orientation) { return (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_VALUE) || (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE -- cgit v1.2.3-83-g751a From 9d71748ba48dbc8793f3e1ecddf5fd31b8e59613 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Fri, 8 Jun 2012 23:02:37 +0900 Subject: Fix an occurrence of nested batch edits. This is harmless, but against policy. Also, rework the checking code to be more readable, give more information, and be called for all relevant methods - and not for informative methods, which are not required to be in a batch edit. Change-Id: I03fa8b2e7d68a6a133f86be8a214671750c29256 --- .../com/android/inputmethod/latin/LatinIME.java | 11 +++----- .../inputmethod/latin/RichInputConnection.java | 31 +++++++++++++--------- java/src/com/android/inputmethod/latin/Utils.java | 10 +++++-- 3 files changed, 30 insertions(+), 22 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java') diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 5236591f6..00d4dfe93 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -1804,14 +1804,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void pickSuggestionManually(final int index, final CharSequence suggestion, - int x, int y) { - mConnection.beginBatchEdit(getCurrentInputConnection()); - pickSuggestionManuallyWhileInBatchEdit(index, suggestion, x, y); - mConnection.endBatchEdit(); - } - - public void pickSuggestionManuallyWhileInBatchEdit(final int index, - final CharSequence suggestion, final int x, final int y) { + final int x, final int y) { final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions(); // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput if (suggestion.length() == 1 && isShowingPunctuationList()) { @@ -1846,7 +1839,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mKeyboardSwitcher.updateShiftState(); resetComposingState(true /* alsoResetLastComposedWord */); final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index]; + mConnection.beginBatchEdit(getCurrentInputConnection()); mConnection.commitCompletion(completionInfo); + mConnection.endBatchEdit(); if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.latinIME_pickApplicationSpecifiedCompletion(index, completionInfo.getText(), x, y); diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 227990acc..0c19bed05 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -65,66 +65,70 @@ public class RichInputConnection { if (--mNestLevel == 0 && null != mIC) mIC.endBatchEdit(); } + private void checkBatchEdit() { + if (mNestLevel != 1) { + // TODO: exception instead + Log.e(TAG, "Batch edit level incorrect : " + mNestLevel); + Log.e(TAG, Utils.getStackTrace(4)); + } + } + public void finishComposingText() { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + checkBatchEdit(); if (null != mIC) mIC.finishComposingText(); } public void commitText(final CharSequence text, final int i) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + checkBatchEdit(); if (null != mIC) mIC.commitText(text, i); } public int getCursorCapsMode(final int inputType) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead if (null == mIC) return Constants.TextUtils.CAP_MODE_OFF; return mIC.getCursorCapsMode(inputType); } public CharSequence getTextBeforeCursor(final int i, final int j) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead if (null != mIC) return mIC.getTextBeforeCursor(i, j); return null; } public CharSequence getTextAfterCursor(final int i, final int j) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead if (null != mIC) return mIC.getTextAfterCursor(i, j); return null; } public void deleteSurroundingText(final int i, final int j) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + checkBatchEdit(); if (null != mIC) mIC.deleteSurroundingText(i, j); } public void performEditorAction(final int actionId) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead if (null != mIC) mIC.performEditorAction(actionId); } public void sendKeyEvent(final KeyEvent keyEvent) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + checkBatchEdit(); if (null != mIC) mIC.sendKeyEvent(keyEvent); } public void setComposingText(final CharSequence text, final int i) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + checkBatchEdit(); if (null != mIC) mIC.setComposingText(text, i); } public void setSelection(final int from, final int to) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + checkBatchEdit(); if (null != mIC) mIC.setSelection(from, to); } public void commitCorrection(final CorrectionInfo correctionInfo) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + checkBatchEdit(); if (null != mIC) mIC.commitCorrection(correctionInfo); } public void commitCompletion(final CompletionInfo completionInfo) { - if (mNestLevel <= 0) Log.e(TAG, "Batch edit not in progress!"); // TODO: exception instead + checkBatchEdit(); if (null != mIC) mIC.commitCompletion(completionInfo); } @@ -316,6 +320,7 @@ public class RichInputConnection { } public void removeTrailingSpace() { + checkBatchEdit(); final CharSequence lastOne = getTextBeforeCursor(1, 0); if (lastOne != null && lastOne.length() == 1 && lastOne.charAt(0) == Keyboard.CODE_SPACE) { @@ -372,6 +377,7 @@ public class RichInputConnection { } public boolean revertDoubleSpace() { + checkBatchEdit(); // Here we test whether we indeed have a period and a space before us. This should not // be needed, but it's there just in case something went wrong. final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0); @@ -395,6 +401,7 @@ public class RichInputConnection { } public boolean revertSwapPunctuation() { + checkBatchEdit(); // Here we test whether we indeed have a space and something else before us. This should not // be needed, but it's there just in case something went wrong. final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 903b5a357..f2d21ab9b 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -204,18 +204,24 @@ public class Utils { } // Get the current stack trace - public static String getStackTrace() { + public static String getStackTrace(final int limit) { StringBuilder sb = new StringBuilder(); try { throw new RuntimeException(); } catch (RuntimeException e) { StackTraceElement[] frames = e.getStackTrace(); // Start at 1 because the first frame is here and we don't care about it - for (int j = 1; j < frames.length; ++j) sb.append(frames[j].toString() + "\n"); + for (int j = 1; j < frames.length && j < limit + 1; ++j) { + sb.append(frames[j].toString() + "\n"); + } } return sb.toString(); } + public static String getStackTrace() { + return getStackTrace(Integer.MAX_VALUE); + } + public static class UsabilityStudyLogUtils { // TODO: remove code duplication with ResearchLog class private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName(); -- cgit v1.2.3-83-g751a From c3ebf1a43a6202c9992773722ff1eed7088b9a91 Mon Sep 17 00:00:00 2001 From: Jean Chalard Date: Mon, 25 Jun 2012 15:58:53 +0900 Subject: Refactoring and groundwork to fix a bug with older apps This has a good, although small, impact on performance : it removes a two-way IPC call in a most frequent case, while possibly adding one in a rather unfrequent and less critical case. Also, this fixes a bug with surrogate pairs. This specific branch of code now correctly handles surrogate pairs. Aside from this, it should have no impact on behavior. However, since it does delay access to the previous character in the text view by a two-way IPC call, it actually goes a long way toward fixing bug#6668226. It is not really a fix and the race condition still exists, but this change makes it much, much harder to hit. Bug: 6668226 Change-Id: Id11cc6a0b7488d6bd392227cafdcf3a8d4c62f6c --- .../android/inputmethod/latin/RichInputConnection.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'java/src/com/android/inputmethod/latin/RichInputConnection.java') diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 0c19bed05..40d327ebb 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -340,13 +340,6 @@ public class RichInputConnection { * Returns the word before the cursor if the cursor is at the end of a word, null otherwise */ public CharSequence getWordBeforeCursorIfAtEndOfWord(final SettingsValues settings) { - // Bail out if the cursor is not at the end of a word (cursor must be preceded by - // non-whitespace, non-separator, non-start-of-text) - // Example ("|" is the cursor here) : "|a" " |a" " | " all get rejected here. - final CharSequence textBeforeCursor = getTextBeforeCursor(1, 0); - if (TextUtils.isEmpty(textBeforeCursor) - || settings.isWordSeparator(textBeforeCursor.charAt(0))) return null; - // Bail out if the cursor is in the middle of a word (cursor must be followed by whitespace, // separator or end of line/text) // Example: "test|" "te|st" get rejected here @@ -363,6 +356,15 @@ public class RichInputConnection { word = word.subSequence(1, word.length()); } if (TextUtils.isEmpty(word)) return null; + // Find the last code point of the string + final int lastCodePoint = Character.codePointBefore(word, word.length()); + // If for some reason the text field contains non-unicode binary data, or if the + // charsequence is exactly one char long and the contents is a low surrogate, return null. + if (!Character.isDefined(lastCodePoint)) return null; + // Bail out if the cursor is not at the end of a word (cursor must be preceded by + // non-whitespace, non-separator, non-start-of-text) + // Example ("|" is the cursor here) : "|a" " |a" " | " all get rejected here. + if (settings.isWordSeparator(lastCodePoint)) return null; final char firstChar = word.charAt(0); // we just tested that word is not empty if (word.length() == 1 && !Character.isLetter(firstChar)) return null; -- cgit v1.2.3-83-g751a