aboutsummaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/res/raw/main_pt_br.dictbin1091987 -> 1091993 bytes
-rw-r--r--java/res/xml/key_styles_common.xml2
-rw-r--r--java/res/xml/key_styles_enter.xml22
-rw-r--r--java/res/xml/spellchecker.xml12
-rw-r--r--java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java2
-rw-r--r--java/src/com/android/inputmethod/event/Combiner.java29
-rw-r--r--java/src/com/android/inputmethod/event/DeadKeyCombiner.java61
-rw-r--r--java/src/com/android/inputmethod/event/Event.java19
-rw-r--r--java/src/com/android/inputmethod/event/EventInterpreter.java35
-rw-r--r--java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java7
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java18
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java16
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java26
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java25
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java18
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java4
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java64
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java7
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java56
-rw-r--r--java/src/com/android/inputmethod/latin/Constants.java4
-rw-r--r--java/src/com/android/inputmethod/latin/DebugSettings.java3
-rw-r--r--java/src/com/android/inputmethod/latin/DicTraverseSession.java6
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryFactory.java14
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java5
-rw-r--r--java/src/com/android/inputmethod/latin/InputTypeUtils.java18
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java147
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java9
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java2
-rw-r--r--java/src/com/android/inputmethod/latin/SettingsValues.java28
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java8
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java5
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java20
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java11
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java266
-rw-r--r--java/src/com/android/inputmethod/research/FixedLogBuffer.java11
-rw-r--r--java/src/com/android/inputmethod/research/LogBuffer.java14
-rw-r--r--java/src/com/android/inputmethod/research/LogUnit.java87
-rw-r--r--java/src/com/android/inputmethod/research/MainLogBuffer.java101
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLog.java15
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java340
-rw-r--r--java/src/com/android/inputmethod/research/Statistics.java56
44 files changed, 1159 insertions, 447 deletions
diff --git a/java/res/raw/main_pt_br.dict b/java/res/raw/main_pt_br.dict
index 28db0ed74..8c1449919 100644
--- a/java/res/raw/main_pt_br.dict
+++ b/java/res/raw/main_pt_br.dict
Binary files differ
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 91ebac1da..0834adf20 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -71,7 +71,7 @@
>
<key-style
latin:styleName="enterKeyStyle"
- latin:parentStyle="defaultEnterKeyStyle" />
+ latin:parentStyle="shiftEnterKeyStyle" />
</case>
<!-- Smiley in textShortMessage field.
Overrides common enter key style. -->
diff --git a/java/res/xml/key_styles_enter.xml b/java/res/xml/key_styles_enter.xml
index 3788ca8ba..61a515bad 100644
--- a/java/res/xml/key_styles_enter.xml
+++ b/java/res/xml/key_styles_enter.xml
@@ -99,7 +99,11 @@
latin:backgroundType="functional"
latin:parentStyle="navigateMoreKeysStyle" />
<key-style
- latin:styleName="defaultActionKeyStyle"
+ latin:styleName="shiftEnterKeyStyle"
+ latin:code="!code/key_shift_enter"
+ latin:parentStyle="defaultEnterKeyStyle" />
+ <key-style
+ latin:styleName="defaultActionEnterKeyStyle"
latin:code="!code/key_action_enter"
latin:keyIcon="!icon/undefined"
latin:backgroundType="action"
@@ -112,7 +116,7 @@
>
<key-style
latin:styleName="enterKeyStyle"
- latin:parentStyle="defaultEnterKeyStyle" />
+ latin:parentStyle="shiftEnterKeyStyle" />
</case>
<case
latin:imeAction="actionGo"
@@ -120,7 +124,7 @@
<key-style
latin:styleName="enterKeyStyle"
latin:keyLabel="!text/label_go_key"
- latin:parentStyle="defaultActionKeyStyle" />
+ latin:parentStyle="defaultActionEnterKeyStyle" />
</case>
<case
latin:imeAction="actionNext"
@@ -128,7 +132,7 @@
<key-style
latin:styleName="enterKeyStyle"
latin:keyLabel="!text/label_next_key"
- latin:parentStyle="defaultActionKeyStyle" />
+ latin:parentStyle="defaultActionEnterKeyStyle" />
</case>
<case
latin:imeAction="actionPrevious"
@@ -136,7 +140,7 @@
<key-style
latin:styleName="enterKeyStyle"
latin:keyLabel="!text/label_previous_key"
- latin:parentStyle="defaultActionKeyStyle" />
+ latin:parentStyle="defaultActionEnterKeyStyle" />
</case>
<case
latin:imeAction="actionDone"
@@ -144,7 +148,7 @@
<key-style
latin:styleName="enterKeyStyle"
latin:keyLabel="!text/label_done_key"
- latin:parentStyle="defaultActionKeyStyle" />
+ latin:parentStyle="defaultActionEnterKeyStyle" />
</case>
<case
latin:imeAction="actionSend"
@@ -152,7 +156,7 @@
<key-style
latin:styleName="enterKeyStyle"
latin:keyLabel="!text/label_send_key"
- latin:parentStyle="defaultActionKeyStyle" />
+ latin:parentStyle="defaultActionEnterKeyStyle" />
</case>
<case
latin:imeAction="actionSearch"
@@ -160,7 +164,7 @@
<key-style
latin:styleName="enterKeyStyle"
latin:keyIcon="!icon/search_key"
- latin:parentStyle="defaultActionKeyStyle" />
+ latin:parentStyle="defaultActionEnterKeyStyle" />
</case>
<case
latin:imeAction="actionCustomLabel"
@@ -168,7 +172,7 @@
<key-style
latin:styleName="enterKeyStyle"
latin:keyLabelFlags="fromCustomActionLabel"
- latin:parentStyle="defaultActionKeyStyle" />
+ latin:parentStyle="defaultActionEnterKeyStyle" />
</case>
<!-- imeAction is either actionNone or actionUnspecified. -->
<default>
diff --git a/java/res/xml/spellchecker.xml b/java/res/xml/spellchecker.xml
index 66cd66280..813319c7b 100644
--- a/java/res/xml/spellchecker.xml
+++ b/java/res/xml/spellchecker.xml
@@ -57,18 +57,6 @@
/>
<subtype
android:label="@string/subtype_generic"
- android:subtypeLocale="cs"
- />
- <subtype
- android:label="@string/subtype_generic"
- android:subtypeLocale="nl"
- />
- <subtype
- android:label="@string/subtype_generic"
- android:subtypeLocale="hr"
- />
- <subtype
- android:label="@string/subtype_generic"
android:subtypeLocale="pt_BR"
/>
</spell-checker>
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 6a01b0190..ea86d98cb 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -209,7 +209,7 @@ public final class KeyCodeDescriptionMapper {
private String getDescriptionForActionKey(final Context context, final Keyboard keyboard,
final Key key) {
final KeyboardId keyboardId = keyboard.mId;
- final int actionId = keyboardId.imeActionId();
+ final int actionId = keyboardId.imeAction();
final int resId;
// Always use the label, if available.
diff --git a/java/src/com/android/inputmethod/event/Combiner.java b/java/src/com/android/inputmethod/event/Combiner.java
new file mode 100644
index 000000000..ab6b70c04
--- /dev/null
+++ b/java/src/com/android/inputmethod/event/Combiner.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 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.event;
+
+/**
+ * A generic interface for combiners.
+ */
+public interface Combiner {
+ /**
+ * Combine an event with the existing state and return the new event.
+ * @param event the event to combine with the existing state.
+ * @return the resulting event.
+ */
+ Event combine(Event event);
+}
diff --git a/java/src/com/android/inputmethod/event/DeadKeyCombiner.java b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
new file mode 100644
index 000000000..52987d571
--- /dev/null
+++ b/java/src/com/android/inputmethod/event/DeadKeyCombiner.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2013 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.event;
+
+import android.text.TextUtils;
+import android.view.KeyCharacterMap;
+
+import com.android.inputmethod.latin.Constants;
+
+/**
+ * A combiner that handles dead keys.
+ */
+public class DeadKeyCombiner implements Combiner {
+ final StringBuilder mDeadSequence = new StringBuilder();
+
+ @Override
+ public Event combine(final Event event) {
+ if (null == event) return null; // Just in case some combiner is broken
+ if (TextUtils.isEmpty(mDeadSequence)) {
+ if (event.isDead()) {
+ mDeadSequence.appendCodePoint(event.mCodePoint);
+ }
+ return event;
+ } else {
+ // TODO: Allow combining for several dead chars rather than only the first one.
+ // The framework doesn't know how to do this now.
+ final int deadCodePoint = mDeadSequence.codePointAt(0);
+ mDeadSequence.setLength(0);
+ final int resultingCodePoint =
+ KeyCharacterMap.getDeadChar(deadCodePoint, event.mCodePoint);
+ if (0 == resultingCodePoint) {
+ // We can't combine both characters. We need to commit the dead key as a committable
+ // character, and the next char too unless it's a space (because as a special case,
+ // dead key + space should result in only the dead key being committed - that's
+ // how dead keys work).
+ // If the event is a space, we should commit the dead char alone, but if it's
+ // not, we need to commit both.
+ return Event.createCommittableEvent(deadCodePoint,
+ Constants.CODE_SPACE == event.mCodePoint ? null : event /* next */);
+ } else {
+ // We could combine the characters.
+ return Event.createCommittableEvent(resultingCodePoint, null /* next */);
+ }
+ }
+ }
+
+}
diff --git a/java/src/com/android/inputmethod/event/Event.java b/java/src/com/android/inputmethod/event/Event.java
index 3fe5d5b68..1f3320eb7 100644
--- a/java/src/com/android/inputmethod/event/Event.java
+++ b/java/src/com/android/inputmethod/event/Event.java
@@ -61,26 +61,33 @@ public class Event {
// ctrl, there is no code point associated so this should be NOT_A_CODE_POINT to avoid
// unintentional use of its value when it's not relevant.
final public int mCodePoint;
+ // The next event, if any. Null if there is no next event yet.
+ final public Event mNextEvent;
// This method is private - to create a new event, use one of the create* utility methods.
- private Event(final int type, final int codePoint) {
+ private Event(final int type, final int codePoint, final Event next) {
mType = type;
mCodePoint = codePoint;
+ mNextEvent = next;
}
- public static Event createDeadEvent(final int codePoint) {
- return new Event(EVENT_DEAD, codePoint);
+ public static Event createDeadEvent(final int codePoint, final Event next) {
+ return new Event(EVENT_DEAD, codePoint, next);
}
- public static Event createCommittableEvent(final int codePoint) {
- return new Event(EVENT_COMMITTABLE, codePoint);
+ public static Event createCommittableEvent(final int codePoint, final Event next) {
+ return new Event(EVENT_COMMITTABLE, codePoint, next);
}
public static Event createNotHandledEvent() {
- return new Event(EVENT_NOT_HANDLED, NOT_A_CODE_POINT);
+ return new Event(EVENT_NOT_HANDLED, NOT_A_CODE_POINT, null);
}
public boolean isCommittable() {
return EVENT_COMMITTABLE == mType;
}
+
+ public boolean isDead() {
+ return EVENT_DEAD == mType;
+ }
}
diff --git a/java/src/com/android/inputmethod/event/EventInterpreter.java b/java/src/com/android/inputmethod/event/EventInterpreter.java
index f9185788e..6efe899bb 100644
--- a/java/src/com/android/inputmethod/event/EventInterpreter.java
+++ b/java/src/com/android/inputmethod/event/EventInterpreter.java
@@ -19,9 +19,12 @@ package com.android.inputmethod.event;
import android.util.SparseArray;
import android.view.KeyEvent;
+import com.android.inputmethod.latin.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.LatinIME;
+import java.util.ArrayList;
+
/**
* This class implements the logic between receiving events and generating code points.
*
@@ -40,6 +43,7 @@ public class EventInterpreter {
final SparseArray<HardwareEventDecoder> mHardwareEventDecoders;
final SoftwareEventDecoder mSoftwareEventDecoder;
final LatinIME mLatinIme;
+ final ArrayList<Combiner> mCombiners;
/**
* Create a default interpreter.
@@ -74,6 +78,8 @@ public class EventInterpreter {
// capacity of 1.
mHardwareEventDecoders = new SparseArray<HardwareEventDecoder>(1);
mSoftwareEventDecoder = new SoftwareKeyboardEventDecoder();
+ mCombiners = CollectionUtils.newArrayList();
+ mCombiners.add(new DeadKeyCombiner());
mLatinIme = latinIme;
}
@@ -106,19 +112,22 @@ public class EventInterpreter {
}
private boolean onEvent(final Event event) {
- if (event.isCommittable()) {
- mLatinIme.onCodeInput(event.mCodePoint,
- Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE);
- return true;
+ Event currentlyProcessingEvent = event;
+ boolean processed = false;
+ for (int i = 0; i < mCombiners.size(); ++i) {
+ currentlyProcessingEvent = mCombiners.get(i).combine(event);
+ }
+ while (null != currentlyProcessingEvent) {
+ if (currentlyProcessingEvent.isCommittable()) {
+ mLatinIme.onCodeInput(currentlyProcessingEvent.mCodePoint,
+ Constants.EXTERNAL_KEYBOARD_COORDINATE,
+ Constants.EXTERNAL_KEYBOARD_COORDINATE);
+ processed = true;
+ } else if (event.isDead()) {
+ processed = true;
+ }
+ currentlyProcessingEvent = currentlyProcessingEvent.mNextEvent;
}
- // TODO: Classify the event - input or non-input (see design doc)
- // TODO: IF action event
- // Send decoded action back to LatinIME
- // ELSE
- // Send input event to the combiner
- // Get back new input material + visual feedback + combiner state
- // Route the event to Latin IME
- // ENDIF
- return false;
+ return processed;
}
}
diff --git a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
index 554319e51..2fb7fe8b4 100644
--- a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
+++ b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
@@ -47,17 +47,18 @@ public class HardwareKeyboardEventDecoder implements HardwareEventDecoder {
// the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock.
final int keyCode = keyEvent.getKeyCode();
if (KeyEvent.KEYCODE_DEL == keyCode) {
- return Event.createCommittableEvent(Constants.CODE_DELETE);
+ return Event.createCommittableEvent(Constants.CODE_DELETE, null /* next */);
}
if (keyEvent.isPrintingKey() || KeyEvent.KEYCODE_SPACE == keyCode
|| KeyEvent.KEYCODE_ENTER == keyCode) {
if (0 != (codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT)) {
// A dead key.
- return Event.createDeadEvent(codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK);
+ return Event.createDeadEvent(
+ codePointAndFlags & KeyCharacterMap.COMBINING_ACCENT_MASK, null /* next */);
} else {
// A committable character. This should be committed right away, taking into
// account the current state.
- return Event.createCommittableEvent(codePointAndFlags);
+ return Event.createCommittableEvent(codePointAndFlags, null /* next */);
}
} else {
return Event.createNotHandledEvent();
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index b41361515..02116ca08 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -59,8 +59,6 @@ public final class KeyboardId {
public static final int FORM_FACTOR_TABLET7 = 1;
public static final int FORM_FACTOR_TABLET10 = 2;
- private static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1;
-
public final InputMethodSubtype mSubtype;
public final Locale mLocale;
public final int mDeviceFormFactor;
@@ -174,19 +172,7 @@ public final class KeyboardId {
}
public int imeAction() {
- final int actionId = mEditorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
- if ((mEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
- return EditorInfo.IME_ACTION_NONE;
- } else if (mEditorInfo.actionLabel != null) {
- return IME_ACTION_CUSTOM_LABEL;
- } else {
- return actionId;
- }
- }
-
- public int imeActionId() {
- final int actionId = imeAction();
- return actionId == IME_ACTION_CUSTOM_LABEL ? mEditorInfo.actionId : actionId;
+ return InputTypeUtils.getImeOptionsActionIdFromEditorInfo(mEditorInfo);
}
@Override
@@ -269,7 +255,7 @@ public final class KeyboardId {
}
public static String actionName(final int actionId) {
- return (actionId == IME_ACTION_CUSTOM_LABEL) ? "actionCustomLabel"
+ return (actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL) ? "actionCustomLabel"
: EditorInfoCompatUtils.imeActionName(actionId);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 63b9ed666..b7584d4cd 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -153,6 +153,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy,
private boolean mShowKeyPreviewPopup = true;
private int mKeyPreviewLingerTimeout;
+ // Gesture floating preview text
+ // TODO: Make this parameter customizable by user via settings.
+ private int mGestureFloatingPreviewTextLingerTimeout;
+
// Background state set
private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
{ // STATE_MIDDLE
@@ -204,6 +208,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy,
public static class DrawingHandler extends StaticInnerHandlerWrapper<KeyboardView> {
private static final int MSG_DISMISS_KEY_PREVIEW = 0;
+ private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
public DrawingHandler(final KeyboardView outerInstance) {
super(outerInstance);
@@ -221,6 +226,9 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy,
previewText.setVisibility(INVISIBLE);
}
break;
+ case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
+ keyboardView.mPreviewPlacerView.setGestureFloatingPreviewText(SuggestedWords.EMPTY);
+ break;
}
}
@@ -236,6 +244,10 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy,
removeMessages(MSG_DISMISS_KEY_PREVIEW);
}
+ public void dismissGestureFloatingPreviewText(final long delay) {
+ sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), delay);
+ }
+
public void cancelAllMessages() {
cancelAllDismissKeyPreviews();
}
@@ -279,6 +291,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy,
R.styleable.KeyboardView_moreKeysLayout, 0);
mBackgroundDimAlpha = keyboardViewAttr.getInt(
R.styleable.KeyboardView_backgroundDimAlpha, 0);
+ mGestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
+ R.styleable.KeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
keyboardViewAttr.recycle();
final TypedArray keyAttr = context.obtainStyledAttributes(attrs,
@@ -877,7 +891,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy,
public void dismissGestureFloatingPreviewText() {
locatePreviewPlacerView();
- mPreviewPlacerView.dismissGestureFloatingPreviewText();
+ mDrawingHandler.dismissGestureFloatingPreviewText(mGestureFloatingPreviewTextLingerTimeout);
}
@Override
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 62e674ad5..036372c37 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -920,8 +920,12 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
final boolean isMajorEvent, final Key key) {
final int gestureTime = (int)(eventTime - sGestureFirstDownTime);
if (mIsDetectingGesture) {
+ final int beforeLength = mGestureStrokeWithPreviewPoints.getLength();
final boolean onValidArea = mGestureStrokeWithPreviewPoints.addPointOnKeyboard(
x, y, gestureTime, isMajorEvent);
+ if (mGestureStrokeWithPreviewPoints.getLength() > beforeLength) {
+ mTimerProxy.startUpdateBatchInputTimer(this);
+ }
// If the move event goes out from valid batch input area, cancel batch input.
if (!onValidArea) {
cancelBatchInput();
@@ -943,7 +947,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element {
if (DEBUG_MOVE_EVENT) {
printTouchEvent("onMoveEvent:", x, y, eventTime);
}
- mTimerProxy.cancelUpdateBatchInputTimer(this);
if (mIsTrackingCanceled) {
return;
}
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index b5ba98d85..0fdbb0d05 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -80,17 +80,17 @@ public final class ProximityInfo {
mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection);
}
- public static ProximityInfo createDummyProximityInfo() {
+ private static ProximityInfo createDummyProximityInfo() {
return new ProximityInfo("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null);
}
- public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximity,
+ public static ProximityInfo createSpellCheckerProximityInfo(final int[] proximityCharsArray,
final int rowSize, final int gridWidth, final int gridHeight) {
final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
spellCheckerProximityInfo.mNativeProximityInfo =
spellCheckerProximityInfo.setProximityInfoNative("",
rowSize, gridWidth, gridHeight, gridWidth, gridHeight,
- 1, proximity, 0, null, null, null, null, null, null, null, null);
+ 1, proximityCharsArray, 0, null, null, null, null, null, null, null, null);
return spellCheckerProximityInfo;
}
@@ -100,15 +100,13 @@ public final class ProximityInfo {
}
// TODO: Stop passing proximityCharsArray
- private native long setProximityInfoNative(
- String locale, int maxProximityCharsSize, int displayWidth,
- int displayHeight, int gridWidth, int gridHeight,
- int mostCommonKeyWidth, int[] proximityCharsArray,
- int keyCount, int[] keyXCoordinates, int[] keyYCoordinates,
- int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
+ private static native long setProximityInfoNative(String locale, int maxProximityCharsSize,
+ int displayWidth, int displayHeight, int gridWidth, int gridHeight,
+ int mostCommonKeyWidth, int[] proximityCharsArray, int keyCount, int[] keyXCoordinates,
+ int[] keyYCoordinates, int[] keyWidths, int[] keyHeights, int[] keyCharCodes,
float[] sweetSpotCenterX, float[] sweetSpotCenterY, float[] sweetSpotRadii);
- private native void releaseProximityInfoNative(long nativeProximityInfo);
+ private static native void releaseProximityInfoNative(long nativeProximityInfo);
private static boolean needsProximityInfo(final Key key) {
// Don't include special keys into ProximityInfo.
@@ -232,10 +230,10 @@ public final class ProximityInfo {
}
// TODO: Stop passing proximityCharsArray
- return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE,
- mKeyboardMinWidth, mKeyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth,
- proximityCharsArray, keyCount, keyXCoordinates, keyYCoordinates, keyWidths,
- keyHeights, keyCharCodes, sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
+ return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE, mKeyboardMinWidth,
+ mKeyboardHeight, mGridWidth, mGridHeight, mMostCommonKeyWidth, proximityCharsArray,
+ keyCount, keyXCoordinates, keyYCoordinates, keyWidths, keyHeights, keyCharCodes,
+ sweetSpotCenterXs, sweetSpotCenterYs, sweetSpotRadii);
}
public long getNativeProximityInfo() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
index 30ca859d3..aed23a4db 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
@@ -100,11 +100,7 @@ public class GestureFloatingPreviewText extends AbstractDrawingPreview {
}
public void setSuggetedWords(final SuggestedWords suggestedWords) {
- if (suggestedWords == null) {
- mSuggestedWords = SuggestedWords.EMPTY;
- } else {
- mSuggestedWords = suggestedWords;
- }
+ mSuggestedWords = suggestedWords;
updatePreviewPosition();
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index f8949b2ad..4a8407cb5 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -133,10 +133,12 @@ final class GesturePreviewTrail {
private static float getWidth(final int elapsedTime, final Params params) {
final int deltaTime = params.mTrailLingerDuration - elapsedTime;
final float deltaWidth = params.mTrailStartWidth - params.mTrailEndWidth;
- return (deltaTime * deltaWidth) / params.mTrailLingerDuration + params.mTrailEndWidth;
+ return Math.max(
+ (deltaTime * deltaWidth) / params.mTrailLingerDuration, params.mTrailEndWidth);
}
private final RoundedLine mRoundedLine = new RoundedLine();
+ private final Rect mRoundedLineBounds = new Rect();
/**
* Draw gesture preview trail
@@ -148,6 +150,8 @@ final class GesturePreviewTrail {
*/
public boolean drawGestureTrail(final Canvas canvas, final Paint paint,
final Rect outBoundsRect, final Params params) {
+ // Initialize bounds rectangle.
+ outBoundsRect.setEmpty();
final int trailSize = mEventTimes.getLength();
if (trailSize == 0) {
return false;
@@ -170,39 +174,32 @@ final class GesturePreviewTrail {
if (startIndex < trailSize) {
paint.setColor(params.mTrailColor);
paint.setStyle(Paint.Style.FILL);
- final RoundedLine line = mRoundedLine;
+ final RoundedLine roundedLine = mRoundedLine;
int p1x = getXCoordValue(xCoords[startIndex]);
int p1y = yCoords[startIndex];
final int lastTime = sinceDown - eventTimes[startIndex];
- float maxWidth = getWidth(lastTime, params);
- float r1 = maxWidth / 2.0f;
- // Initialize bounds rectangle.
- outBoundsRect.set(p1x, p1y, p1x, p1y);
+ float r1 = getWidth(lastTime, params) / 2.0f;
for (int i = startIndex + 1; i < trailSize; i++) {
final int elapsedTime = sinceDown - eventTimes[i];
final int p2x = getXCoordValue(xCoords[i]);
final int p2y = yCoords[i];
- final float width = getWidth(elapsedTime, params);
- final float r2 = width / 2.0f;
+ final float r2 = getWidth(elapsedTime, params) / 2.0f;
// Draw trail line only when the current point isn't a down point.
if (!isDownEventXCoord(xCoords[i])) {
- final Path path = line.makePath(p1x, p1y, r1, p2x, p2y, r2);
+ final Path path = roundedLine.makePath(p1x, p1y, r1, p2x, p2y, r2);
if (path != null) {
final int alpha = getAlpha(elapsedTime, params);
paint.setAlpha(alpha);
canvas.drawPath(path, paint);
// Take union for the bounds.
- outBoundsRect.union(p2x, p2y);
- maxWidth = Math.max(maxWidth, width);
+ roundedLine.getBounds(mRoundedLineBounds);
+ outBoundsRect.union(mRoundedLineBounds);
}
}
p1x = p2x;
p1y = p2y;
r1 = r2;
}
- // Take care of trail line width.
- final int inset = -((int)maxWidth + 1);
- outBoundsRect.inset(inset, inset);
}
final int newSize = trailSize - startIndex;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index adf223602..ea03f1bd7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -163,6 +163,10 @@ public class GestureStroke {
}
}
+ public int getLength() {
+ return mEventTimes.getLength();
+ }
+
public void onDownEvent(final int x, final int y, final long downTime,
final long gestureFirstDownTime, final long lastTypingTime) {
reset();
@@ -202,7 +206,7 @@ public class GestureStroke {
if (!hasDetectedFastMove()) {
return false;
}
- final int size = mEventTimes.getLength();
+ final int size = getLength();
if (size <= 0) {
return false;
}
@@ -229,7 +233,7 @@ public class GestureStroke {
}
public void duplicateLastPointWith(final int time) {
- final int lastIndex = mEventTimes.getLength() - 1;
+ final int lastIndex = getLength() - 1;
if (lastIndex >= 0) {
final int x = mXCoordinates.get(lastIndex);
final int y = mYCoordinates.get(lastIndex);
@@ -255,7 +259,7 @@ public class GestureStroke {
}
private void appendPoint(final int x, final int y, final int time) {
- final int lastIndex = mEventTimes.getLength() - 1;
+ final int lastIndex = getLength() - 1;
// The point that is created by {@link duplicateLastPointWith(int)} may have later event
// time than the next {@link MotionEvent}. To maintain the monotonicity of the event time,
// drop the successive point here.
@@ -281,7 +285,7 @@ public class GestureStroke {
}
private int detectFastMove(final int x, final int y, final int time) {
- final int size = mEventTimes.getLength();
+ final int size = getLength();
final int lastIndex = size - 1;
final int lastX = mXCoordinates.get(lastIndex);
final int lastY = mYCoordinates.get(lastIndex);
@@ -321,7 +325,7 @@ public class GestureStroke {
*/
public boolean addPointOnKeyboard(final int x, final int y, final int time,
final boolean isMajorEvent) {
- final int size = mEventTimes.getLength();
+ final int size = getLength();
if (size <= 0) {
// Down event
appendPoint(x, y, time);
@@ -348,7 +352,7 @@ public class GestureStroke {
final int pixelsPerSec = pixels * MSEC_PER_SEC;
// Equivalent to (pixels / msecs < mGestureRecognitionThreshold / MSEC_PER_SEC)
if (pixelsPerSec < mGestureRecognitionSpeedThreshold * msecs) {
- mIncrementalRecognitionSize = mEventTimes.getLength();
+ mIncrementalRecognitionSize = getLength();
}
}
@@ -358,7 +362,7 @@ public class GestureStroke {
}
public final void appendAllBatchPoints(final InputPointers out) {
- appendBatchPoints(out, mEventTimes.getLength());
+ appendBatchPoints(out, getLength());
}
public final void appendIncrementalBatchPoints(final InputPointers out) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index 428e31ccd..0d852dde2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -51,6 +51,7 @@ public final class KeyboardCodesSet {
"key_action_enter",
"key_action_next",
"key_action_previous",
+ "key_shift_enter",
"key_language_switch",
"key_research",
"key_unspecified",
@@ -86,6 +87,7 @@ public final class KeyboardCodesSet {
Constants.CODE_ACTION_ENTER,
Constants.CODE_ACTION_NEXT,
Constants.CODE_ACTION_PREVIOUS,
+ Constants.CODE_SHIFT_ENTER,
Constants.CODE_LANGUAGE_SWITCH,
Constants.CODE_RESEARCH,
Constants.CODE_UNSPECIFIED,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 6fefb809b..6ad7f9ba2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -528,7 +528,7 @@ public final class KeyboardTextsSet {
// U+064E: "َ" ARABIC FATHA
// U+0640: "ـ" ARABIC TATWEEL
// In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
- /* 57 */ "!fixedColumnOrder!7,\u0655,\u0654,\u0652,\u064D,\u064C,\u064B,\u0651,\u0656,\u0670,\u0653,\u0650,\u064F,\u064E,\u0640\u0640\u0640|\u0640",
+ /* 57 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
/* 58 */ "\u0651",
// U+0661: "١" ARABIC-INDIC DIGIT ONE
/* 59 */ "\u0661",
@@ -1327,7 +1327,7 @@ public final class KeyboardTextsSet {
// U+064E: "َ" ARABIC FATHA
// U+0640: "ـ" ARABIC TATWEEL
// In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
- /* 57 */ "!fixedColumnOrder!7,\u0655,\u0652,\u0651,\u064C,\u064D,\u064B,\u0654,\u0656,\u0670,\u0653,\u064F,\u0650,\u064E,\u0640\u0640\u0640|\u0640",
+ /* 57 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
/* 58 */ "\u064B",
// U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
/* 59 */ "\u06F1",
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index a005dc975..7c87467bb 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -41,6 +41,7 @@ import com.android.inputmethod.latin.SuggestedWords;
public final class PreviewPlacerView extends RelativeLayout {
private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance();
+ // TODO: Consolidate gesture preview trail with {@link KeyboardView}
private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails =
CollectionUtils.newSparseArray();
private final Params mGesturePreviewTrailParams;
@@ -51,7 +52,8 @@ public final class PreviewPlacerView extends RelativeLayout {
private int mOffscreenOffsetY;
private Bitmap mOffscreenBuffer;
private final Canvas mOffscreenCanvas = new Canvas();
- private final Rect mOffscreenDirtyRect = new Rect();
+ private final Rect mOffscreenSrcRect = new Rect();
+ private final Rect mDirtyRect = new Rect();
private final Rect mGesturePreviewTrailBoundsRect = new Rect(); // per trail
private final GestureFloatingPreviewText mGestureFloatingPreviewText;
private boolean mShowSlidingKeyInputPreview;
@@ -60,19 +62,16 @@ public final class PreviewPlacerView extends RelativeLayout {
private final DrawingHandler mDrawingHandler;
+ // TODO: Remove drawing handler.
private static final class DrawingHandler extends StaticInnerHandlerWrapper<PreviewPlacerView> {
- private static final int MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 0;
- private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 1;
+ private static final int MSG_UPDATE_GESTURE_PREVIEW_TRAIL = 0;
private final Params mGesturePreviewTrailParams;
- private final int mGestureFloatingPreviewTextLingerTimeout;
public DrawingHandler(final PreviewPlacerView outerInstance,
- final Params gesturePreviewTrailParams,
- final int getstureFloatinPreviewTextLinerTimeout) {
+ final Params gesturePreviewTrailParams) {
super(outerInstance);
mGesturePreviewTrailParams = gesturePreviewTrailParams;
- mGestureFloatingPreviewTextLingerTimeout = getstureFloatinPreviewTextLinerTimeout;
}
@Override
@@ -80,21 +79,12 @@ public final class PreviewPlacerView extends RelativeLayout {
final PreviewPlacerView placerView = getOuterInstance();
if (placerView == null) return;
switch (msg.what) {
- case MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT:
- placerView.setGestureFloatingPreviewText(null);
- break;
case MSG_UPDATE_GESTURE_PREVIEW_TRAIL:
placerView.invalidate();
break;
}
}
- public void dismissGestureFloatingPreviewText() {
- removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT);
- sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT),
- mGestureFloatingPreviewTextLingerTimeout);
- }
-
public void postUpdateGestureTrailPreview() {
removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL);
sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL),
@@ -112,16 +102,13 @@ public final class PreviewPlacerView extends RelativeLayout {
final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
attrs, R.styleable.KeyboardView, defStyle, R.style.KeyboardView);
- final int gestureFloatingPreviewTextLingerTimeout = keyboardViewAttr.getInt(
- R.styleable.KeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
// TODO: mGestureFloatingPreviewText could be an instance of GestureFloatingPreviewText or
// MultiGesturePreviewText, depending on the user's choice in the settings.
mGestureFloatingPreviewText = new GestureFloatingPreviewText(keyboardViewAttr, context);
mGesturePreviewTrailParams = new Params(keyboardViewAttr);
keyboardViewAttr.recycle();
- mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams,
- gestureFloatingPreviewTextLingerTimeout);
+ mDrawingHandler = new DrawingHandler(this, mGesturePreviewTrailParams);
final Paint gesturePaint = new Paint();
gesturePaint.setAntiAlias(true);
@@ -207,6 +194,7 @@ public final class PreviewPlacerView extends RelativeLayout {
mOffscreenBuffer = Bitmap.createBitmap(
mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888);
mOffscreenCanvas.setBitmap(mOffscreenBuffer);
+ mOffscreenCanvas.translate(0, mOffscreenOffsetY);
}
@Override
@@ -219,19 +207,18 @@ public final class PreviewPlacerView extends RelativeLayout {
mayAllocateOffscreenBuffer();
// Draw gesture trails to offscreen buffer.
final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails(
- mOffscreenCanvas, mGesturePaint, mOffscreenDirtyRect);
+ mOffscreenCanvas, mGesturePaint, mDirtyRect);
+ if (needsUpdatingGesturePreviewTrail) {
+ mDrawingHandler.postUpdateGestureTrailPreview();
+ }
// Transfer offscreen buffer to screen.
- if (!mOffscreenDirtyRect.isEmpty()) {
- canvas.translate(0, - mOffscreenOffsetY);
- canvas.drawBitmap(mOffscreenBuffer, mOffscreenDirtyRect, mOffscreenDirtyRect,
- mGesturePaint);
- canvas.translate(0, mOffscreenOffsetY);
+ if (!mDirtyRect.isEmpty()) {
+ mOffscreenSrcRect.set(mDirtyRect);
+ mOffscreenSrcRect.offset(0, mOffscreenOffsetY);
+ canvas.drawBitmap(mOffscreenBuffer, mOffscreenSrcRect, mDirtyRect, null);
// Note: Defer clearing the dirty rectangle here because we will get cleared
// rectangle on the canvas.
}
- if (needsUpdatingGesturePreviewTrail) {
- mDrawingHandler.postUpdateGestureTrailPreview();
- }
}
mGestureFloatingPreviewText.onDraw(canvas);
if (mShowSlidingKeyInputPreview) {
@@ -249,10 +236,8 @@ public final class PreviewPlacerView extends RelativeLayout {
offscreenCanvas.drawRect(dirtyRect, paint);
}
dirtyRect.setEmpty();
-
- // Draw gesture trails to offscreen buffer.
- offscreenCanvas.translate(0, mOffscreenOffsetY);
boolean needsUpdatingGesturePreviewTrail = false;
+ // Draw gesture trails to offscreen buffer.
synchronized (mGesturePreviewTrails) {
// Trails count == fingers count that have ever been active.
final int trailsCount = mGesturePreviewTrails.size();
@@ -265,30 +250,15 @@ public final class PreviewPlacerView extends RelativeLayout {
dirtyRect.union(mGesturePreviewTrailBoundsRect);
}
}
- offscreenCanvas.translate(0, -mOffscreenOffsetY);
-
- // Clip dirty rectangle with offscreen buffer width/height.
- dirtyRect.offset(0, mOffscreenOffsetY);
- clipRect(dirtyRect, 0, 0, mOffscreenWidth, mOffscreenHeight);
return needsUpdatingGesturePreviewTrail;
}
- private static void clipRect(final Rect out, final int left, final int top, final int right,
- final int bottom) {
- out.set(Math.max(out.left, left), Math.max(out.top, top), Math.min(out.right, right),
- Math.min(out.bottom, bottom));
- }
-
public void setGestureFloatingPreviewText(final SuggestedWords suggestedWords) {
if (!mGestureFloatingPreviewText.isPreviewEnabled()) return;
mGestureFloatingPreviewText.setSuggetedWords(suggestedWords);
invalidate();
}
- public void dismissGestureFloatingPreviewText() {
- mDrawingHandler.dismissGestureFloatingPreviewText();
- }
-
private void drawSlidingKeyInputPreview(final Canvas canvas) {
// TODO: Implement rubber band preview
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
index 1f5252077..cd6efc4b7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
@@ -15,6 +15,7 @@
package com.android.inputmethod.keyboard.internal;
import android.graphics.Path;
+import android.graphics.Rect;
import android.graphics.RectF;
public final class RoundedLine {
@@ -100,4 +101,10 @@ public final class RoundedLine {
mPath.close();
return mPath;
}
+
+ public void getBounds(final Rect outBounds) {
+ // Reuse mArc1 as working variable
+ mPath.computeBounds(mArc1, true /* unused */);
+ mArc1.roundOut(outBounds);
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 448d25c73..27af3d1e0 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -35,24 +35,16 @@ public final class BinaryDictionary extends Dictionary {
public static final String DICTIONARY_PACK_AUTHORITY =
"com.android.inputmethod.latin.dictionarypack";
- /**
- * There is a difference between what java and native code can handle.
- * This value should only be used in BinaryDictionary.java
- * It is necessary to keep it at this value because some languages e.g. German have
- * really long words.
- */
+ // Must be identical to MAX_WORD_LENGTH in native/jni/src/defines.h
private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
- private static final int MAX_WORDS = 18;
- private static final int MAX_SPACES = 16;
-
- private static final int MAX_PREDICTIONS = 60;
- private static final int MAX_RESULTS = Math.max(MAX_PREDICTIONS, MAX_WORDS);
+ // Must be identical to MAX_RESULTS in native/jni/src/defines.h
+ private static final int MAX_RESULTS = 18;
private long mNativeDict;
private final Locale mLocale;
private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
- private final int[] mSpaceIndices = new int[MAX_SPACES];
+ private final int[] mSpaceIndices = new int[MAX_RESULTS];
private final int[] mOutputScores = new int[MAX_RESULTS];
private final int[] mOutputTypes = new int[MAX_RESULTS];
@@ -80,16 +72,14 @@ public final class BinaryDictionary extends Dictionary {
/**
* Constructor for the binary dictionary. This is supposed to be called from the
* dictionary factory.
- * @param context the context to access the environment from.
* @param filename the name of the file to read through native code.
* @param offset the offset of the dictionary data within the file.
* @param length the length of the binary data.
* @param useFullEditDistance whether to use the full edit distance in suggestions
* @param dictType the dictionary type, as a human-readable string
*/
- public BinaryDictionary(final Context context, final String filename, final long offset,
- final long length, final boolean useFullEditDistance, final Locale locale,
- final String dictType) {
+ public BinaryDictionary(final String filename, final long offset, final long length,
+ final boolean useFullEditDistance, final Locale locale, final String dictType) {
super(dictType);
mLocale = locale;
mUseFullEditDistance = useFullEditDistance;
@@ -100,24 +90,22 @@ public final class BinaryDictionary extends Dictionary {
JniUtils.loadNativeLibrary();
}
- private native long openNative(String sourceDir, long dictOffset, long dictSize,
- int maxWordLength, int maxWords, int maxPredictions);
- private native void closeNative(long dict);
- private native int getFrequencyNative(long dict, int[] word);
- private native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
- private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession,
- int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds,
- int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture,
- int[] prevWordCodePointArray, boolean useFullEditDistance, int[] outputCodePoints,
- int[] outputScores, int[] outputIndices, int[] outputTypes);
+ private static native long openNative(String sourceDir, long dictOffset, long dictSize);
+ private static native void closeNative(long dict);
+ private static native int getFrequencyNative(long dict, int[] word);
+ private static native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
+ private static native int getSuggestionsNative(long dict, long proximityInfo,
+ long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
+ int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
+ boolean isGesture, int[] prevWordCodePointArray, boolean useFullEditDistance,
+ int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes);
private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
private static native int editDistanceNative(int[] before, int[] after);
// TODO: Move native dict into session
private final void loadDictionary(final String path, final long startOffset,
final long length) {
- mNativeDict = openNative(path, startOffset, length, MAX_WORD_LENGTH, MAX_WORDS,
- MAX_PREDICTIONS);
+ mNativeDict = openNative(path, startOffset, length);
}
@Override
@@ -146,16 +134,14 @@ public final class BinaryDictionary extends Dictionary {
}
final InputPointers ips = composer.getInputPointers();
- final int codesSize = isGesture ? ips.getPointerSize() : composerSize;
+ final int inputSize = isGesture ? ips.getPointerSize() : composerSize;
// proximityInfo and/or prevWordForBigrams may not be null.
- final int tmpCount = getSuggestionsNative(mNativeDict,
- proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(),
- ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(),
- mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
+ final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
+ getTraverseSession(sessionId).getSession(), ips.getXCoordinates(),
+ ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints,
+ inputSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
mUseFullEditDistance, mOutputCodePoints, mOutputScores, mSpaceIndices,
mOutputTypes);
- final int count = Math.min(tmpCount, MAX_PREDICTIONS);
-
final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
for (int j = 0; j < count; ++j) {
if (composerSize > 0 && mOutputScores[j] < 1) break;
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 3a7772452..483504cfb 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -127,6 +127,7 @@ public final class Constants {
}
public static final class Dictionary {
+ // Must be identical to MAX_WORD_LENGTH in native/jni/src/defines.h
public static final int MAX_WORD_LENGTH = 48;
private Dictionary() {
@@ -183,8 +184,9 @@ public final class Constants {
public static final int CODE_ACTION_PREVIOUS = -9;
public static final int CODE_LANGUAGE_SWITCH = -10;
public static final int CODE_RESEARCH = -11;
+ public static final int CODE_SHIFT_ENTER = -12;
// Code value representing the code is not specified.
- public static final int CODE_UNSPECIFIED = -12;
+ public static final int CODE_UNSPECIFIED = -13;
public static boolean isLetterCode(final int code) {
return code >= CODE_SPACE;
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index d1792a369..9be87848f 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -107,6 +107,9 @@ public final class DebugSettings extends PreferenceFragment
String version = "";
try {
final Context context = getActivity();
+ if (context == null) {
+ return;
+ }
final String packageName = context.getPackageName();
PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
version = "Version " + info.versionName;
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index ce1b64660..534e2116b 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -23,10 +23,10 @@ public final class DicTraverseSession {
JniUtils.loadNativeLibrary();
}
- private native long setDicTraverseSessionNative(String locale);
- private native void initDicTraverseSessionNative(long nativeDicTraverseSession,
+ private static native long setDicTraverseSessionNative(String locale);
+ private static native void initDicTraverseSessionNative(long nativeDicTraverseSession,
long dictionary, int[] previousWord, int previousWordLength);
- private native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
+ private static native void releaseDicTraverseSessionNative(long nativeDicTraverseSession);
private long mNativeDicTraverseSession;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index f381973ae..22cf5b38d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -58,9 +58,8 @@ public final class DictionaryFactory {
BinaryDictionaryGetter.getDictionaryFiles(locale, context);
if (null != assetFileList) {
for (final AssetFileAddress f : assetFileList) {
- final BinaryDictionary binaryDictionary =
- new BinaryDictionary(context, f.mFilename, f.mOffset, f.mLength,
- useFullEditDistance, locale, Dictionary.TYPE_MAIN);
+ final BinaryDictionary binaryDictionary = new BinaryDictionary(f.mFilename,
+ f.mOffset, f.mLength, useFullEditDistance, locale, Dictionary.TYPE_MAIN);
if (binaryDictionary.isValidDictionary()) {
dictList.add(binaryDictionary);
}
@@ -112,7 +111,7 @@ public final class DictionaryFactory {
Log.e(TAG, "sourceDir is not a file: " + sourceDir);
return null;
}
- return new BinaryDictionary(context, sourceDir, afd.getStartOffset(), afd.getLength(),
+ return new BinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(),
false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
} catch (android.content.res.Resources.NotFoundException e) {
Log.e(TAG, "Could not find the resource");
@@ -130,17 +129,16 @@ public final class DictionaryFactory {
/**
* Create a dictionary from passed data. This is intended for unit tests only.
- * @param context the test context to create this data from.
* @param dictionary the file to read
* @param startOffset the offset in the file where the data starts
* @param length the length of the data
* @param useFullEditDistance whether to use the full edit distance in suggestions
* @return the created dictionary, or null.
*/
- public static Dictionary createDictionaryForTest(Context context, File dictionary,
- long startOffset, long length, final boolean useFullEditDistance, Locale locale) {
+ public static Dictionary createDictionaryForTest(File dictionary, long startOffset, long length,
+ final boolean useFullEditDistance, Locale locale) {
if (dictionary.isFile()) {
- return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
+ return new BinaryDictionary(dictionary.getAbsolutePath(), startOffset, length,
useFullEditDistance, locale, Dictionary.TYPE_MAIN);
} else {
Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 47adaa8ed..2c7fdcc93 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -279,9 +279,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary {
final long length = file.length();
// Build the new binary dictionary
- final BinaryDictionary newBinaryDictionary =
- new BinaryDictionary(mContext, filename, 0, length, true /* useFullEditDistance */,
- null, mDictType);
+ final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length,
+ true /* useFullEditDistance */, null, mDictType);
if (mBinaryDictionary != null) {
// Ensure all threads accessing the current dictionary have finished before swapping in
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index 9a4503bf4..e2eacb3f0 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin;
import android.text.InputType;
+import android.view.inputmethod.EditorInfo;
public final class InputTypeUtils implements InputType {
private static final int WEB_TEXT_PASSWORD_INPUT_TYPE =
@@ -35,6 +36,7 @@ public final class InputTypeUtils implements InputType {
InputType.TYPE_TEXT_VARIATION_URI,
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD };
+ public static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1;
private InputTypeUtils() {
// This utility class is not publicly instantiable.
@@ -102,4 +104,20 @@ public final class InputTypeUtils implements InputType {
}
return true;
}
+
+ public static int getImeOptionsActionIdFromEditorInfo(final EditorInfo editorInfo) {
+ final int actionId = editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
+ if ((editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
+ return EditorInfo.IME_ACTION_NONE;
+ } else if (editorInfo.actionLabel != null) {
+ return IME_ACTION_CUSTOM_LABEL;
+ } else {
+ return actionId;
+ }
+ }
+
+ public static int getConcreteActionIdFromEditorInfo(final EditorInfo editorInfo) {
+ final int actionId = getImeOptionsActionIdFromEditorInfo(editorInfo);
+ return actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL ? editorInfo.actionId : actionId;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 38f137784..819625225 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -166,6 +166,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
private boolean mExpectingUpdateSelection;
private int mDeleteCount;
private long mLastKeyTime;
+ private int mActionId;
// Member variables for remembering the current device orientation.
private int mDisplayOrientation;
@@ -754,6 +755,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mLastSelectionStart = editorInfo.initialSelStart;
mLastSelectionEnd = editorInfo.initialSelEnd;
+ mActionId = InputTypeUtils.getConcreteActionIdFromEditorInfo(editorInfo);
mHandler.cancelUpdateSuggestionStrip();
mHandler.cancelDoubleSpacePeriodTimer();
@@ -1131,7 +1133,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
separatorString);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.getInstance().onWordFinished(typedWord);
+ ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
}
}
}
@@ -1163,7 +1165,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
private void swapSwapperAndSpace() {
- CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0);
+ final 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
&& lastTwo.charAt(0) == Constants.CODE_SPACE) {
@@ -1171,7 +1173,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final String text = lastTwo.charAt(1) + " ";
mConnection.commitText(text, 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_swapSwapperAndSpace(text);
+ ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text);
}
mKeyboardSwitcher.updateShiftState();
}
@@ -1191,7 +1193,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final String textToInsert = ". ";
mConnection.commitText(textToInsert, 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert);
+ ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert,
+ false /* isBatchMode */);
}
mKeyboardSwitcher.updateShiftState();
return true;
@@ -1271,10 +1274,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
return mOptionsDialog != null && mOptionsDialog.isShowing();
}
- private static int getActionId(final Keyboard keyboard) {
- return keyboard != null ? keyboard.mId.imeActionId() : EditorInfo.IME_ACTION_NONE;
- }
-
private void performEditorAction(final int actionId) {
mConnection.performEditorAction(actionId);
}
@@ -1300,13 +1299,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
private void sendKeyCodePoint(final int code) {
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_sendKeyCodePoint(code);
+ }
// TODO: Remove this special handling of digit letters.
// For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}.
if (code >= '0' && code <= '9') {
sendDownUpKeyEventForBackwardCompatibility(code - '0' + KeyEvent.KEYCODE_0);
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_sendKeyCodePoint(code);
- }
return;
}
@@ -1326,6 +1325,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// Implementation of {@link KeyboardActionListener}.
@Override
public void onCodeInput(final int primaryCode, final int x, final int y) {
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
+ }
final long when = SystemClock.uptimeMillis();
if (primaryCode != Constants.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
mDeleteCount = 0;
@@ -1365,9 +1367,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
case Constants.CODE_SHORTCUT:
mSubtypeSwitcher.switchToShortcutIME(this);
break;
- case Constants.CODE_ACTION_ENTER:
- performEditorAction(getActionId(switcher.getKeyboard()));
- break;
case Constants.CODE_ACTION_NEXT:
performEditorAction(EditorInfo.IME_ACTION_NEXT);
break;
@@ -1382,32 +1381,19 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
ResearchLogger.getInstance().onResearchKeySelected(this);
}
break;
- default:
- mSpaceState = SPACE_STATE_NONE;
- if (mSettings.getCurrent().isWordSeparator(primaryCode)) {
- didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
- } else {
- if (SPACE_STATE_PHANTOM == spaceState) {
- if (ProductionFlag.IS_INTERNAL) {
- if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
- Stats.onAutoCorrection(
- "", mWordComposer.getTypedWord(), " ", mWordComposer);
- }
- }
- commitTyped(LastComposedWord.NOT_A_SEPARATOR);
- }
- final int keyX, keyY;
- final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
- if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) {
- keyX = x;
- keyY = y;
- } else {
- keyX = Constants.NOT_A_COORDINATE;
- keyY = Constants.NOT_A_COORDINATE;
- }
- handleCharacter(primaryCode, keyX, keyY, spaceState);
+ case Constants.CODE_ACTION_ENTER:
+ if (EditorInfo.IME_ACTION_NONE != mActionId
+ && EditorInfo.IME_ACTION_UNSPECIFIED != mActionId) {
+ performEditorAction(mActionId);
+ break;
}
- mExpectingUpdateSelection = true;
+ didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
+ break;
+ case Constants.CODE_SHIFT_ENTER:
+ didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
+ break;
+ default:
+ didAutoCorrect = handleNonSpecialCharacter(primaryCode, x, y, spaceState);
break;
}
switcher.onCodeInput(primaryCode);
@@ -1419,9 +1405,38 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mEnteredText = null;
}
mConnection.endBatchEdit();
- if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onCodeInput(primaryCode, x, y);
+ }
+
+ private boolean handleNonSpecialCharacter(final int primaryCode, final int x, final int y,
+ final int spaceState) {
+ mSpaceState = SPACE_STATE_NONE;
+ final boolean didAutoCorrect;
+ if (mSettings.getCurrent().isWordSeparator(primaryCode)) {
+ didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
+ } else {
+ didAutoCorrect = false;
+ if (SPACE_STATE_PHANTOM == spaceState) {
+ if (ProductionFlag.IS_INTERNAL) {
+ if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
+ Stats.onAutoCorrection(
+ "", mWordComposer.getTypedWord(), " ", mWordComposer);
+ }
+ }
+ commitTyped(LastComposedWord.NOT_A_SEPARATOR);
+ }
+ final int keyX, keyY;
+ final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
+ if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) {
+ keyX = x;
+ keyY = y;
+ } else {
+ keyX = Constants.NOT_A_COORDINATE;
+ keyY = Constants.NOT_A_COORDINATE;
+ }
+ handleCharacter(primaryCode, keyX, keyY, spaceState);
}
+ mExpectingUpdateSelection = true;
+ return didAutoCorrect;
}
// Called from PointerTracker through the KeyboardActionListener interface
@@ -1440,7 +1455,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
mConnection.commitText(text, 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_onTextInput(text);
+ ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */);
}
mConnection.endBatchEdit();
// Space state must be updated before calling updateShiftState
@@ -1587,10 +1602,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final boolean dismissGestureFloatingPreviewText) {
showSuggestionStrip(suggestedWords, null);
final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+ mainKeyboardView.showGestureFloatingPreviewText(suggestedWords);
if (dismissGestureFloatingPreviewText) {
mainKeyboardView.dismissGestureFloatingPreviewText();
- } else {
- mainKeyboardView.showGestureFloatingPreviewText(suggestedWords);
}
}
@@ -1665,10 +1679,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
final int length = mWordComposer.size();
if (length > 0) {
if (mWordComposer.isBatchMode()) {
- mWordComposer.reset();
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_handleBackspace_batch(mWordComposer.getTypedWord());
+ final String word = mWordComposer.getTypedWord();
+ ResearchLogger.latinIME_handleBackspace_batch(word, 1);
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(
+ word, false /* dumpCurrentLogUnit */);
}
+ mWordComposer.reset();
} else {
mWordComposer.deleteLast();
}
@@ -1715,14 +1732,17 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// We should backspace one char and restart suggestion if at the end of a word.
if (mLastSelectionStart != mLastSelectionEnd) {
// If there is a selection, remove it.
- final int lengthToDelete = mLastSelectionEnd - mLastSelectionStart;
+ final int numCharsDeleted = mLastSelectionEnd - mLastSelectionStart;
mConnection.setSelection(mLastSelectionEnd, mLastSelectionEnd);
// Reset mLastSelectionEnd to mLastSelectionStart. This is what is supposed to
// happen, and if it's wrong, the next call to onUpdateSelection will correct it,
// but we want to set it right away to avoid it being used with the wrong values
// later (typically, in a subsequent press on backspace).
mLastSelectionEnd = mLastSelectionStart;
- mConnection.deleteSurroundingText(lengthToDelete, 0);
+ mConnection.deleteSurroundingText(numCharsDeleted, 0);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_handleBackspace(numCharsDeleted);
+ }
} else {
// There is no selection, just delete one character.
if (NOT_A_CURSOR_POSITION == mLastSelectionEnd) {
@@ -1739,8 +1759,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
} else {
mConnection.deleteSurroundingText(1, 0);
}
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_handleBackspace(1);
+ }
if (mDeleteCount > DELETE_ACCELERATE_AT) {
mConnection.deleteSurroundingText(1, 0);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_handleBackspace(1);
+ }
}
}
if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
@@ -1840,6 +1866,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// Returns true if we did an autocorrection, false otherwise.
private boolean handleSeparator(final int primaryCode, final int x, final int y,
final int spaceState) {
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.recordTimeForLogUnitSplit();
+ }
boolean didAutoCorrect = false;
// Handle separator
if (mWordComposer.isComposingWord()) {
@@ -2084,7 +2113,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
}
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
- separatorString);
+ separatorString, mWordComposer.isBatchMode());
}
mExpectingUpdateSelection = true;
commitChosenWord(autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD,
@@ -2118,7 +2147,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
onCodeInput(primaryCode,
Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_punctuationSuggestion(index, suggestion);
+ ResearchLogger.latinIME_punctuationSuggestion(index, suggestion,
+ false /* isBatchMode */, suggestedWords.mIsPrediction);
}
return;
}
@@ -2157,7 +2187,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK,
LastComposedWord.NOT_A_SEPARATOR);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion);
+ ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
+ mWordComposer.isBatchMode());
}
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
@@ -2254,6 +2285,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
mConnection.getWordBeforeCursorIfAtEndOfWord(mSettings.getCurrent());
if (null != word) {
restartSuggestionsOnWordBeforeCursor(word);
+ // TODO: Handle the case where the user manually moves the cursor and then backs up over
+ // a separator. In that case, the current log unit should not be uncommitted.
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(word.toString(),
+ true /* dumpCurrentLogUnit */);
+ }
}
}
@@ -2297,7 +2334,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
}
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord);
+ ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord,
+ mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
+ ResearchLogger.getInstance().uncommitCurrentLogUnit(committedWord,
+ true /* dumpCurrentLogUnit */);
}
// Don't restart suggestion yet. We'll restart if the user deletes the
// separator.
@@ -2310,6 +2350,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
public void promotePhantomSpace() {
if (mSettings.getCurrent().shouldInsertSpacesAutomatically()) {
sendKeyCodePoint(Constants.CODE_SPACE);
+ if (ProductionFlag.IS_EXPERIMENTAL) {
+ ResearchLogger.latinIME_promotePhantomSpace();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 0d3ebacb1..f7268fc33 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -648,19 +648,20 @@ public final class RichInputConnection {
// 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)) {
+ final String periodSpace = ". ";
+ if (!periodSpace.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.");
+ + "\"" + periodSpace + "\" just before the cursor.");
return false;
}
deleteSurroundingText(2, 0);
final String doubleSpace = " ";
commitText(doubleSpace, 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.richInputConnection_revertDoubleSpacePeriod(doubleSpace);
+ ResearchLogger.richInputConnection_revertDoubleSpacePeriod();
}
return true;
}
@@ -685,7 +686,7 @@ public final class RichInputConnection {
final String text = " " + textBeforeCursor.subSequence(0, 1);
commitText(text, 1);
if (ProductionFlag.IS_EXPERIMENTAL) {
- ResearchLogger.richInputConnection_revertSwapPunctuation(text);
+ ResearchLogger.richInputConnection_revertSwapPunctuation();
}
return true;
}
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index f592a2515..c5930a935 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -135,7 +135,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static int readKeyPreviewPopupDismissDelay(final SharedPreferences prefs,
final Resources res) {
- // TODO: use mKeyPreviewPopupDismissDelayRawValue instead of reading it again here.
return Integer.parseInt(prefs.getString(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
Integer.toString(res.getInteger(
R.integer.config_key_preview_linger_timeout))));
@@ -186,7 +185,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
}
public static boolean readUsabilityStudyMode(final SharedPreferences prefs) {
- // TODO: use mUsabilityStudyMode instead of reading it again here
return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
}
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java
index fac85a8cc..9a2024618 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/SettingsValues.java
@@ -51,14 +51,8 @@ public final class SettingsValues {
public final boolean mSoundOn;
public final boolean mKeyPreviewPopupOn;
private final String mVoiceMode;
- private final String mAutoCorrectionThresholdRawValue;
- public final String mShowSuggestionsSetting;
- @SuppressWarnings("unused") // TODO: Use this
- private final boolean mUsabilityStudyMode;
public final boolean mIncludesOtherImesInLanguageSwitchList;
public final boolean mShowsLanguageSwitchKey;
- @SuppressWarnings("unused") // TODO: Use this
- private final String mKeyPreviewPopupDismissDelayRawValue;
public final boolean mUseContactsDict;
public final boolean mUseDoubleSpacePeriod;
// Use bigrams to predict the next word when there is no input for it yet
@@ -122,20 +116,15 @@ public final class SettingsValues {
final String voiceModeMain = res.getString(R.string.voice_mode_main);
final String voiceModeOff = res.getString(R.string.voice_mode_off);
mVoiceMode = prefs.getString(Settings.PREF_VOICE_MODE, voiceModeMain);
- mAutoCorrectionThresholdRawValue = prefs.getString(Settings.PREF_AUTO_CORRECTION_THRESHOLD,
+ final String autoCorrectionThresholdRawValue = prefs.getString(
+ Settings.PREF_AUTO_CORRECTION_THRESHOLD,
res.getString(R.string.auto_correction_threshold_mode_index_modest));
- mShowSuggestionsSetting = prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING,
- res.getString(R.string.prefs_suggestion_visibility_default_value));
- mUsabilityStudyMode = Settings.readUsabilityStudyMode(prefs);
mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean(
Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false);
mShowsLanguageSwitchKey = Settings.readShowsLanguageSwitchKey(prefs);
- mKeyPreviewPopupDismissDelayRawValue = prefs.getString(
- Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
- Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout)));
mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true);
- mAutoCorrectEnabled = readAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue);
+ mAutoCorrectEnabled = readAutoCorrectEnabled(res, autoCorrectionThresholdRawValue);
mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res);
// Compute other readable settings
@@ -143,7 +132,7 @@ public final class SettingsValues {
mKeypressSoundVolume = Settings.readKeypressSoundVolume(prefs, res);
mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res);
mAutoCorrectionThreshold = readAutoCorrectionThreshold(res,
- mAutoCorrectionThresholdRawValue);
+ autoCorrectionThresholdRawValue);
mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
final boolean gestureInputEnabledByBuildConfig = res.getBoolean(
@@ -154,7 +143,10 @@ public final class SettingsValues {
mGestureFloatingPreviewTextEnabled = prefs.getBoolean(
Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true);
mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect;
- mSuggestionVisibility = createSuggestionVisibility(res);
+ final String showSuggestionsSetting = prefs.getString(
+ Settings.PREF_SHOW_SUGGESTIONS_SETTING,
+ res.getString(R.string.prefs_suggestion_visibility_default_value));
+ mSuggestionVisibility = createSuggestionVisibility(res, showSuggestionsSetting);
}
public boolean isApplicationSpecifiedCompletionsOn() {
@@ -271,8 +263,8 @@ public final class SettingsValues {
SUGGESTION_VISIBILITY_HIDE_VALUE
};
- private int createSuggestionVisibility(final Resources res) {
- final String suggestionVisiblityStr = mShowSuggestionsSetting;
+ private static int createSuggestionVisibility(final Resources res,
+ final String suggestionVisiblityStr) {
for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) {
if (suggestionVisiblityStr.equals(res.getString(visibility))) {
return visibility;
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index fe2908428..eb0ec3990 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -31,6 +31,7 @@ import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
+import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import java.util.List;
@@ -239,7 +240,14 @@ public final class SubtypeSwitcher {
return mNeedsToDisplayLanguage.getValue();
}
+ private static Locale sForcedLocaleForTesting = null;
+ @UsedForTesting
+ void forceLocale(final Locale locale) {
+ sForcedLocaleForTesting = locale;
+ }
+
public Locale getCurrentSubtypeLocale() {
+ if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting;
return SubtypeLocale.getSubtypeLocale(getCurrentSubtype());
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 3dc2ba95b..2abf75da4 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -72,9 +72,8 @@ public final class Suggest {
}
@UsedForTesting
- Suggest(final Context context, final File dictionary,
- final long startOffset, final long length, final Locale locale) {
- final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
+ Suggest(final File dictionary, final long startOffset, final long length, final Locale locale) {
+ final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionary,
startOffset, length /* useFullEditDistance */, false, locale);
mLocale = locale;
mMainDictionary = mainDict;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 89d6c9010..907c0cdca 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -81,6 +81,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
public static final int SCRIPT_LATIN = 0;
public static final int SCRIPT_CYRILLIC = 1;
+ public static final int SCRIPT_GREEK = 2;
public static final String SINGLE_QUOTE = "\u0027";
public static final String APOSTROPHE = "\u2019";
private static final TreeMap<String, Integer> mLanguageToScript;
@@ -94,18 +95,23 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
// IMPORTANT: this only contains languages - do not write countries in there.
// Only the language is searched from the map.
mLanguageToScript = CollectionUtils.newTreeMap();
- mLanguageToScript.put("en", SCRIPT_LATIN);
- mLanguageToScript.put("fr", SCRIPT_LATIN);
- mLanguageToScript.put("de", SCRIPT_LATIN);
- mLanguageToScript.put("nl", SCRIPT_LATIN);
mLanguageToScript.put("cs", SCRIPT_LATIN);
+ mLanguageToScript.put("da", SCRIPT_LATIN);
+ mLanguageToScript.put("de", SCRIPT_LATIN);
+ mLanguageToScript.put("el", SCRIPT_GREEK);
+ mLanguageToScript.put("en", SCRIPT_LATIN);
mLanguageToScript.put("es", SCRIPT_LATIN);
- mLanguageToScript.put("it", SCRIPT_LATIN);
+ mLanguageToScript.put("fi", SCRIPT_LATIN);
+ mLanguageToScript.put("fr", SCRIPT_LATIN);
mLanguageToScript.put("hr", SCRIPT_LATIN);
+ mLanguageToScript.put("it", SCRIPT_LATIN);
+ mLanguageToScript.put("lt", SCRIPT_LATIN);
+ mLanguageToScript.put("lv", SCRIPT_LATIN);
+ mLanguageToScript.put("nb", SCRIPT_LATIN);
+ mLanguageToScript.put("nl", SCRIPT_LATIN);
mLanguageToScript.put("pt", SCRIPT_LATIN);
+ mLanguageToScript.put("sl", SCRIPT_LATIN);
mLanguageToScript.put("ru", SCRIPT_CYRILLIC);
- // TODO: Make a persian proximity, and activate the Farsi subtype.
- // mLanguageToScript.put("fa", SCRIPT_PERSIAN);
}
@Override public void onCreate() {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 470943be1..6581978c9 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -143,8 +143,17 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
// All Cyrillic characters are in the 400~52F block. There are some in the upper
// Unicode range, but they are archaic characters that are not used in modern
- // russian and are not used by our dictionary.
+ // Russian and are not used by our dictionary.
return codePoint >= 0x400 && codePoint <= 0x52F && Character.isLetter(codePoint);
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ // Greek letters are either in the 370~3FF range (Greek & Coptic), or in the
+ // 1F00~1FFF range (Greek extended). Our dictionary contains both sort of characters.
+ // Our dictionary also contains a few words with 0xF2; it would be best to check
+ // if that's correct, but a Google search does return results for these words so
+ // they are probably okay.
+ return (codePoint >= 0x370 && codePoint <= 0x3FF)
+ || (codePoint >= 0x1F00 && codePoint <= 0x1FFF)
+ || codePoint == 0xF2;
default:
// Should never come here
throw new RuntimeException("Impossible value of script: " + script);
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
index 6c0d79c2b..572a826a5 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -73,6 +73,12 @@ public final class SpellCheckerProximityInfo {
// to spell check has been entered with one of the keyboards above. Also, specifically
// to English, many spelling errors consist of the last vowel of the word being wrong
// because in English vowels tend to merge with each other in pronunciation.
+ /*
+ The Qwerty layout this represents looks like the following:
+ q w e r t y u i o p
+ a s d f g h j k l
+ z x c v b n m
+ */
final static int[] PROXIMITY = {
// Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
// and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
@@ -127,10 +133,13 @@ public final class SpellCheckerProximityInfo {
final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
// TODO: The following table is solely based on the keyboard layout. Consult with Russian
// speakers on commonly misspelled words/letters.
- final static int[] PROXIMITY = {
- // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
- // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
- // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
+ /*
+ The Russian layout this represents looks like the following:
+ й ц у к е н г ш щ з х
+ ф ы в а п р о л д ж э
+ я ч с м и т ь б ю
+
+ This gives us the following table:
'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -143,7 +152,6 @@ public final class SpellCheckerProximityInfo {
'з', 'щ', 'д', 'ж', 'э', 'х', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'х', 'з', 'ж', 'э', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- // Proximity for row 2. See comment above about size.
'ф', 'й', 'ц', 'ы', 'я', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ы', 'й', 'ц', 'у', 'ф', 'в', 'я', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'в', 'ц', 'у', 'к', 'ы', 'а', 'я', 'ч', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -156,7 +164,6 @@ public final class SpellCheckerProximityInfo {
'ж', 'щ', 'з', 'х', 'д', 'э', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'э', 'з', 'х', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
- // Proximity for row 3. See comment above about size.
'я', 'ф', 'ы', 'в', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ч', 'ы', 'в', 'а', 'я', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'с', 'в', 'а', 'п', 'ч', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
@@ -166,6 +173,249 @@ public final class SpellCheckerProximityInfo {
'ь', 'о', 'л', 'д', 'т', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'б', 'л', 'д', 'ж', 'ь', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
'ю', 'д', 'ж', 'э', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ Using the following characters:
+ */
+ private static final int CY_SHORT_I = '\u0439'; // й
+ private static final int CY_TSE = '\u0446'; // ц
+ private static final int CY_U = '\u0443'; // у
+ private static final int CY_KA = '\u043A'; // к
+ private static final int CY_IE = '\u0435'; // е
+ private static final int CY_EN = '\u043D'; // н
+ private static final int CY_GHE = '\u0433'; // г
+ private static final int CY_SHA = '\u0448'; // ш
+ private static final int CY_SHCHA = '\u0449'; // щ
+ private static final int CY_ZE = '\u0437'; // з
+ private static final int CY_HA = '\u0445'; // х
+ private static final int CY_EF = '\u0444'; // ф
+ private static final int CY_YERU = '\u044B'; // ы
+ private static final int CY_VE = '\u0432'; // в
+ private static final int CY_A = '\u0430'; // а
+ private static final int CY_PE = '\u043F'; // п
+ private static final int CY_ER = '\u0440'; // р
+ private static final int CY_O = '\u043E'; // о
+ private static final int CY_EL = '\u043B'; // л
+ private static final int CY_DE = '\u0434'; // д
+ private static final int CY_ZHE = '\u0436'; // ж
+ private static final int CY_E = '\u044D'; // э
+ private static final int CY_YA = '\u044F'; // я
+ private static final int CY_CHE = '\u0447'; // ч
+ private static final int CY_ES = '\u0441'; // с
+ private static final int CY_EM = '\u043C'; // м
+ private static final int CY_I = '\u0438'; // и
+ private static final int CY_TE = '\u0442'; // т
+ private static final int CY_SOFT_SIGN = '\u044C'; // ь
+ private static final int CY_BE = '\u0431'; // б
+ private static final int CY_YU = '\u044E'; // ю
+ final static int[] PROXIMITY = {
+ // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
+ // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
+ // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
+ CY_SHORT_I, CY_TSE, CY_EF, CY_YERU, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_TSE, CY_SHORT_I, CY_EF, CY_YERU, CY_VE, CY_U, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_U, CY_TSE, CY_YERU, CY_VE, CY_A, CY_KA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_KA, CY_U, CY_VE, CY_A, CY_PE, CY_IE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_IE, CY_KA, CY_A, CY_PE, CY_ER, CY_EN, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_EN, CY_IE, CY_PE, CY_ER, CY_O, CY_GHE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_GHE, CY_EN, CY_ER, CY_O, CY_EL, CY_SHA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_SHA, CY_GHE, CY_O, CY_EL, CY_DE, CY_SHCHA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_SHCHA, CY_SHA, CY_EL, CY_DE, CY_ZHE, CY_ZE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_ZE, CY_SHCHA, CY_DE, CY_ZHE, CY_E, CY_HA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_HA, CY_ZE, CY_ZHE, CY_E, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ // Proximity for row 2. See comment above about size.
+ CY_EF, CY_SHORT_I, CY_TSE, CY_YERU, CY_YA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_YERU, CY_SHORT_I, CY_TSE, CY_U, CY_EF, CY_VE, CY_YA, CY_CHE,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_VE, CY_TSE, CY_U, CY_KA, CY_YERU, CY_A, CY_YA, CY_CHE,
+ CY_ES, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_A, CY_U, CY_KA, CY_IE, CY_VE, CY_PE, CY_CHE, CY_ES,
+ CY_EM, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_PE, CY_KA, CY_IE, CY_EN, CY_A, CY_ER, CY_ES, CY_EM,
+ CY_I, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_ER, CY_IE, CY_EN, CY_GHE, CY_PE, CY_O, CY_EM, CY_I,
+ CY_TE, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_O, CY_EN, CY_GHE, CY_SHA, CY_ER, CY_EL, CY_I, CY_TE,
+ CY_SOFT_SIGN, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_EL, CY_GHE, CY_SHA, CY_SHCHA, CY_O, CY_DE, CY_TE, CY_SOFT_SIGN,
+ CY_BE, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_DE, CY_SHA, CY_SHCHA, CY_ZE, CY_EL, CY_ZHE, CY_SOFT_SIGN, CY_BE,
+ CY_YU, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_ZHE, CY_SHCHA, CY_ZE, CY_HA, CY_DE, CY_E, CY_BE, CY_YU,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_E, CY_ZE, CY_HA, CY_YU, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ // Proximity for row 3. See comment above about size.
+ CY_YA, CY_EF, CY_YERU, CY_VE, CY_CHE, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_CHE, CY_YERU, CY_VE, CY_A, CY_YA, CY_ES, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_ES, CY_VE, CY_A, CY_PE, CY_CHE, CY_EM, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_EM, CY_A, CY_PE, CY_ER, CY_ES, CY_I, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_I, CY_PE, CY_ER, CY_O, CY_EM, CY_TE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_TE, CY_ER, CY_O, CY_EL, CY_I, CY_SOFT_SIGN, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_SOFT_SIGN, CY_O, CY_EL, CY_DE, CY_TE, CY_BE, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_BE, CY_EL, CY_DE, CY_ZHE, CY_SOFT_SIGN, CY_YU, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ CY_YU, CY_DE, CY_ZHE, CY_E, CY_BE, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ };
+ static {
+ buildProximityIndices(PROXIMITY, INDICES);
+ }
+ static int getIndexOf(int characterCode) {
+ return computeIndex(characterCode, INDICES);
+ }
+ }
+
+ private static final class Greek {
+ final private static TreeMap<Integer, Integer> INDICES = CollectionUtils.newTreeMap();
+ // TODO: The following table is solely based on the keyboard layout. Consult with Greek
+ // speakers on commonly misspelled words/letters.
+ /*
+ The Greek layout this represents looks like the following:
+ ; ς ε ρ τ υ θ ι ο π
+ α σ δ φ γ η ξ κ λ
+ ζ χ ψ ω β ν μ
+
+ This gives us the following table:
+ 'ς', 'ε', 'α', 'σ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ε', 'ς', 'ρ', 'σ', 'δ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ρ', 'ε', 'τ', 'δ', 'φ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'τ', 'ρ', 'υ', 'φ', 'γ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'υ', 'τ', 'θ', 'γ', 'η', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'θ', 'υ', 'ι', 'η', 'ξ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ι', 'θ', 'ο', 'ξ', 'κ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ο', 'ι', 'π', 'κ', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'π', 'ο', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ 'α', 'ς', 'σ', 'ζ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'σ', 'ς', 'ε', 'α', 'δ', 'ζ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'δ', 'ε', 'ρ', 'σ', 'φ', 'ζ', 'χ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'φ', 'ρ', 'τ', 'δ', 'γ', 'χ', 'ψ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'γ', 'τ', 'υ', 'φ', 'η', 'ψ', 'ω', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'η', 'υ', 'θ', 'γ', 'ξ', 'ω', 'β', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ξ', 'θ', 'ι', 'η', 'κ', 'β', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'κ', 'ι', 'ο', 'ξ', 'λ', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'λ', 'ο', 'π', 'κ', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ 'ζ', 'α', 'σ', 'δ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'χ', 'σ', 'δ', 'φ', 'ζ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ψ', 'δ', 'φ', 'γ', 'χ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ω', 'φ', 'γ', 'η', 'ψ', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'β', 'γ', 'η', 'ξ', 'ω', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'ν', 'η', 'ξ', 'κ', 'β', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'μ', 'ξ', 'κ', 'λ', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ Using the following characters:
+ */
+ private static final int GR_FINAL_SIGMA = '\u03C2'; // ς
+ private static final int GR_EPSILON = '\u03B5'; // ε
+ private static final int GR_RHO = '\u03C1'; // ρ
+ private static final int GR_TAU = '\u03C4'; // τ
+ private static final int GR_UPSILON = '\u03C5'; // υ
+ private static final int GR_THETA = '\u03B8'; // θ
+ private static final int GR_IOTA = '\u03B9'; // ι
+ private static final int GR_OMICRON = '\u03BF'; // ο
+ private static final int GR_PI = '\u03C0'; // π
+ private static final int GR_ALPHA = '\u03B1'; // α
+ private static final int GR_SIGMA = '\u03C3'; // σ
+ private static final int GR_DELTA = '\u03B4'; // δ
+ private static final int GR_PHI = '\u03C6'; // φ
+ private static final int GR_GAMMA = '\u03B3'; // γ
+ private static final int GR_ETA = '\u03B7'; // η
+ private static final int GR_XI = '\u03BE'; // ξ
+ private static final int GR_KAPPA = '\u03BA'; // κ
+ private static final int GR_LAMDA = '\u03BB'; // λ
+ private static final int GR_ZETA = '\u03B6'; // ζ
+ private static final int GR_CHI = '\u03C7'; // χ
+ private static final int GR_PSI = '\u03C8'; // ψ
+ private static final int GR_OMEGA = '\u03C9'; // ω
+ private static final int GR_BETA = '\u03B2'; // β
+ private static final int GR_NU = '\u03BD'; // ν
+ private static final int GR_MU = '\u03BC'; // μ
+ final static int[] PROXIMITY = {
+ // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter,
+ // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's.
+ // The number of rows must be exactly PROXIMITY_GRID_HEIGHT.
+ GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_SIGMA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_EPSILON, GR_FINAL_SIGMA, GR_RHO, GR_SIGMA, GR_DELTA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_RHO, GR_EPSILON, GR_TAU, GR_DELTA, GR_PHI, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_TAU, GR_RHO, GR_UPSILON, GR_PHI, GR_GAMMA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_UPSILON, GR_TAU, GR_THETA, GR_GAMMA, GR_ETA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_THETA, GR_UPSILON, GR_IOTA, GR_ETA, GR_XI, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_IOTA, GR_THETA, GR_OMICRON, GR_XI, GR_KAPPA, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_OMICRON, GR_IOTA, GR_PI, GR_KAPPA, GR_LAMDA, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_PI, GR_OMICRON, GR_LAMDA, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ GR_ALPHA, GR_FINAL_SIGMA, GR_SIGMA, GR_ZETA, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_SIGMA, GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_DELTA, GR_ZETA, GR_CHI, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_DELTA, GR_EPSILON, GR_RHO, GR_SIGMA, GR_PHI, GR_ZETA, GR_CHI, GR_PSI,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_PHI, GR_RHO, GR_TAU, GR_DELTA, GR_GAMMA, GR_CHI, GR_PSI, GR_OMEGA,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_GAMMA, GR_TAU, GR_UPSILON, GR_PHI, GR_ETA, GR_PSI, GR_OMEGA, GR_BETA,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_ETA, GR_UPSILON, GR_THETA, GR_GAMMA, GR_XI, GR_OMEGA, GR_BETA, GR_NU,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_XI, GR_THETA, GR_IOTA, GR_ETA, GR_KAPPA, GR_BETA, GR_NU, GR_MU,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_KAPPA, GR_IOTA, GR_OMICRON, GR_XI, GR_LAMDA, GR_NU, GR_MU, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_LAMDA, GR_OMICRON, GR_PI, GR_KAPPA, GR_MU, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ GR_ZETA, GR_ALPHA, GR_SIGMA, GR_DELTA, GR_CHI, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_CHI, GR_SIGMA, GR_DELTA, GR_PHI, GR_ZETA, GR_PSI, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_PSI, GR_DELTA, GR_PHI, GR_GAMMA, GR_CHI, GR_OMEGA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_OMEGA, GR_PHI, GR_GAMMA, GR_ETA, GR_PSI, GR_BETA, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_BETA, GR_GAMMA, GR_ETA, GR_XI, GR_OMEGA, GR_NU, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_NU, GR_ETA, GR_XI, GR_KAPPA, GR_BETA, GR_MU, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ GR_MU, GR_XI, GR_KAPPA, GR_LAMDA, GR_NU, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
};
@@ -183,6 +433,8 @@ public final class SpellCheckerProximityInfo {
return Latin.PROXIMITY;
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
return Cyrillic.PROXIMITY;
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ return Greek.PROXIMITY;
default:
throw new RuntimeException("Wrong script supplied: " + script);
}
@@ -194,6 +446,8 @@ public final class SpellCheckerProximityInfo {
return Latin.getIndexOf(codePoint);
case AndroidSpellCheckerService.SCRIPT_CYRILLIC:
return Cyrillic.getIndexOf(codePoint);
+ case AndroidSpellCheckerService.SCRIPT_GREEK:
+ return Greek.getIndexOf(codePoint);
default:
throw new RuntimeException("Wrong script supplied: " + script);
}
diff --git a/java/src/com/android/inputmethod/research/FixedLogBuffer.java b/java/src/com/android/inputmethod/research/FixedLogBuffer.java
index f3302d856..777111947 100644
--- a/java/src/com/android/inputmethod/research/FixedLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/FixedLogBuffer.java
@@ -72,7 +72,16 @@ public class FixedLogBuffer extends LogBuffer {
mNumActualWords++; // Must be a word, or we wouldn't be here.
}
- private void shiftOutThroughFirstWord() {
+ @Override
+ public LogUnit unshiftIn() {
+ final LogUnit logUnit = super.unshiftIn();
+ if (logUnit != null && logUnit.hasWord()) {
+ mNumActualWords--;
+ }
+ return logUnit;
+ }
+
+ public void shiftOutThroughFirstWord() {
final LinkedList<LogUnit> logUnits = getLogUnits();
while (!logUnits.isEmpty()) {
final LogUnit logUnit = logUnits.removeFirst();
diff --git a/java/src/com/android/inputmethod/research/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java
index 14e8d08a2..9d095f8ad 100644
--- a/java/src/com/android/inputmethod/research/LogBuffer.java
+++ b/java/src/com/android/inputmethod/research/LogBuffer.java
@@ -46,6 +46,20 @@ public class LogBuffer {
mLogUnits.add(logUnit);
}
+ public LogUnit unshiftIn() {
+ if (mLogUnits.isEmpty()) {
+ return null;
+ }
+ return mLogUnits.removeLast();
+ }
+
+ public LogUnit peekLastLogUnit() {
+ if (mLogUnits.isEmpty()) {
+ return null;
+ }
+ return mLogUnits.peekLast();
+ }
+
public boolean isEmpty() {
return mLogUnits.isEmpty();
}
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index bcb144f5f..cfba28909 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -114,24 +114,37 @@ import java.util.Map;
debugStringWriter = null;
debugJsonWriter = null;
}
- final int size = mLogStatementList.size();
// Write out any logStatement that passes the privacy filter.
- for (int i = 0; i < size; i++) {
- final LogStatement logStatement = mLogStatementList.get(i);
- if (!isIncludingPrivateData && logStatement.mIsPotentiallyPrivate) {
- continue;
- }
- if (mIsPartOfMegaword && logStatement.mIsPotentiallyRevealing) {
- continue;
+ final int size = mLogStatementList.size();
+ if (size != 0) {
+ // Note that jsonWriter is only set to a non-null value if the logUnit start text is
+ // output and at least one logStatement is output.
+ JsonWriter jsonWriter = null;
+ for (int i = 0; i < size; i++) {
+ final LogStatement logStatement = mLogStatementList.get(i);
+ if (!isIncludingPrivateData && logStatement.mIsPotentiallyPrivate) {
+ continue;
+ }
+ if (mIsPartOfMegaword && logStatement.mIsPotentiallyRevealing) {
+ continue;
+ }
+ // Only retrieve the jsonWriter if we need to. If we don't get this far, then
+ // researchLog.getValidJsonWriterLocked() will not ever be called, and the file
+ // will not have been opened for writing.
+ if (jsonWriter == null) {
+ jsonWriter = researchLog.getValidJsonWriterLocked();
+ outputLogUnitStart(jsonWriter, isIncludingPrivateData);
+ }
+ outputLogStatementToLocked(jsonWriter, mLogStatementList.get(i), mValuesList.get(i),
+ mTimeList.get(i));
+ if (DEBUG) {
+ outputLogStatementToLocked(debugJsonWriter, mLogStatementList.get(i),
+ mValuesList.get(i), mTimeList.get(i));
+ }
}
- // Only retrieve the jsonWriter if we need to. If we don't get this far, then
- // researchLog.getValidJsonWriter() will not open the file for writing.
- final JsonWriter jsonWriter = researchLog.getValidJsonWriterLocked();
- outputLogStatementToLocked(jsonWriter, mLogStatementList.get(i), mValuesList.get(i),
- mTimeList.get(i));
- if (DEBUG) {
- outputLogStatementToLocked(debugJsonWriter, mLogStatementList.get(i),
- mValuesList.get(i), mTimeList.get(i));
+ if (jsonWriter != null) {
+ // We must have called logUnitStart earlier, so emit a logUnitStop.
+ outputLogUnitStop(jsonWriter, isIncludingPrivateData);
}
}
if (DEBUG) {
@@ -152,6 +165,38 @@ import java.util.Map;
private static final String CURRENT_TIME_KEY = "_ct";
private static final String UPTIME_KEY = "_ut";
private static final String EVENT_TYPE_KEY = "_ty";
+ private static final String WORD_KEY = "_wo";
+ private static final String LOG_UNIT_BEGIN_KEY = "logUnitStart";
+ private static final String LOG_UNIT_END_KEY = "logUnitEnd";
+
+ private void outputLogUnitStart(final JsonWriter jsonWriter,
+ final boolean isIncludingPrivateData) {
+ try {
+ jsonWriter.beginObject();
+ jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
+ if (isIncludingPrivateData) {
+ jsonWriter.name(WORD_KEY).value(getWord());
+ }
+ jsonWriter.name(EVENT_TYPE_KEY).value(LOG_UNIT_BEGIN_KEY);
+ jsonWriter.endObject();
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.w(TAG, "Error in JsonWriter; cannot write LogUnitStart");
+ }
+ }
+
+ private void outputLogUnitStop(final JsonWriter jsonWriter,
+ final boolean isIncludingPrivateData) {
+ try {
+ jsonWriter.beginObject();
+ jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis());
+ jsonWriter.name(EVENT_TYPE_KEY).value(LOG_UNIT_END_KEY);
+ jsonWriter.endObject();
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.w(TAG, "Error in JsonWriter; cannot write LogUnitStop");
+ }
+ }
/**
* Write the logStatement and its contents out through jsonWriter.
@@ -240,6 +285,7 @@ import java.util.Map;
public LogUnit splitByTime(final long maxTime) {
// Assume that mTimeList is in sorted order.
final int length = mTimeList.size();
+ // TODO: find time by binary search, e.g. using Collections#binarySearch()
for (int index = 0; index < length; index++) {
if (mTimeList.get(index) > maxTime) {
final List<LogStatement> laterLogStatements =
@@ -267,4 +313,13 @@ import java.util.Map;
}
return new LogUnit();
}
+
+ public void append(final LogUnit logUnit) {
+ mLogStatementList.addAll(logUnit.mLogStatementList);
+ mValuesList.addAll(logUnit.mValuesList);
+ mTimeList.addAll(logUnit.mTimeList);
+ mWord = null;
+ mMayContainDigit = mMayContainDigit || logUnit.mMayContainDigit;
+ mIsPartOfMegaword = false;
+ }
}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index bec21d7e0..a8f255a41 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -26,18 +26,42 @@ import java.util.LinkedList;
import java.util.Random;
/**
- * Provide a log buffer of fixed length that enforces privacy restrictions.
+ * MainLogBuffer is a FixedLogBuffer that tracks the state of LogUnits to make privacy guarantees.
*
- * The privacy restrictions include making sure that no numbers are logged, that all logged words
- * are in the dictionary, and that words are recorded infrequently enough that the user's meaning
- * cannot be easily determined.
+ * There are three forms of privacy protection: 1) only words in the main dictionary are allowed to
+ * be logged in enough detail to determine their contents, 2) only a subset of words are logged
+ * in detail, such as 10%, and 3) no numbers are logged.
+ *
+ * This class maintains a list of LogUnits, each corresponding to a word. As the user completes
+ * words, they are added here. But if the user backs up over their current word to edit a word
+ * entered earlier, then it is pulled out of this LogBuffer, changes are then added to the end of
+ * the LogUnit, and it is pushed back in here when the user is done. Because words may be pulled
+ * back out even after they are pushed in, we must not publish the contents of this LogBuffer too
+ * quickly. However, we cannot let the contents pile up either, or it will limit the editing that
+ * a user can perform.
+ *
+ * To balance these requirements (keep history so user can edit, flush history so it does not pile
+ * up), the LogBuffer is considered "complete" when the user has entered enough words to form an
+ * n-gram, followed by enough additional non-detailed words (that are in the 90%, as per above).
+ * Once complete, the n-gram may be published to flash storage (via the ResearchLog class).
+ * However, the additional non-detailed words are retained, in case the user backspaces to edit
+ * them. The MainLogBuffer then continues to add words, publishing individual non-detailed words
+ * as new words arrive. After enough non-detailed words have been pushed out to account for the
+ * 90% between words, the words at the front of the LogBuffer can be published as an n-gram again.
+ *
+ * If the words that would form the valid n-gram are not in the dictionary, then words are pushed
+ * through the LogBuffer one at a time until an n-gram is found that is entirely composed of
+ * dictionary words.
+ *
+ * If the user closes a session, then the entire LogBuffer is flushed, publishing any embedded
+ * n-gram containing dictionary words.
*/
public class MainLogBuffer extends FixedLogBuffer {
private static final String TAG = MainLogBuffer.class.getSimpleName();
private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
// The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams.
- private static final int N_GRAM_SIZE = 2;
+ public static final int N_GRAM_SIZE = 2;
// The number of words between n-grams to omit from the log. If debugging, record 50% of all
// words. Otherwise, only record 10%.
private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES =
@@ -46,49 +70,31 @@ public class MainLogBuffer extends FixedLogBuffer {
private final ResearchLog mResearchLog;
private Suggest mSuggest;
- // The minimum periodicity with which n-grams can be sampled. E.g. mWinWordPeriod is 10 if
- // every 10th bigram is sampled, i.e., words 1-8 are not, but the bigram at words 9 and 10, etc.
- // for 11-18, and the bigram at words 19 and 20. If an n-gram is not safe (e.g. it contains a
- // number in the middle or an out-of-vocabulary word), then sampling is delayed until a safe
- // n-gram does appear.
- /* package for test */ int mMinWordPeriod;
+ /* package for test */ int mNumWordsBetweenNGrams;
// Counter for words left to suppress before an n-gram can be sampled. Reset to mMinWordPeriod
// after a sample is taken.
- /* package for test */ int mWordsUntilSafeToSample;
+ /* package for test */ int mNumWordsUntilSafeToSample;
public MainLogBuffer(final ResearchLog researchLog) {
- super(N_GRAM_SIZE);
+ super(N_GRAM_SIZE + DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES);
mResearchLog = researchLog;
- mMinWordPeriod = DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES + N_GRAM_SIZE;
+ mNumWordsBetweenNGrams = DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES;
final Random random = new Random();
- mWordsUntilSafeToSample = random.nextInt(mMinWordPeriod);
+ mNumWordsUntilSafeToSample = DEBUG ? 0 : random.nextInt(mNumWordsBetweenNGrams + 1);
}
public void setSuggest(final Suggest suggest) {
mSuggest = suggest;
}
- @Override
- public void shiftIn(final LogUnit newLogUnit) {
- super.shiftIn(newLogUnit);
- if (newLogUnit.hasWord()) {
- if (mWordsUntilSafeToSample > 0) {
- mWordsUntilSafeToSample--;
- }
- }
- if (DEBUG) {
- Log.d(TAG, "shiftedIn " + (newLogUnit.hasWord() ? newLogUnit.getWord() : ""));
- }
- }
-
public void resetWordCounter() {
- mWordsUntilSafeToSample = mMinWordPeriod;
+ mNumWordsUntilSafeToSample = mNumWordsBetweenNGrams;
}
/**
- * Determines whether the content of the MainLogBuffer can be safely uploaded in its complete
- * form and still protect the user's privacy.
+ * Determines whether uploading the n words at the front the MainLogBuffer will not violate
+ * user privacy.
*
* The size of the MainLogBuffer is just enough to hold one n-gram, its corrections, and any
* non-character data that is typed between words. The decision about privacy is made based on
@@ -97,10 +103,10 @@ public class MainLogBuffer extends FixedLogBuffer {
* the screen orientation and other characteristics about the device can be uploaded without
* revealing much about the user.
*/
- public boolean isSafeToLog() {
+ public boolean isNGramSafe() {
// Check that we are not sampling too frequently. Having sampled recently might disclose
// too much of the user's intended meaning.
- if (mWordsUntilSafeToSample > 0) {
+ if (mNumWordsUntilSafeToSample > 0) {
return false;
}
if (mSuggest == null || !mSuggest.hasMainDictionary()) {
@@ -119,7 +125,8 @@ public class MainLogBuffer extends FixedLogBuffer {
// complete buffer contents in detail.
final LinkedList<LogUnit> logUnits = getLogUnits();
final int length = logUnits.size();
- for (int i = 0; i < length; i++) {
+ int wordsNeeded = N_GRAM_SIZE;
+ for (int i = 0; i < length && wordsNeeded > 0; i++) {
final LogUnit logUnit = logUnits.get(i);
final String word = logUnit.getWord();
if (word == null) {
@@ -142,10 +149,34 @@ public class MainLogBuffer extends FixedLogBuffer {
return true;
}
+ public boolean isNGramComplete() {
+ final LinkedList<LogUnit> logUnits = getLogUnits();
+ final int length = logUnits.size();
+ int wordsNeeded = N_GRAM_SIZE;
+ for (int i = 0; i < length && wordsNeeded > 0; i++) {
+ final LogUnit logUnit = logUnits.get(i);
+ final String word = logUnit.getWord();
+ if (word != null) {
+ wordsNeeded--;
+ }
+ }
+ return wordsNeeded == 0;
+ }
+
@Override
protected void onShiftOut(final LogUnit logUnit) {
if (mResearchLog != null) {
- mResearchLog.publish(logUnit, false /* isIncludingPrivateData */);
+ mResearchLog.publish(logUnit,
+ ResearchLogger.IS_LOGGING_EVERYTHING /* isIncludingPrivateData */);
+ }
+ if (logUnit.hasWord()) {
+ if (mNumWordsUntilSafeToSample > 0) {
+ mNumWordsUntilSafeToSample--;
+ Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample);
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "shiftedOut " + (logUnit.hasWord() ? logUnit.getWord() : ""));
}
}
}
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index a6b1b889f..5edb46e27 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -16,6 +16,7 @@
package com.android.inputmethod.research;
+import android.content.Context;
import android.util.JsonWriter;
import android.util.Log;
@@ -23,7 +24,7 @@ import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.BufferedWriter;
import java.io.File;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
@@ -50,6 +51,8 @@ public class ResearchLog {
/* package */ final ScheduledExecutorService mExecutor;
/* package */ final File mFile;
+ private final Context mContext;
+
private JsonWriter mJsonWriter = NULL_JSON_WRITER;
// true if at least one byte of data has been written out to the log file. This must be
// remembered because JsonWriter requires that calls matching calls to beginObject and
@@ -78,12 +81,13 @@ public class ResearchLog {
}
}
- public ResearchLog(final File outputFile) {
+ public ResearchLog(final File outputFile, Context context) {
if (outputFile == null) {
throw new IllegalArgumentException();
}
mExecutor = Executors.newSingleThreadScheduledExecutor();
mFile = outputFile;
+ mContext = context;
}
public synchronized void close(final Runnable onClosed) {
@@ -193,6 +197,9 @@ public class ResearchLog {
});
} catch (RejectedExecutionException e) {
// TODO: Add code to record loss of data, and report.
+ if (DEBUG) {
+ Log.d(TAG, "ResearchLog.publish() rejecting scheduled execution");
+ }
}
}
@@ -203,7 +210,9 @@ public class ResearchLog {
public JsonWriter getValidJsonWriterLocked() {
try {
if (mJsonWriter == NULL_JSON_WRITER) {
- mJsonWriter = new JsonWriter(new BufferedWriter(new FileWriter(mFile)));
+ final FileOutputStream fos =
+ mContext.openFileOutput(mFile.getName(), Context.MODE_PRIVATE);
+ mJsonWriter = new JsonWriter(new BufferedWriter(new OutputStreamWriter(fos)));
mJsonWriter.beginArray();
mHasWrittenData = true;
}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index b1484e696..f4249a045 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -85,7 +85,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final String TAG = ResearchLogger.class.getSimpleName();
private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
// Whether all n-grams should be logged. true will disclose private info.
- private static final boolean LOG_EVERYTHING = false
+ public static final boolean IS_LOGGING_EVERYTHING = false
&& ProductionFlag.IS_EXPERIMENTAL_DEBUG;
// Whether the TextView contents are logged at the end of the session. true will disclose
// private info.
@@ -105,7 +105,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final boolean IS_SHOWING_INDICATOR = true;
// Change the default indicator to something very visible. Currently two red vertical bars on
// either side of they keyboard.
- private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false || LOG_EVERYTHING;
+ private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false || IS_LOGGING_EVERYTHING;
public static final int FEEDBACK_WORD_BUFFER_SIZE = 5;
// constants related to specific log points
@@ -324,11 +324,22 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
sIsLogging = enableLogging;
}
+ private static int sLogFileCounter = 0;
+
private File createLogFile(File filesDir) {
final StringBuilder sb = new StringBuilder();
sb.append(FILENAME_PREFIX).append('-');
sb.append(mUUIDString).append('-');
- sb.append(TIMESTAMP_DATEFORMAT.format(new Date()));
+ sb.append(TIMESTAMP_DATEFORMAT.format(new Date())).append('-');
+ // Sometimes logFiles are created within milliseconds of each other. Append a counter to
+ // separate these.
+ if (sLogFileCounter < Integer.MAX_VALUE) {
+ sLogFileCounter++;
+ } else {
+ // Wrap the counter, in the unlikely event of overflow.
+ sLogFileCounter = 0;
+ }
+ sb.append(sLogFileCounter);
sb.append(FILENAME_SUFFIX);
return new File(filesDir, sb.toString());
}
@@ -374,12 +385,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
return;
}
if (mMainLogBuffer == null) {
- mMainResearchLog = new ResearchLog(createLogFile(mFilesDir));
+ mMainResearchLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME);
mMainLogBuffer = new MainLogBuffer(mMainResearchLog);
mMainLogBuffer.setSuggest(mSuggest);
}
if (mFeedbackLogBuffer == null) {
- mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
+ mFeedbackLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME);
// LogBuffer is one more than FEEDBACK_WORD_BUFFER_SIZE, because it must also hold
// the feedback LogUnit itself.
mFeedbackLogBuffer = new FixedLogBuffer(FEEDBACK_WORD_BUFFER_SIZE + 1);
@@ -390,11 +401,20 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (DEBUG) {
Log.d(TAG, "stop called");
}
+ // Commit mCurrentLogUnit before closing.
commitCurrentLogUnit();
if (mMainLogBuffer != null) {
- publishLogBuffer(mMainLogBuffer, mMainResearchLog,
- LOG_EVERYTHING /* isIncludingPrivateData */);
+ while (!mMainLogBuffer.isEmpty()) {
+ if ((mMainLogBuffer.isNGramSafe() || IS_LOGGING_EVERYTHING) &&
+ mMainResearchLog != null) {
+ publishLogBuffer(mMainLogBuffer, mMainResearchLog,
+ true /* isIncludingPrivateData */);
+ mMainLogBuffer.resetWordCounter();
+ } else {
+ mMainLogBuffer.shiftOutThroughFirstWord();
+ }
+ }
mMainResearchLog.close(null /* callback */);
mMainLogBuffer = null;
}
@@ -590,7 +610,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
uploadNow();
}
});
- mFeedbackLog = new ResearchLog(createLogFile(mFilesDir));
+ mFeedbackLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME);
}
public void uploadNow() {
@@ -676,11 +696,17 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
/**
* Buffer a research log event, flagging it as privacy-sensitive.
*/
- private synchronized void enqueueEvent(LogStatement logStatement, Object... values) {
+ private synchronized void enqueueEvent(final LogStatement logStatement,
+ final Object... values) {
+ enqueueEvent(mCurrentLogUnit, logStatement, values);
+ }
+
+ private synchronized void enqueueEvent(final LogUnit logUnit, final LogStatement logStatement,
+ final Object... values) {
assert values.length == logStatement.mKeys.length;
- if (isAllowedToLog()) {
+ if (isAllowedToLog() && logUnit != null) {
final long time = SystemClock.uptimeMillis();
- mCurrentLogUnit.addLogStatement(logStatement, time, values);
+ logUnit.addLogStatement(logStatement, time, values);
}
}
@@ -695,17 +721,70 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
if (!mCurrentLogUnit.isEmpty()) {
if (mMainLogBuffer != null) {
- mMainLogBuffer.shiftIn(mCurrentLogUnit);
- if ((mMainLogBuffer.isSafeToLog() || LOG_EVERYTHING) && mMainResearchLog != null) {
+ if ((mMainLogBuffer.isNGramSafe() || IS_LOGGING_EVERYTHING) &&
+ mMainLogBuffer.isNGramComplete() &&
+ mMainResearchLog != null) {
publishLogBuffer(mMainLogBuffer, mMainResearchLog,
true /* isIncludingPrivateData */);
mMainLogBuffer.resetWordCounter();
}
+ mMainLogBuffer.shiftIn(mCurrentLogUnit);
}
if (mFeedbackLogBuffer != null) {
mFeedbackLogBuffer.shiftIn(mCurrentLogUnit);
}
mCurrentLogUnit = new LogUnit();
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Warning: tried to commit empty log unit.");
+ }
+ }
+ }
+
+ private static final LogStatement LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT =
+ new LogStatement("UncommitCurrentLogUnit", false, false);
+ public void uncommitCurrentLogUnit(final String expectedWord,
+ final boolean dumpCurrentLogUnit) {
+ // The user has deleted this word and returned to the previous. Check that the word in the
+ // logUnit matches the expected word. If so, restore the last log unit committed to be the
+ // current logUnit. I.e., pull out the last LogUnit from all the LogBuffers, and make
+ // restore it to mCurrentLogUnit so the new edits are captured with the word. Optionally
+ // dump the contents of mCurrentLogUnit (useful if they contain deletions of the next word
+ // that should not be reported to protect user privacy)
+ //
+ // Note that we don't use mLastLogUnit here, because it only goes one word back and is only
+ // needed for reverts, which only happen one back.
+ if (mMainLogBuffer == null) {
+ return;
+ }
+ final LogUnit oldLogUnit = mMainLogBuffer.peekLastLogUnit();
+
+ // Check that expected word matches.
+ if (oldLogUnit != null) {
+ final String oldLogUnitWord = oldLogUnit.getWord();
+ if (!oldLogUnitWord.equals(expectedWord)) {
+ return;
+ }
+ }
+
+ // Uncommit, merging if necessary.
+ mMainLogBuffer.unshiftIn();
+ if (oldLogUnit != null && !dumpCurrentLogUnit) {
+ oldLogUnit.append(mCurrentLogUnit);
+ mSavedDownEventTime = Long.MAX_VALUE;
+ }
+ if (oldLogUnit == null) {
+ mCurrentLogUnit = new LogUnit();
+ } else {
+ mCurrentLogUnit = oldLogUnit;
+ }
+ if (mFeedbackLogBuffer != null) {
+ mFeedbackLogBuffer.unshiftIn();
+ }
+ enqueueEvent(LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT);
+ if (DEBUG) {
+ Log.d(TAG, "uncommitCurrentLogUnit (dump=" + dumpCurrentLogUnit + ") back to "
+ + (mCurrentLogUnit.hasWord() ? ": '" + mCurrentLogUnit.getWord() + "'" : ""));
}
}
@@ -721,12 +800,16 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
isIncludingPrivateData);
researchLog.publish(openingLogUnit, true /* isIncludingPrivateData */);
LogUnit logUnit;
- while ((logUnit = logBuffer.shiftOut()) != null) {
+ int numWordsToPublish = MainLogBuffer.N_GRAM_SIZE;
+ while ((logUnit = logBuffer.shiftOut()) != null && numWordsToPublish > 0) {
if (DEBUG) {
Log.d(TAG, "publishLogBuffer: " + (logUnit.hasWord() ? logUnit.getWord()
: "<wordless>"));
}
researchLog.publish(logUnit, isIncludingPrivateData);
+ if (logUnit.getWord() != null) {
+ numWordsToPublish--;
+ }
}
final LogUnit closingLogUnit = new LogUnit();
closingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_CLOSING,
@@ -751,24 +834,30 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
* After this operation completes, mCurrentLogUnit will hold any logStatements that happened
* after maxTime.
*/
- private static final LogStatement LOGSTATEMENT_COMMIT_RECORD_SPLIT_WORDS =
- new LogStatement("recordSplitWords", true, false);
- /* package for test */ void commitCurrentLogUnitAsWord(final String word, final long maxTime) {
+ /* package for test */ void commitCurrentLogUnitAsWord(final String word, final long maxTime,
+ final boolean isBatchMode) {
+ if (word == null) {
+ return;
+ }
final Dictionary dictionary = getDictionary();
- if (word != null && word.length() > 0 && hasLetters(word)) {
+ if (word.length() > 0 && hasLetters(word)) {
mCurrentLogUnit.setWord(word);
final boolean isDictionaryWord = dictionary != null
&& dictionary.isValidWord(word);
mStatistics.recordWordEntered(isDictionaryWord);
}
final LogUnit newLogUnit = mCurrentLogUnit.splitByTime(maxTime);
- enqueueCommitText(word);
+ enqueueCommitText(word, isBatchMode);
commitCurrentLogUnit();
mCurrentLogUnit = newLogUnit;
}
- public void onWordFinished(final String word) {
- commitCurrentLogUnitAsWord(word, mSavedDownEventTime);
+ private void setSavedDownEventTime(final long time) {
+ mSavedDownEventTime = time;
+ }
+
+ public void onWordFinished(final String word, final boolean isBatchMode) {
+ commitCurrentLogUnitAsWord(word, mSavedDownEventTime, isBatchMode);
mSavedDownEventTime = Long.MAX_VALUE;
}
@@ -863,7 +952,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
Integer.toHexString(editorInfo.inputType),
Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
- OUTPUT_FORMAT_VERSION, LOG_EVERYTHING,
+ OUTPUT_FORMAT_VERSION, IS_LOGGING_EVERYTHING,
ProductionFlag.IS_EXPERIMENTAL_DEBUG);
} catch (NameNotFoundException e) {
e.printStackTrace();
@@ -916,7 +1005,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (action == MotionEvent.ACTION_DOWN) {
// Subtract 1 from eventTime so the down event is included in the later
// LogUnit, not the earlier (the test is for inequality).
- researchLogger.mSavedDownEventTime = eventTime - 1;
+ researchLogger.setSavedDownEventTime(eventTime - 1);
}
}
}
@@ -1060,9 +1149,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
*
* SystemResponse: Raw text is added to the TextView.
*/
- public static void latinIME_onTextInput(final String text) {
+ public static void latinIME_onTextInput(final String text, final boolean isBatchMode) {
final ResearchLogger researchLogger = getInstance();
- researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE);
+ researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE, isBatchMode);
}
/**
@@ -1074,15 +1163,15 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index",
"suggestion", "x", "y");
public static void latinIME_pickSuggestionManually(final String replacedWord,
- final int index, final String suggestion) {
+ final int index, final String suggestion, final boolean isBatchMode) {
final String scrubbedWord = scrubDigitsFromString(suggestion);
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY,
scrubDigitsFromString(replacedWord), index,
suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
Constants.SUGGESTION_STRIP_COORDINATE);
- researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE);
- researchLogger.mStatistics.recordManualSuggestion();
+ researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
+ researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis());
}
/**
@@ -1092,20 +1181,21 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
*/
private static final LogStatement LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION =
new LogStatement("LatinIMEPunctuationSuggestion", false, false, "index", "suggestion",
- "x", "y");
- public static void latinIME_punctuationSuggestion(final int index, final String suggestion) {
+ "x", "y", "isPrediction");
+ public static void latinIME_punctuationSuggestion(final int index, final String suggestion,
+ final boolean isBatchMode, final boolean isPrediction) {
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION, index, suggestion,
- Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE);
- researchLogger.commitCurrentLogUnitAsWord(suggestion, Long.MAX_VALUE);
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+ isPrediction);
+ researchLogger.commitCurrentLogUnitAsWord(suggestion, Long.MAX_VALUE, isBatchMode);
}
/**
* Log a call to LatinIME.sendKeyCodePoint().
*
- * SystemResponse: The IME is simulating a hardware keypress. This happens for numbers; other
- * input typically goes through RichInputConnection.setComposingText() and
- * RichInputConnection.commitText().
+ * SystemResponse: The IME is inserting text into the TextView for numbers, fixed strings, or
+ * some other unusual mechanism.
*/
private static final LogStatement LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT =
new LogStatement("LatinIMESendKeyCodePoint", true, false, "code");
@@ -1119,17 +1209,45 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
/**
+ * Log a call to LatinIME.promotePhantomSpace().
+ *
+ * SystemResponse: The IME is inserting a real space in place of a phantom space.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE =
+ new LogStatement("LatinIMEPromotPhantomSpace", false, false);
+ public static void latinIME_promotePhantomSpace() {
+ final ResearchLogger researchLogger = getInstance();
+ final LogUnit logUnit;
+ if (researchLogger.mMainLogBuffer == null) {
+ logUnit = researchLogger.mCurrentLogUnit;
+ } else {
+ logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
+ }
+ researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE);
+ }
+
+ /**
* Log a call to LatinIME.swapSwapperAndSpace().
*
* SystemResponse: A symbol has been swapped with a space character. E.g. punctuation may swap
* if a soft space is inserted after a word.
*/
private static final LogStatement LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE =
- new LogStatement("LatinIMESwapSwapperAndSpace", false, false);
- public static void latinIME_swapSwapperAndSpace(final String text) {
+ new LogStatement("LatinIMESwapSwapperAndSpace", false, false, "originalCharacters",
+ "charactersAfterSwap");
+ public static void latinIME_swapSwapperAndSpace(final CharSequence originalCharacters,
+ final String charactersAfterSwap) {
final ResearchLogger researchLogger = getInstance();
- researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE);
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE);
+ final LogUnit logUnit;
+ if (researchLogger.mMainLogBuffer == null) {
+ logUnit = null;
+ } else {
+ logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
+ }
+ if (logUnit != null) {
+ researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE,
+ originalCharacters, charactersAfterSwap);
+ }
}
/**
@@ -1137,9 +1255,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
*
* SystemResponse: Two spaces have been replaced by period space.
*/
- public static void latinIME_maybeDoubleSpacePeriod(final String text) {
+ public static void latinIME_maybeDoubleSpacePeriod(final String text,
+ final boolean isBatchMode) {
final ResearchLogger researchLogger = getInstance();
- researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE);
+ researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE, isBatchMode);
}
/**
@@ -1169,8 +1288,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
public static void mainKeyboardView_setKeyboard(final Keyboard keyboard) {
final KeyboardId kid = keyboard.mId;
final boolean isPasswordView = kid.passwordInput();
- getInstance().setIsPasswordView(isPasswordView);
- getInstance().enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD,
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.setIsPasswordView(isPasswordView);
+ researchLogger.enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD,
KeyboardId.elementIdToName(kid.mElementId),
kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
kid.mOrientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(),
@@ -1189,14 +1309,28 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
*/
private static final LogStatement LOGSTATEMENT_LATINIME_REVERTCOMMIT =
new LogStatement("LatinIMERevertCommit", true, false, "committedWord",
- "originallyTypedWord");
+ "originallyTypedWord", "separatorString");
public static void latinIME_revertCommit(final String committedWord,
- final String originallyTypedWord) {
+ final String originallyTypedWord, final boolean isBatchMode,
+ final String separatorString) {
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_REVERTCOMMIT, committedWord,
- originallyTypedWord);
- researchLogger.mStatistics.recordRevertCommit();
- researchLogger.commitCurrentLogUnitAsWord(originallyTypedWord, Long.MAX_VALUE);
+ // TODO: Verify that mCurrentLogUnit has been restored and contains the reverted word.
+ final LogUnit logUnit;
+ if (researchLogger.mMainLogBuffer == null) {
+ logUnit = null;
+ } else {
+ logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
+ }
+ if (originallyTypedWord.length() > 0 && hasLetters(originallyTypedWord)) {
+ if (logUnit != null) {
+ logUnit.setWord(originallyTypedWord);
+ }
+ }
+ researchLogger.enqueueEvent(logUnit != null ? logUnit : researchLogger.mCurrentLogUnit,
+ LOGSTATEMENT_LATINIME_REVERTCOMMIT, committedWord, originallyTypedWord,
+ separatorString);
+ researchLogger.mStatistics.recordRevertCommit(SystemClock.uptimeMillis());
+ researchLogger.commitCurrentLogUnitAsWord(originallyTypedWord, Long.MAX_VALUE, isBatchMode);
}
/**
@@ -1295,9 +1429,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
*
* SystemResponse: The IME has reverted ". ", which had previously replaced two typed spaces.
*/
- public static void richInputConnection_revertDoubleSpacePeriod(final String doubleSpace) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.commitCurrentLogUnitAsWord(doubleSpace, Long.MAX_VALUE);
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD =
+ new LogStatement("RichInputConnectionRevertDoubleSpacePeriod", false, false);
+ public static void richInputConnection_revertDoubleSpacePeriod() {
+ getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD);
}
/**
@@ -1305,9 +1440,10 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
*
* SystemResponse: The IME has reverted a punctuation swap.
*/
- public static void richInputConnection_revertSwapPunctuation(final String text) {
- final ResearchLogger researchLogger = getInstance();
- researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE);
+ private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTSWAPPUNCTUATION =
+ new LogStatement("RichInputConnectionRevertSwapPunctuation", false, false);
+ public static void richInputConnection_revertSwapPunctuation() {
+ getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTSWAPPUNCTUATION);
}
/**
@@ -1317,16 +1453,24 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
* text input to another word that the user more likely desired to type.
*/
private static final LogStatement LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION =
- new LogStatement("LatinIMECommitCurrentAutoCorrection", true, false, "typedWord",
+ new LogStatement("LatinIMECommitCurrentAutoCorrection", true, true, "typedWord",
"autoCorrection", "separatorString");
public static void latinIme_commitCurrentAutoCorrection(final String typedWord,
- final String autoCorrection, final String separatorString) {
+ final String autoCorrection, final String separatorString, final boolean isBatchMode) {
final String scrubbedTypedWord = scrubDigitsFromString(typedWord);
final String scrubbedAutoCorrection = scrubDigitsFromString(autoCorrection);
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION,
+ researchLogger.commitCurrentLogUnitAsWord(scrubbedAutoCorrection, Long.MAX_VALUE,
+ isBatchMode);
+
+ // Add the autocorrection logStatement at the end of the logUnit for the committed word.
+ // We have to do this after calling commitCurrentLogUnitAsWord, because it may split the
+ // current logUnit, and then we have to peek to get the logUnit reference back.
+ final LogUnit logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
+ // TODO: Add test to confirm that the commitCurrentAutoCorrection log statement should
+ // always be added to logUnit (if non-null) and not mCurrentLogUnit.
+ researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION,
scrubbedTypedWord, scrubbedAutoCorrection, separatorString);
- researchLogger.commitCurrentLogUnitAsWord(scrubbedAutoCorrection, Long.MAX_VALUE);
}
private boolean isExpectingCommitText = false;
@@ -1340,13 +1484,13 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// add invocations.
private static final LogStatement LOGSTATEMENT_LATINIME_COMMIT_PARTIAL_TEXT =
new LogStatement("LatinIMECommitPartialText", true, false, "newCursorPosition");
- public static void latinIME_commitPartialText(final CharSequence committedWord,
- final long lastTimestampOfWordData) {
+ public static void latinIME_commitPartialText(final String committedWord,
+ final long lastTimestampOfWordData, final boolean isBatchMode) {
final ResearchLogger researchLogger = getInstance();
- final String scrubbedWord = scrubDigitsFromString(committedWord.toString());
+ final String scrubbedWord = scrubDigitsFromString(committedWord);
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_COMMIT_PARTIAL_TEXT);
- researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, lastTimestampOfWordData);
- researchLogger.mStatistics.recordSplitWords();
+ researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, lastTimestampOfWordData,
+ isBatchMode);
}
/**
@@ -1357,14 +1501,14 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
*/
private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT =
new LogStatement("RichInputConnectionCommitText", true, false, "newCursorPosition");
- public static void richInputConnection_commitText(final CharSequence committedWord,
- final int newCursorPosition) {
+ public static void richInputConnection_commitText(final String committedWord,
+ final int newCursorPosition, final boolean isBatchMode) {
final ResearchLogger researchLogger = getInstance();
- final String scrubbedWord = scrubDigitsFromString(committedWord.toString());
+ final String scrubbedWord = scrubDigitsFromString(committedWord);
if (!researchLogger.isExpectingCommitText) {
researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT,
newCursorPosition);
- researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE);
+ researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
}
researchLogger.isExpectingCommitText = false;
}
@@ -1373,9 +1517,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
* Shared event for logging committed text.
*/
private static final LogStatement LOGSTATEMENT_COMMITTEXT =
- new LogStatement("CommitText", true, false, "committedText");
- private void enqueueCommitText(final CharSequence word) {
- enqueueEvent(LOGSTATEMENT_COMMITTEXT, word);
+ new LogStatement("CommitText", true, false, "committedText", "isBatchMode");
+ private void enqueueCommitText(final String word, final boolean isBatchMode) {
+ enqueueEvent(LOGSTATEMENT_COMMITTEXT, word, isBatchMode);
}
/**
@@ -1515,20 +1659,62 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
final ResearchLogger researchLogger = getInstance();
researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText,
enteredWordPos);
- researchLogger.mStatistics.recordGestureInput(enteredText.length());
+ researchLogger.mStatistics.recordGestureInput(enteredText.length(),
+ SystemClock.uptimeMillis());
}
/**
- * Log a call to LatinIME.handleBackspace().
+ * Log a call to LatinIME.handleBackspace() that is not a batch delete.
+ *
+ * UserInput: The user is deleting one or more characters by hitting the backspace key once.
+ * The covers single character deletes as well as deleting selections.
+ */
+ private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE =
+ new LogStatement("LatinIMEHandleBackspace", true, false, "numCharacters");
+ public static void latinIME_handleBackspace(final int numCharacters) {
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE, numCharacters);
+ }
+
+ /**
+ * Log a call to LatinIME.handleBackspace() that is a batch delete.
*
* UserInput: The user is deleting a gestured word by hitting the backspace key once.
*/
private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH =
- new LogStatement("LatinIMEHandleBackspaceBatch", true, false, "deletedText");
- public static void latinIME_handleBackspace_batch(final CharSequence deletedText) {
+ new LogStatement("LatinIMEHandleBackspaceBatch", true, false, "deletedText",
+ "numCharacters");
+ public static void latinIME_handleBackspace_batch(final CharSequence deletedText,
+ final int numCharacters) {
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH, deletedText,
+ numCharacters);
+ researchLogger.mStatistics.recordGestureDelete(deletedText.length(),
+ SystemClock.uptimeMillis());
+ }
+
+ /**
+ * Log a long interval between user operation.
+ *
+ * UserInput: The user has not done anything for a while.
+ */
+ private static final LogStatement LOGSTATEMENT_ONUSERPAUSE = new LogStatement("OnUserPause",
+ false, false, "intervalInMs");
+ public static void onUserPause(final long interval) {
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.enqueueEvent(LOGSTATEMENT_ONUSERPAUSE, interval);
+ }
+
+ /**
+ * Record the current time in case the LogUnit is later split.
+ *
+ * If the current logUnitis split, then tapping, motion events, etc. before this time should
+ * be assigned to one LogUnit, and events after this time should go into the following LogUnit.
+ */
+ public static void recordTimeForLogUnitSplit() {
final ResearchLogger researchLogger = getInstance();
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH, deletedText);
- researchLogger.mStatistics.recordGestureDelete();
+ researchLogger.setSavedDownEventTime(SystemClock.uptimeMillis());
+ researchLogger.mSavedDownEventTime = Long.MAX_VALUE;
}
/**
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index e9c02c919..a9202651e 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -134,17 +134,9 @@ public class Statistics {
if (DEBUG) {
Log.d(TAG, "recordChar() called");
}
- final long delta = time - mLastTapTime;
if (codePoint == Constants.CODE_DELETE) {
mDeleteKeyCount++;
- if (delta < MIN_DELETION_INTERMISSION) {
- if (mIsLastKeyDeleteKey) {
- mDuringRepeatedDeleteKeysCounter.add(delta);
- } else {
- mBeforeDeleteKeyCounter.add(delta);
- }
- }
- mIsLastKeyDeleteKey = true;
+ recordUserAction(time, true /* isDeletion */);
} else {
mCharCount++;
if (Character.isDigit(codePoint)) {
@@ -156,14 +148,8 @@ public class Statistics {
if (Character.isSpaceChar(codePoint)) {
mSpaceCount++;
}
- if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
- mAfterDeleteKeyCounter.add(delta);
- } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
- mKeyCounter.add(delta);
- }
- mIsLastKeyDeleteKey = false;
+ recordUserAction(time, false /* isDeletion */);
}
- mLastTapTime = time;
}
public void recordWordEntered(final boolean isDictionaryWord) {
@@ -177,9 +163,10 @@ public class Statistics {
mSplitWordsCount++;
}
- public void recordGestureInput(final int numCharsEntered) {
+ public void recordGestureInput(final int numCharsEntered, final long time) {
mGesturesInputCount++;
mGesturesCharsCount += numCharsEntered;
+ recordUserAction(time, false /* isDeletion */);
}
public void setIsEmptyUponStarting(final boolean isEmpty) {
@@ -187,14 +174,43 @@ public class Statistics {
mIsEmptinessStateKnown = true;
}
- public void recordGestureDelete() {
+ public void recordGestureDelete(final int length, final long time) {
mGesturesDeletedCount++;
+ recordUserAction(time, true /* isDeletion */);
}
- public void recordManualSuggestion() {
+
+ public void recordManualSuggestion(final long time) {
mManualSuggestionsCount++;
+ recordUserAction(time, false /* isDeletion */);
}
- public void recordRevertCommit() {
+ public void recordRevertCommit(final long time) {
mRevertCommitsCount++;
+ recordUserAction(time, true /* isDeletion */);
+ }
+
+ private void recordUserAction(final long time, final boolean isDeletion) {
+ final long delta = time - mLastTapTime;
+ if (isDeletion) {
+ if (delta < MIN_DELETION_INTERMISSION) {
+ if (mIsLastKeyDeleteKey) {
+ mDuringRepeatedDeleteKeysCounter.add(delta);
+ } else {
+ mBeforeDeleteKeyCounter.add(delta);
+ }
+ } else {
+ ResearchLogger.onUserPause(delta);
+ }
+ } else {
+ if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) {
+ mAfterDeleteKeyCounter.add(delta);
+ } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) {
+ mKeyCounter.add(delta);
+ } else {
+ ResearchLogger.onUserPause(delta);
+ }
+ }
+ mIsLastKeyDeleteKey = isDeletion;
+ mLastTapTime = time;
}
}