aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--java/res/values/config.xml3
-rw-r--r--java/src/com/android/inputmethod/latin/EditingUtils.java52
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java13
-rw-r--r--java/src/com/android/inputmethod/latin/ResearchLogger.java76
-rw-r--r--tests/src/com/android/inputmethod/latin/EditingUtilsTests.java161
-rw-r--r--tests/src/com/android/inputmethod/latin/UtilsTests.java65
6 files changed, 291 insertions, 79 deletions
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index d5268ea5f..e20061d7d 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -23,7 +23,8 @@
<bool name="config_enable_show_voice_key_option">true</bool>
<bool name="config_enable_show_popup_on_keypress_option">true</bool>
<bool name="config_enable_next_word_suggestions_option">true</bool>
- <bool name="config_enable_usability_study_mode_option">false</bool>
+ <!-- TODO: Disable the following configuration for production. -->
+ <bool name="config_enable_usability_study_mode_option">true</bool>
<!-- Whether or not Popup on key press is enabled by default -->
<bool name="config_default_popup_preview">true</bool>
<!-- Default value for next word suggestion: while showing suggestions for a word should we weigh
diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java
index 0f34d50bb..479b3bf5a 100644
--- a/java/src/com/android/inputmethod/latin/EditingUtils.java
+++ b/java/src/com/android/inputmethod/latin/EditingUtils.java
@@ -55,7 +55,7 @@ public class EditingUtils {
*/
public static String getWordAtCursor(InputConnection connection, String separators) {
// getWordRangeAtCursor returns null if the connection is null
- Range r = getWordRangeAtCursor(connection, separators);
+ Range r = getWordRangeAtCursor(connection, separators, 0);
return (r == null) ? null : r.mWord;
}
@@ -85,7 +85,17 @@ public class EditingUtils {
}
}
- private static Range getWordRangeAtCursor(InputConnection connection, String sep) {
+ /**
+ * 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;
}
@@ -95,14 +105,40 @@ public class EditingUtils {
return null;
}
- // Find first word separator before the cursor
+ // Going backward, alternate skipping non-separators and separators until enough words
+ // have been read.
int start = before.length();
- while (start > 0 && !isWhitespace(before.charAt(start - 1), sep)) start--;
+ 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() && !isWhitespace(after.charAt(end), sep)) {
- // Nothing to do here.
+ 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);
@@ -115,8 +151,8 @@ public class EditingUtils {
return null;
}
- private static boolean isWhitespace(int code, String whitespace) {
- return whitespace.contains(String.valueOf((char) code));
+ private static boolean isSeparator(int code, String sep) {
+ return sep.indexOf(code) != -1;
}
private static final Pattern spaceRegex = Pattern.compile("\\s+");
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index bdefaee92..f27968cd4 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -711,6 +711,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
@Override
public void onWindowHidden() {
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onWindowHidden(mLastSelectionStart, mLastSelectionEnd,
+ getCurrentInputConnection());
+ }
super.onWindowHidden();
KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView != null) inputView.closing();
@@ -741,7 +745,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
int composingSpanStart, int composingSpanEnd) {
super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd,
composingSpanStart, composingSpanEnd);
-
if (DEBUG) {
Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
+ ", ose=" + oldSelEnd
@@ -753,9 +756,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ ", ce=" + composingSpanEnd);
}
if (ProductionFlag.IS_EXPERIMENTAL) {
+ final boolean expectingUpdateSelectionFromLogger =
+ ResearchLogger.getAndClearLatinIMEExpectingUpdateSelection();
ResearchLogger.latinIME_onUpdateSelection(mLastSelectionStart, mLastSelectionEnd,
oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart,
- composingSpanEnd);
+ composingSpanEnd, mExpectingUpdateSelection,
+ expectingUpdateSelectionFromLogger, getCurrentInputConnection());
+ if (expectingUpdateSelectionFromLogger) {
+ return;
+ }
}
// TODO: refactor the following code to be less contrived.
diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java
index 566af7061..a46ed03af 100644
--- a/java/src/com/android/inputmethod/latin/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin;
import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.os.Handler;
@@ -29,11 +30,13 @@ import android.util.Log;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.KeyDetector;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.internal.KeyboardState;
+import com.android.inputmethod.latin.EditingUtils.Range;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.BufferedWriter;
@@ -47,6 +50,7 @@ import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.Map;
+import java.util.UUID;
/**
* Logs the use of the LatinIME keyboard.
@@ -59,13 +63,20 @@ import java.util.Map;
public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String TAG = ResearchLogger.class.getSimpleName();
private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
+ private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid";
private static final boolean DEBUG = false;
+ private static final String WHITESPACE_SEPARATORS = " \t\n\r";
private static final ResearchLogger sInstance = new ResearchLogger(new LogFileManager());
+ private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
public static boolean sIsLogging = false;
/* package */ final Handler mLoggingHandler;
private InputMethodService mIms;
+ // set when LatinIME should ignore a onUpdateSelection() callback that
+ // arises from operations in this class
+ private static boolean mLatinIMEExpectingUpdateSelection = false;
+
/**
* Isolates management of files. This variable should never be null, but can be changed
* to support testing.
@@ -336,6 +347,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final boolean LATINIME_DELETESURROUNDINGTEXT_ENABLED = DEFAULT_ENABLED;
private static final boolean LATINIME_DOUBLESPACEAUTOPERIOD_ENABLED = DEFAULT_ENABLED;
private static final boolean LATINIME_ONDISPLAYCOMPLETIONS_ENABLED = DEFAULT_ENABLED;
+ private static final boolean LATINIME_ONWINDOWHIDDEN_ENABLED = DEFAULT_ENABLED;
private static final boolean LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED = DEFAULT_ENABLED;
private static final boolean LATINIME_ONUPDATESELECTION_ENABLED = DEFAULT_ENABLED;
private static final boolean LATINIME_PERFORMEDITORACTION_ENABLED = DEFAULT_ENABLED;
@@ -528,11 +540,51 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
+ /* package */ static boolean getAndClearLatinIMEExpectingUpdateSelection() {
+ boolean returnValue = mLatinIMEExpectingUpdateSelection;
+ mLatinIMEExpectingUpdateSelection = false;
+ return returnValue;
+ }
+
+ public static void latinIME_onWindowHidden(final int savedSelectionStart,
+ final int savedSelectionEnd, final InputConnection ic) {
+ if (UnsLogGroup.LATINIME_ONWINDOWHIDDEN_ENABLED) {
+ if (ic != null) {
+ ic.beginBatchEdit();
+ ic.performContextMenuAction(android.R.id.selectAll);
+ CharSequence charSequence = ic.getSelectedText(0);
+ ic.setSelection(savedSelectionStart, savedSelectionEnd);
+ ic.endBatchEdit();
+ mLatinIMEExpectingUpdateSelection = true;
+ if (TextUtils.isEmpty(charSequence)) {
+ logUnstructured("LatinIME_onWindowHidden", "<no text>");
+ } else {
+ if (charSequence.length() > MAX_INPUTVIEW_LENGTH_TO_CAPTURE) {
+ int length = MAX_INPUTVIEW_LENGTH_TO_CAPTURE;
+ // do not cut in the middle of a supplementary character
+ final char c = charSequence.charAt(length-1);
+ if (Character.isHighSurrogate(c)) {
+ length--;
+ }
+ final CharSequence truncatedCharSequence = charSequence.subSequence(0,
+ length);
+ logUnstructured("LatinIME_onWindowHidden", truncatedCharSequence.toString()
+ + "<truncated>");
+ } else {
+ logUnstructured("LatinIME_onWindowHidden", charSequence.toString());
+ }
+ }
+ }
+ }
+ }
+
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
if (UnsLogGroup.LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED) {
final StringBuilder builder = new StringBuilder();
builder.append("onStartInputView: editorInfo:");
+ builder.append("\tpackageName=");
+ builder.append(editorInfo.packageName);
builder.append("\tinputType=");
builder.append(Integer.toHexString(editorInfo.inputType));
builder.append("\timeOptions=");
@@ -544,14 +596,28 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
Object value = entry.getValue();
builder.append("=" + ((value == null) ? "<null>" : value.toString()));
}
+ builder.append("\tuuid="); builder.append(getUUID(prefs));
logUnstructured("LatinIME_onStartInputViewInternal", builder.toString());
}
}
+ private static String getUUID(final SharedPreferences prefs) {
+ String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null);
+ if (null == uuidString) {
+ UUID uuid = UUID.randomUUID();
+ uuidString = uuid.toString();
+ Editor editor = prefs.edit();
+ editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString);
+ editor.apply();
+ }
+ return uuidString;
+ }
+
public static void latinIME_onUpdateSelection(final int lastSelectionStart,
final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd,
final int newSelStart, final int newSelEnd, final int composingSpanStart,
- final int composingSpanEnd) {
+ final int composingSpanEnd, final boolean expectingUpdateSelection,
+ final boolean expectingUpdateSelectionFromLogger, final InputConnection connection) {
if (UnsLogGroup.LATINIME_ONUPDATESELECTION_ENABLED) {
final String s = "onUpdateSelection: oss=" + oldSelStart
+ ", ose=" + oldSelEnd
@@ -560,7 +626,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
+ ", nss=" + newSelStart
+ ", nse=" + newSelEnd
+ ", cs=" + composingSpanStart
- + ", ce=" + composingSpanEnd;
+ + ", ce=" + composingSpanEnd
+ + ", eus=" + expectingUpdateSelection
+ + ", eusfl=" + expectingUpdateSelectionFromLogger
+ + ", context=\"" + EditingUtils.getWordRangeAtCursor(connection,
+ WHITESPACE_SEPARATORS, 1).mWord + "\"";
logUnstructured("LatinIME_onUpdateSelection", s);
}
}
@@ -754,4 +824,4 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
logUnstructured("SuggestionsView_setSuggestions", mSuggestedWords.toString());
}
}
-} \ No newline at end of file
+}
diff --git a/tests/src/com/android/inputmethod/latin/EditingUtilsTests.java b/tests/src/com/android/inputmethod/latin/EditingUtilsTests.java
new file mode 100644
index 000000000..c73f8891f
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/EditingUtilsTests.java
@@ -0,0 +1,161 @@
+/*
+ * 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/UtilsTests.java b/tests/src/com/android/inputmethod/latin/UtilsTests.java
deleted file mode 100644
index 2ef4e2ff5..000000000
--- a/tests/src/com/android/inputmethod/latin/UtilsTests.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2010,2011 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;
-
-public class UtilsTests 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();
- }
-
- /************************** 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));
- }
-}