diff --git a/java/res/values-sw600dp/config.xml b/java/res/values-sw600dp/config.xml
index 2f35d9ae5..745d3b995 100644
--- a/java/res/values-sw600dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -34,6 +34,7 @@
+ 40.0dp
false
diff --git a/java/res/xml-sw600dp/rowkeys_symbols3.xml b/java/res/xml-sw600dp/rowkeys_symbols3.xml
index 4bfa0d730..30fba3812 100644
--- a/java/res/xml-sw600dp/rowkeys_symbols3.xml
+++ b/java/res/xml-sw600dp/rowkeys_symbols3.xml
@@ -49,7 +49,7 @@
= MINIMUM_LETTER_CODE;
+ return code >= CODE_SPACE;
}
public static class Params {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index dc27769ab..1f3ee7680 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -16,6 +16,9 @@
package com.android.inputmethod.keyboard;
+import com.android.inputmethod.latin.InputPointers;
+import com.android.inputmethod.latin.SuggestedWords;
+
public interface KeyboardActionListener {
/**
@@ -64,13 +67,18 @@ public interface KeyboardActionListener {
*/
public void onTextInput(CharSequence text);
- // TODO: Should move this method to some more appropriate interface.
/**
* Called when user started batch input.
*/
public void onStartBatchInput();
- // TODO: Should move this method to some more appropriate interface.
+ /**
+ * Sends the batch input points data to get updated suggestions
+ * @param batchPointers the batch input points representing the user input
+ * @return updated suggestions that reflects the user input
+ */
+ public SuggestedWords onUpdateBatchInput(InputPointers batchPointers);
+
/**
* Sends a sequence of characters to the listener as batch input.
*
@@ -101,6 +109,8 @@ public interface KeyboardActionListener {
@Override
public void onStartBatchInput() {}
@Override
+ public SuggestedWords onUpdateBatchInput(InputPointers batchPointers) { return null; }
+ @Override
public void onEndBatchInput(CharSequence text) {}
@Override
public void onCancelInput() {}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 1ae0020a4..733d3b09b 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -22,6 +22,7 @@ import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
+import com.android.inputmethod.keyboard.internal.GestureTracker;
import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.ResearchLogger;
@@ -161,6 +162,9 @@ public class PointerTracker {
private static final KeyboardActionListener EMPTY_LISTENER =
new KeyboardActionListener.Adapter();
+ // Gesture tracker singleton instance
+ private static final GestureTracker sGestureTracker = GestureTracker.getInstance();
+
public static void init(boolean hasDistinctMultitouch,
boolean needsPhantomSuddenMoveEventHack) {
if (hasDistinctMultitouch) {
@@ -199,6 +203,7 @@ public class PointerTracker {
for (final PointerTracker tracker : sTrackers) {
tracker.mListener = listener;
}
+ GestureTracker.init(listener);
}
public static void setKeyDetector(KeyDetector keyDetector) {
@@ -207,6 +212,7 @@ public class PointerTracker {
// Mark that keyboard layout has been changed.
tracker.mKeyboardLayoutHasBeenChanged = true;
}
+ sGestureTracker.setKeyboard(keyDetector.getKeyboard());
}
public static void dismissAllKeyPreviews() {
@@ -233,6 +239,9 @@ public class PointerTracker {
// Returns true if keyboard has been changed by this callback.
private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) {
+ if (sGestureTracker.isInGesture()) {
+ return false;
+ }
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
if (DEBUG_LISTENER) {
Log.d(TAG, "onPress : " + KeyDetector.printableCode(key)
@@ -286,6 +295,9 @@ public class PointerTracker {
// Note that we need primaryCode argument because the keyboard may in shifted state and the
// primaryCode is different from {@link Key#mCode}.
private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
+ if (sGestureTracker.isInGesture()) {
+ return;
+ }
final boolean ignoreModifierKey = mIgnoreModifierKey && key.isModifier();
if (DEBUG_LISTENER) {
Log.d(TAG, "onRelease : " + Keyboard.printableCode(primaryCode)
@@ -386,7 +398,7 @@ public class PointerTracker {
return;
}
- if (!key.noKeyPreview()) {
+ if (!key.noKeyPreview() && !sGestureTracker.isInGesture()) {
mDrawingProxy.showKeyPreview(this);
}
updatePressKeyGraphics(key);
@@ -504,8 +516,8 @@ public class PointerTracker {
}
final PointerTrackerQueue queue = sPointerTrackerQueue;
+ final Key key = getKeyOn(x, y);
if (queue != null) {
- final Key key = getKeyOn(x, y);
if (key != null && key.isModifier()) {
// Before processing a down event of modifier key, all pointers already being
// tracked should be released.
@@ -514,6 +526,9 @@ public class PointerTracker {
queue.add(this);
}
onDownEventInternal(x, y, eventTime);
+ if (queue != null && queue.size() == 1) {
+ sGestureTracker.onDownEvent(this, x, y, eventTime, key);
+ }
}
private void onDownEventInternal(int x, int y, long eventTime) {
@@ -554,10 +569,34 @@ public class PointerTracker {
if (mKeyAlreadyProcessed)
return;
+ if (me != null) {
+ // Add historical points to gesture path.
+ final int pointerIndex = me.findPointerIndex(mPointerId);
+ final int historicalSize = me.getHistorySize();
+ for (int h = 0; h < historicalSize; h++) {
+ final int historicalX = (int)me.getHistoricalX(pointerIndex, h);
+ final int historicalY = (int)me.getHistoricalY(pointerIndex, h);
+ final long historicalTime = me.getHistoricalEventTime(h);
+ sGestureTracker.onMoveEvent(this, historicalX, historicalY, historicalTime,
+ true /* isHistorical */, null);
+ }
+ }
+
final int lastX = mLastX;
final int lastY = mLastY;
final Key oldKey = mCurrentKey;
Key key = onMoveKey(x, y);
+
+ // Register move event on gesture tracker.
+ sGestureTracker.onMoveEvent(this, x, y, eventTime, false, key);
+ if (sGestureTracker.isInGesture()) {
+ mIgnoreModifierKey = true;
+ mTimerProxy.cancelLongPressTimer();
+ mIsInSlidingKeyInput = true;
+ mCurrentKey = null;
+ setReleasedKeyGraphics(oldKey);
+ }
+
if (key != null) {
if (oldKey == null) {
// The pointer has been slid in to the new key, but the finger was not on any keys.
@@ -607,7 +646,7 @@ public class PointerTracker {
if (ProductionFlag.IS_EXPERIMENTAL) {
ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY);
}
- onUpEventInternal();
+ onUpEventInternal(x, y, eventTime);
onDownEventInternal(x, y, eventTime);
} else {
// HACK: If there are currently multiple touches, register the key even if
@@ -617,7 +656,7 @@ public class PointerTracker {
// this hack.
if (me != null && me.getPointerCount() > 1
&& !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) {
- onUpEventInternal();
+ onUpEventInternal(x, y, eventTime);
}
mKeyAlreadyProcessed = true;
setReleasedKeyGraphics(oldKey);
@@ -647,16 +686,18 @@ public class PointerTracker {
final PointerTrackerQueue queue = sPointerTrackerQueue;
if (queue != null) {
- if (mCurrentKey != null && mCurrentKey.isModifier()) {
- // Before processing an up event of modifier key, all pointers already being
- // tracked should be released.
- queue.releaseAllPointersExcept(this, eventTime);
- } else {
- queue.releaseAllPointersOlderThan(this, eventTime);
+ if (!sGestureTracker.isInGesture()) {
+ if (mCurrentKey != null && mCurrentKey.isModifier()) {
+ // Before processing an up event of modifier key, all pointers already being
+ // tracked should be released.
+ queue.releaseAllPointersExcept(this, eventTime);
+ } else {
+ queue.releaseAllPointersOlderThan(this, eventTime);
+ }
}
queue.remove(this);
}
- onUpEventInternal();
+ onUpEventInternal(x, y, eventTime);
}
// Let this pointer tracker know that one of newer-than-this pointer trackers got an up event.
@@ -665,11 +706,11 @@ public class PointerTracker {
public void onPhantomUpEvent(int x, int y, long eventTime) {
if (DEBUG_EVENT)
printTouchEvent("onPhntEvent:", x, y, eventTime);
- onUpEventInternal();
+ onUpEventInternal(x, y, eventTime);
mKeyAlreadyProcessed = true;
}
- private void onUpEventInternal() {
+ private void onUpEventInternal(int x, int y, long eventTime) {
mTimerProxy.cancelKeyTimers();
mIsInSlidingKeyInput = false;
// Release the last pressed key.
@@ -678,6 +719,24 @@ public class PointerTracker {
mDrawingProxy.dismissMoreKeysPanel();
mIsShowingMoreKeysPanel = false;
}
+
+ if (sGestureTracker.isInGesture()) {
+ // Register up event on gesture tracker.
+ sGestureTracker.onUpEvent(this, x, y, eventTime);
+ if (!sPointerTrackerQueue.isAnyInSlidingKeyInput()) {
+ // TODO: Calls to beginBatchInput() is missing in this class. Reorganize the code.
+ sGestureTracker.endBatchInput();
+ }
+ if (mCurrentKey != null) {
+ callListenerOnRelease(mCurrentKey, mCurrentKey.mCode, true);
+ }
+ mCurrentKey = null;
+ return;
+ } else {
+ // TODO: Calls to beginBatchInput() is missing in this class. Reorganize the code.
+ sGestureTracker.endBatchInput();
+ }
+
if (mKeyAlreadyProcessed)
return;
if (mCurrentKey != null && !mCurrentKey.isRepeatable()) {
@@ -689,6 +748,8 @@ public class PointerTracker {
onLongPressed();
onDownEvent(x, y, SystemClock.uptimeMillis(), handler);
mIsShowingMoreKeysPanel = true;
+ // TODO: Calls to beginBatchInput() is missing in this class. Reorganize the code.
+ sGestureTracker.abortBatchInput();
}
public void onLongPressed() {
@@ -723,7 +784,7 @@ public class PointerTracker {
}
private void startRepeatKey(Key key) {
- if (key != null && key.isRepeatable()) {
+ if (key != null && key.isRepeatable() && !sGestureTracker.isInGesture()) {
onRegisterKey(key);
mTimerProxy.startKeyRepeatTimer(this);
}
@@ -753,7 +814,7 @@ public class PointerTracker {
}
private void startLongPressTimer(Key key) {
- if (key != null && key.isLongPressEnabled()) {
+ if (key != null && key.isLongPressEnabled() && !sGestureTracker.isInGesture()) {
mTimerProxy.startLongPressTimer(this);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java
new file mode 100644
index 000000000..dfd697a7a
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTracker.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardActionListener;
+import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.latin.InputPointers;
+import com.android.inputmethod.latin.SuggestedWords;
+
+// TODO: Remove this class by consolidating with PointerTracker
+public class GestureTracker {
+ private static final String TAG = GestureTracker.class.getSimpleName();
+ private static final boolean DEBUG_LISTENER = false;
+
+ // TODO: There should be an option to turn on/off the gesture input.
+ private static final boolean GESTURE_ON = true;
+
+ private static final GestureTracker sInstance = new GestureTracker();
+
+ private static final int MIN_RECOGNITION_TIME = 100;
+ private static final int MIN_GESTURE_DURATION = 200;
+
+ private static final float GESTURE_RECOG_SPEED_THRESHOLD = 0.4f;
+ private static final float SQUARED_GESTURE_RECOG_SPEED_THRESHOLD =
+ GESTURE_RECOG_SPEED_THRESHOLD * GESTURE_RECOG_SPEED_THRESHOLD;
+ private static final float GESTURE_RECOG_CURVATURE_THRESHOLD = (float) (Math.PI / 4);
+
+ private boolean mIsAlphabetKeyboard;
+ private boolean mIsPossibleGesture = false;
+ private boolean mInGesture = false;
+
+ private KeyboardActionListener mListener;
+ private SuggestedWords mSuggestions;
+
+ private final SparseArray mGestureStrokes = new SparseArray();
+
+ private int mLastRecognitionPointSize = 0;
+ private long mLastRecognitionTime = 0;
+
+ public static void init(KeyboardActionListener listner) {
+ sInstance.mListener = listner;
+ }
+
+ public static GestureTracker getInstance() {
+ return sInstance;
+ }
+
+ private GestureTracker() {
+ }
+
+ public void setKeyboard(Keyboard keyboard) {
+ mIsAlphabetKeyboard = keyboard.mId.isAlphabetKeyboard();
+ GestureStroke.setGestureSampleLength(keyboard.mMostCommonKeyWidth / 2,
+ keyboard.mMostCommonKeyHeight / 6);
+ }
+
+ private void startBatchInput() {
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onStartBatchInput");
+ }
+ mInGesture = true;
+ mListener.onStartBatchInput();
+ mSuggestions = null;
+ }
+
+ // TODO: The corresponding startBatchInput() is a private method. Reorganize the code.
+ public void endBatchInput() {
+ if (isInGesture() && mSuggestions != null && mSuggestions.size() > 0) {
+ final CharSequence text = mSuggestions.getWord(0);
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onEndBatchInput: text=" + text);
+ }
+ mListener.onEndBatchInput(text);
+ }
+ mInGesture = false;
+ clearBatchInputPoints();
+ }
+
+ public void abortBatchInput() {
+ mIsPossibleGesture = false;
+ mInGesture = false;
+ }
+
+ public boolean isInGesture() {
+ return mInGesture;
+ }
+
+ public void onDownEvent(PointerTracker tracker, int x, int y, long eventTime, Key key) {
+ mIsPossibleGesture = false;
+ // A gesture should start only from the letter key.
+ if (GESTURE_ON && mIsAlphabetKeyboard && key != null && Keyboard.isLetterCode(key.mCode)) {
+ mIsPossibleGesture = true;
+ addPointToStroke(x, y, 0, tracker.mPointerId, false);
+ }
+ }
+
+ public void onMoveEvent(PointerTracker tracker, int x, int y, long eventTime,
+ boolean isHistorical, Key key) {
+ final int gestureTime = (int)(eventTime - tracker.getDownTime());
+ if (GESTURE_ON && mIsPossibleGesture) {
+ final GestureStroke stroke = addPointToStroke(x, y, gestureTime, tracker.mPointerId,
+ isHistorical);
+ if (!isInGesture() && stroke.isStartOfAGesture(gestureTime)) {
+ startBatchInput();
+ }
+ }
+
+ if (key != null && isInGesture()) {
+ final InputPointers batchPoints = getIncrementalBatchPoints();
+ if (updateBatchInputRecognitionState(eventTime, batchPoints.getPointerSize())) {
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
+ }
+ mSuggestions = mListener.onUpdateBatchInput(batchPoints);
+ }
+ }
+ }
+
+ public void onUpEvent(PointerTracker tracker, int x, int y, long eventTime) {
+ if (isInGesture()) {
+ final InputPointers batchPoints = getAllBatchPoints();
+ if (DEBUG_LISTENER) {
+ Log.d(TAG, "onUpdateBatchInput: batchPoints=" + batchPoints.getPointerSize());
+ }
+ mSuggestions = mListener.onUpdateBatchInput(batchPoints);
+ }
+ }
+
+ private GestureStroke addPointToStroke(int x, int y, int time, int pointerId,
+ boolean isHistorical) {
+ GestureStroke stroke = mGestureStrokes.get(pointerId);
+ if (stroke == null) {
+ stroke = new GestureStroke(pointerId);
+ mGestureStrokes.put(pointerId, stroke);
+ }
+ stroke.addPoint(x, y, time, isHistorical);
+ return stroke;
+ }
+
+ // The working and return object of the following methods, {@link #getIncrementalBatchPoints()}
+ // and {@link #getAllBatchPoints()}.
+ private final InputPointers mAggregatedPointers = new InputPointers();
+
+ private InputPointers getIncrementalBatchPoints() {
+ final InputPointers pointers = mAggregatedPointers;
+ pointers.reset();
+ final int strokeSize = mGestureStrokes.size();
+ for (int index = 0; index < strokeSize; index++) {
+ final GestureStroke stroke = mGestureStrokes.valueAt(index);
+ stroke.appendIncrementalBatchPoints(pointers);
+ }
+ return pointers;
+ }
+
+ private InputPointers getAllBatchPoints() {
+ final InputPointers pointers = mAggregatedPointers;
+ pointers.reset();
+ final int strokeSize = mGestureStrokes.size();
+ for (int index = 0; index < strokeSize; index++) {
+ final GestureStroke stroke = mGestureStrokes.valueAt(index);
+ stroke.appendAllBatchPoints(pointers);
+ }
+ return pointers;
+ }
+
+ private void clearBatchInputPoints() {
+ final int strokeSize = mGestureStrokes.size();
+ for (int index = 0; index < strokeSize; index++) {
+ final GestureStroke stroke = mGestureStrokes.valueAt(index);
+ stroke.reset();
+ }
+ mLastRecognitionPointSize = 0;
+ mLastRecognitionTime = 0;
+ }
+
+ private boolean updateBatchInputRecognitionState(long eventTime, int size) {
+ if (size > mLastRecognitionPointSize
+ && eventTime > mLastRecognitionTime + MIN_RECOGNITION_TIME) {
+ mLastRecognitionPointSize = size;
+ mLastRecognitionTime = eventTime;
+ return true;
+ }
+ return false;
+ }
+
+ private static class GestureStroke {
+ private final int mPointerId;
+ private final InputPointers mInputPointers = new InputPointers();
+ private float mLength;
+ private float mAngle;
+ private int mIncrementalRecognitionPoint;
+ private boolean mHasSharpCorner;
+ private long mLastPointTime;
+ private int mLastPointX;
+ private int mLastPointY;
+
+ private static int sMinGestureLength;
+ private static int sSquaredGestureSampleLength;
+
+ private static final float DOUBLE_PI = (float)(2 * Math.PI);
+
+ public static void setGestureSampleLength(final int minGestureLength,
+ final int sampleLength) {
+ sMinGestureLength = minGestureLength;
+ sSquaredGestureSampleLength = sampleLength * sampleLength;
+ }
+
+ public GestureStroke(int pointerId) {
+ mPointerId = pointerId;
+ reset();
+ }
+
+ public boolean isStartOfAGesture(int downDuration) {
+ return downDuration > MIN_GESTURE_DURATION / 2 && mLength > sMinGestureLength / 2;
+ }
+
+ public void reset() {
+ mLength = 0;
+ mAngle = 0;
+ mIncrementalRecognitionPoint = 0;
+ mHasSharpCorner = false;
+ mLastPointTime = 0;
+ mInputPointers.reset();
+ }
+
+ private void updateLastPoint(final int x, final int y, final int time) {
+ mLastPointTime = time;
+ mLastPointX = x;
+ mLastPointY = y;
+ }
+
+ public void addPoint(final int x, final int y, final int time, final boolean isHistorical) {
+ final int size = mInputPointers.getPointerSize();
+ if (size == 0) {
+ mInputPointers.addPointer(x, y, mPointerId, time);
+ if (!isHistorical) {
+ updateLastPoint(x, y, time);
+ }
+ return;
+ }
+
+ final int[] xCoords = mInputPointers.getXCoordinates();
+ final int[] yCoords = mInputPointers.getYCoordinates();
+ final int lastX = xCoords[size - 1];
+ final int lastY = yCoords[size - 1];
+ final float dist = squaredDistance(lastX, lastY, x, y);
+ if (dist > sSquaredGestureSampleLength) {
+ mInputPointers.addPointer(x, y, mPointerId, time);
+ mLength += dist;
+ final float angle = angle(lastX, lastY, x, y);
+ if (size > 1) {
+ float curvature = getAngleDiff(angle, mAngle);
+ if (curvature > GESTURE_RECOG_CURVATURE_THRESHOLD) {
+ if (size > mIncrementalRecognitionPoint) {
+ mIncrementalRecognitionPoint = size;
+ }
+ mHasSharpCorner = true;
+ }
+ if (!mHasSharpCorner) {
+ mIncrementalRecognitionPoint = size;
+ }
+ }
+ mAngle = angle;
+ }
+
+ if (!isHistorical) {
+ final int duration = (int)(time - mLastPointTime);
+ if (mLastPointTime != 0 && duration > 0) {
+ final int squaredDuration = duration * duration;
+ final float squaredSpeed =
+ squaredDistance(mLastPointX, mLastPointY, x, y) / squaredDuration;
+ if (squaredSpeed < SQUARED_GESTURE_RECOG_SPEED_THRESHOLD) {
+ mIncrementalRecognitionPoint = size;
+ }
+ }
+ updateLastPoint(x, y, time);
+ }
+ }
+
+ private float getAngleDiff(float a1, float a2) {
+ final float diff = Math.abs(a1 - a2);
+ if (diff > Math.PI) {
+ return DOUBLE_PI - diff;
+ }
+ return diff;
+ }
+
+ public void appendAllBatchPoints(InputPointers out) {
+ out.append(mInputPointers, 0, mInputPointers.getPointerSize());
+ }
+
+ public void appendIncrementalBatchPoints(InputPointers out) {
+ out.append(mInputPointers, 0, mIncrementalRecognitionPoint);
+ }
+ }
+
+ static float squaredDistance(int p1x, int p1y, int p2x, int p2y) {
+ final float dx = p1x - p2x;
+ final float dy = p1y - p2y;
+ return dx * dx + dy * dy;
+ }
+
+ static float angle(int p1x, int p1y, int p2x, int p2y) {
+ final int dx = p1x - p2x;
+ final int dy = p1y - p2y;
+ if (dx == 0 && dy == 0) return 0;
+ return (float)Math.atan2(dy, dx);
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 8c218c6d3..d732fc061 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -195,38 +195,37 @@ public final class KeyboardTextsSet {
/* 87 */ "more_keys_for_symbols_0",
/* 88 */ "keylabel_for_comma",
/* 89 */ "more_keys_for_comma",
- /* 90 */ "keylabel_for_symbols_exclamation",
- /* 91 */ "keylabel_for_symbols_question",
- /* 92 */ "keylabel_for_symbols_semicolon",
- /* 93 */ "keylabel_for_symbols_percent",
- /* 94 */ "more_keys_for_symbols_exclamation",
- /* 95 */ "more_keys_for_symbols_question",
- /* 96 */ "more_keys_for_symbols_semicolon",
- /* 97 */ "more_keys_for_symbols_percent",
- /* 98 */ "keylabel_for_tablet_comma",
- /* 99 */ "keyhintlabel_for_tablet_comma",
- /* 100 */ "more_keys_for_tablet_comma",
- /* 101 */ "keyhintlabel_for_tablet_period",
- /* 102 */ "more_keys_for_tablet_period",
- /* 103 */ "keylabel_for_apostrophe",
- /* 104 */ "keyhintlabel_for_apostrophe",
- /* 105 */ "more_keys_for_apostrophe",
- /* 106 */ "more_keys_for_am_pm",
- /* 107 */ "settings_as_more_key",
- /* 108 */ "shortcut_as_more_key",
- /* 109 */ "action_next_as_more_key",
- /* 110 */ "action_previous_as_more_key",
- /* 111 */ "label_to_more_symbol_key",
- /* 112 */ "label_to_more_symbol_for_tablet_key",
- /* 113 */ "label_tab_key",
- /* 114 */ "label_to_phone_numeric_key",
- /* 115 */ "label_to_phone_symbols_key",
- /* 116 */ "label_time_am",
- /* 117 */ "label_time_pm",
- /* 118 */ "label_to_symbol_key_pcqwerty",
- /* 119 */ "keylabel_for_popular_domain",
- /* 120 */ "more_keys_for_popular_domain",
- /* 121 */ "more_keys_for_smiley",
+ /* 90 */ "keylabel_for_symbols_question",
+ /* 91 */ "keylabel_for_symbols_semicolon",
+ /* 92 */ "keylabel_for_symbols_percent",
+ /* 93 */ "more_keys_for_symbols_exclamation",
+ /* 94 */ "more_keys_for_symbols_question",
+ /* 95 */ "more_keys_for_symbols_semicolon",
+ /* 96 */ "more_keys_for_symbols_percent",
+ /* 97 */ "keylabel_for_tablet_comma",
+ /* 98 */ "keyhintlabel_for_tablet_comma",
+ /* 99 */ "more_keys_for_tablet_comma",
+ /* 100 */ "keyhintlabel_for_tablet_period",
+ /* 101 */ "more_keys_for_tablet_period",
+ /* 102 */ "keylabel_for_apostrophe",
+ /* 103 */ "keyhintlabel_for_apostrophe",
+ /* 104 */ "more_keys_for_apostrophe",
+ /* 105 */ "more_keys_for_am_pm",
+ /* 106 */ "settings_as_more_key",
+ /* 107 */ "shortcut_as_more_key",
+ /* 108 */ "action_next_as_more_key",
+ /* 109 */ "action_previous_as_more_key",
+ /* 110 */ "label_to_more_symbol_key",
+ /* 111 */ "label_to_more_symbol_for_tablet_key",
+ /* 112 */ "label_tab_key",
+ /* 113 */ "label_to_phone_numeric_key",
+ /* 114 */ "label_to_phone_symbols_key",
+ /* 115 */ "label_time_am",
+ /* 116 */ "label_time_pm",
+ /* 117 */ "label_to_symbol_key_pcqwerty",
+ /* 118 */ "keylabel_for_popular_domain",
+ /* 119 */ "more_keys_for_popular_domain",
+ /* 120 */ "more_keys_for_smiley",
};
private static final String EMPTY = "";
@@ -331,52 +330,51 @@ public final class KeyboardTextsSet {
/* 87 */ "\u207F,\u2205",
/* 88 */ ",",
/* 89 */ EMPTY,
- /* 90 */ "!",
- /* 91 */ "?",
- /* 92 */ ";",
- /* 93 */ "%",
+ /* 90 */ "?",
+ /* 91 */ ";",
+ /* 92 */ "%",
// U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 94 */ "\u00A1",
+ /* 93 */ "\u00A1",
// U+00BF: "¿" INVERTED QUESTION MARK
- /* 95 */ "\u00BF",
- /* 96 */ EMPTY,
+ /* 94 */ "\u00BF",
+ /* 95 */ EMPTY,
// U+2030: "‰" PER MILLE SIGN
- /* 97 */ "\u2030",
- /* 98 */ ",",
+ /* 96 */ "\u2030",
+ /* 97 */ ",",
+ /* 98 */ "!",
/* 99 */ "!",
- /* 100 */ "!",
+ /* 100 */ "?",
/* 101 */ "?",
- /* 102 */ "?",
- /* 103 */ "\'",
+ /* 102 */ "\'",
+ /* 103 */ "\"",
/* 104 */ "\"",
- /* 105 */ "\"",
- /* 106 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
- /* 107 */ "!icon/settings_key|!code/key_settings",
- /* 108 */ "!icon/shortcut_key|!code/key_shortcut",
- /* 109 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
- /* 110 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+ /* 105 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
+ /* 106 */ "!icon/settings_key|!code/key_settings",
+ /* 107 */ "!icon/shortcut_key|!code/key_shortcut",
+ /* 108 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
+ /* 109 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
// Label for "switch to more symbol" modifier key. Must be short to fit on key!
- /* 111 */ "= \\ <",
+ /* 110 */ "= \\ <",
// Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key!
- /* 112 */ "~ \\ {",
+ /* 111 */ "~ \\ {",
// Label for "Tab" key. Must be short to fit on key!
- /* 113 */ "Tab",
+ /* 112 */ "Tab",
// Label for "switch to phone numeric" key. Must be short to fit on key!
- /* 114 */ "123",
+ /* 113 */ "123",
// Label for "switch to phone symbols" key. Must be short to fit on key!
// U+FF0A: "*" FULLWIDTH ASTERISK
// U+FF03: "#" FULLWIDTH NUMBER SIGN
- /* 115 */ "\uFF0A\uFF03",
+ /* 114 */ "\uFF0A\uFF03",
// Key label for "ante meridiem"
- /* 116 */ "AM",
+ /* 115 */ "AM",
// Key label for "post meridiem"
- /* 117 */ "PM",
+ /* 116 */ "PM",
// Label for "switch to symbols" key on PC QWERTY layout
- /* 118 */ "Sym",
- /* 119 */ ".com",
+ /* 117 */ "Sym",
+ /* 118 */ ".com",
// popular web domains for the locale - most popular, displayed on the keyboard
- /* 120 */ "!hasLabels!,.net,.org,.gov,.edu",
- /* 121 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+ /* 119 */ "!hasLabels!,.net,.org,.gov,.edu",
+ /* 120 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
};
/* Language ar: Arabic */
@@ -483,25 +481,24 @@ public final class KeyboardTextsSet {
// U+060C: "،" ARABIC COMMA
/* 88 */ "\u060C",
/* 89 */ "\\,",
- /* 90 */ null,
- /* 91 */ "\u061F",
- /* 92 */ "\u061B",
+ /* 90 */ "\u061F",
+ /* 91 */ "\u061B",
// U+066A: "٪" ARABIC PERCENT SIGN
- /* 93 */ "\u066A",
- /* 94 */ null,
- /* 95 */ "?",
- /* 96 */ ";",
+ /* 92 */ "\u066A",
+ /* 93 */ null,
+ /* 94 */ "?",
+ /* 95 */ ";",
// U+2030: "‰" PER MILLE SIGN
- /* 97 */ "\\%,\u2030",
- /* 98~ */
+ /* 96 */ "\\%,\u2030",
+ /* 97~ */
null, null, null, null, null,
- /* ~102 */
+ /* ~101 */
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
// U+061F: "؟" ARABIC QUESTION MARK
- /* 103 */ "\u060C",
- /* 104 */ "\u061F",
- /* 105 */ "\u061F,\u061B,!,:,-,/,\',\"",
+ /* 102 */ "\u060C",
+ /* 103 */ "\u061F",
+ /* 104 */ "\u061F,\u061B,!,:,-,/,\',\"",
};
/* Language be: Belarusian */
@@ -861,27 +858,18 @@ public final class KeyboardTextsSet {
/* ~47 */
// U+00A1: "¡" INVERTED EXCLAMATION MARK
// U+00BF: "¿" INVERTED QUESTION MARK
- /* 48 */ "!fixedColumnOrder!9,\",\',#,-,\u00A1,!,\u00BF,\\,,?,@,&,\\%,+,;,:,/,(,)",
+ /* 48 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
/* 49~ */
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, null, null, null, null, null, null,
- /* ~89 */
- // U+00A1: "¡" INVERTED EXCLAMATION MARK
- /* 90 */ "\u00A1",
- // U+00BF: "¿" INVERTED QUESTION MARK
- /* 91 */ "\u00BF",
- /* 92 */ null,
- /* 93 */ null,
- /* 94 */ "!",
- /* 95 */ "?",
- /* 96~ */
- null, null, null,
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ null, null, null, null, null,
/* ~98 */
- /* 99 */ "\u00A1",
- /* 100 */ "\u00A1,!",
- /* 101 */ "\u00BF",
- /* 102 */ "\u00BF,?",
+ // U+00A1: "¡" INVERTED EXCLAMATION MARK
+ /* 99 */ "!,\u00A1",
+ /* 100 */ null,
+ // U+00BF: "¿" INVERTED QUESTION MARK
+ /* 101 */ "?,\u00BF",
};
/* Language et: Estonian */
@@ -1088,29 +1076,28 @@ public final class KeyboardTextsSet {
// U+060C: "،" ARABIC COMMA
/* 88 */ "\u060C",
/* 89 */ "\\,",
- /* 90 */ null,
- /* 91 */ "\u061F",
- /* 92 */ "\u061B",
+ /* 90 */ "\u061F",
+ /* 91 */ "\u061B",
// U+066A: "٪" ARABIC PERCENT SIGN
- /* 93 */ "\u066A",
- /* 94 */ null,
- /* 95 */ "?",
- /* 96 */ ";",
+ /* 92 */ "\u066A",
+ /* 93 */ null,
+ /* 94 */ "?",
+ /* 95 */ ";",
// U+2030: "‰" PER MILLE SIGN
- /* 97 */ "\\%,\u2030",
+ /* 96 */ "\\%,\u2030",
// U+060C: "،" ARABIC COMMA
// U+061B: "؛" ARABIC SEMICOLON
// U+061F: "؟" ARABIC QUESTION MARK
// U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
// U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
- /* 98 */ "\u060C",
- /* 99 */ "!",
- /* 100 */ "!,\\,",
- /* 101 */ "\u061F",
- /* 102 */ "\u061F,?",
- /* 103 */ "\u060C",
- /* 104 */ "\u061F",
- /* 105 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
+ /* 97 */ "\u060C",
+ /* 98 */ "!",
+ /* 99 */ "!,\\,",
+ /* 100 */ "\u061F",
+ /* 101 */ "\u061F,?",
+ /* 102 */ "\u060C",
+ /* 103 */ "\u061F",
+ /* 104 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
};
/* Language fi: Finnish */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index d3bb85d4b..e4a71844a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -31,6 +31,10 @@ public class PointerTrackerQueue {
// TODO: Use ring buffer instead of {@link LinkedList}.
private final LinkedList mQueue = new LinkedList();
+ public int size() {
+ return mQueue.size();
+ }
+
public synchronized void add(PointerTracker tracker) {
mQueue.add(tracker);
}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index f27d32150..518bcd5ce 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -1268,13 +1268,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (SPACE_STATE_PHANTOM == spaceState) {
commitTyped(LastComposedWord.NOT_A_SEPARATOR);
}
+ final int keyX, keyY;
final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
if (keyboard != null && keyboard.hasProximityCharsCorrection(primaryCode)) {
- handleCharacter(primaryCode, x, y, spaceState);
+ keyX = x;
+ keyY = y;
} else {
- handleCharacter(primaryCode, NOT_A_TOUCH_COORDINATE, NOT_A_TOUCH_COORDINATE,
- spaceState);
+ keyX = NOT_A_TOUCH_COORDINATE;
+ keyY = NOT_A_TOUCH_COORDINATE;
}
+ handleCharacter(primaryCode, keyX, keyY, spaceState);
}
mExpectingUpdateSelection = true;
mShouldSwitchToLastSubtype = true;
@@ -1320,10 +1323,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
mSpaceState = SPACE_STATE_PHANTOM;
}
mConnection.endBatchEdit();
+ // TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
+ mWordComposer.setAutoCapitalized(
+ getCurrentAutoCapsState() != Constants.TextUtils.CAP_MODE_OFF);
+ }
+
+ @Override
+ public SuggestedWords onUpdateBatchInput(InputPointers batchPointers) {
+ mWordComposer.setBatchInputPointers(batchPointers);
+ return updateSuggestionsOrPredictions();
}
@Override
public void onEndBatchInput(CharSequence text) {
+ mWordComposer.setBatchInputWord(text);
mConnection.beginBatchEdit();
if (SPACE_STATE_PHANTOM == mSpaceState) {
sendKeyCodePoint(Keyboard.CODE_SPACE);
@@ -1669,7 +1682,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
// TODO: rename this method to updateSuggestionStrip or simply updateSuggestions
- private void updateSuggestionsOrPredictions() {
+ private SuggestedWords updateSuggestionsOrPredictions() {
mHandler.cancelUpdateSuggestionStrip();
// Check if we have a suggestion engine attached.
@@ -1679,13 +1692,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
+ "requested!");
mWordComposer.setAutoCorrection(mWordComposer.getTypedWord());
}
- return;
+ return null;
}
final String typedWord = mWordComposer.getTypedWord();
if (!mWordComposer.isComposingWord() && !mCurrentSettings.mBigramPredictionEnabled) {
setPunctuationSuggestions();
- return;
+ return null;
}
// Get the word on which we should search the bigrams. If we are composing a word, it's
@@ -1701,6 +1714,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
suggestedWords = maybeRetrieveOlderSuggestions(typedWord, suggestedWords);
showSuggestions(suggestedWords, typedWord);
+ return suggestedWords;
}
private SuggestedWords maybeRetrieveOlderSuggestions(final CharSequence typedWord,
@@ -1761,9 +1775,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (mHandler.hasPendingUpdateSuggestions()) {
updateSuggestionsOrPredictions();
}
- final CharSequence autoCorrection = mWordComposer.getAutoCorrectionOrNull();
+ final CharSequence typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull();
+ final String typedWord = mWordComposer.getTypedWord();
+ final CharSequence autoCorrection = (typedAutoCorrection != null)
+ ? typedAutoCorrection : typedWord;
if (autoCorrection != null) {
- final String typedWord = mWordComposer.getTypedWord();
if (TextUtils.isEmpty(typedWord)) {
throw new RuntimeException("We have an auto-correction but the typed word "
+ "is empty? Impossible! I must commit suicide.");
@@ -1808,7 +1824,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
mConnection.beginBatchEdit();
- if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0) {
+ if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0
+ // In the batch input mode, a manually picked suggested word should just replace
+ // the current batch input text and there is no need for a phantom space.
+ && !mWordComposer.isBatchMode()) {
int firstChar = Character.codePointAt(suggestion, 0);
if ((!mCurrentSettings.isWeakSpaceStripper(firstChar))
&& (!mCurrentSettings.isWeakSpaceSwapper(firstChar))) {
diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java
index 9055d5d32..1abfbad13 100644
--- a/java/src/com/android/inputmethod/latin/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java
@@ -19,12 +19,16 @@ package com.android.inputmethod.latin;
import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
import android.app.AlertDialog;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Log;
import android.view.MotionEvent;
import android.view.inputmethod.CompletionInfo;
@@ -40,6 +44,7 @@ import com.android.inputmethod.latin.RichInputConnection.Range;
import com.android.inputmethod.latin.define.ProductionFlag;
import java.io.File;
+import java.io.FileFilter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -93,6 +98,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
Character.codePointAt("\uE000", 0); // U+E000 is in the "private-use area"
// U+E001 is in the "private-use area"
/* package for test */ static final String WORD_REPLACEMENT_STRING = "\uE001";
+ private static final String PREF_LAST_CLEANUP_TIME = "pref_last_cleanup_time";
+ private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = DateUtils.DAY_IN_MILLIS;
+ private static final long MAX_LOGFILE_AGE_IN_MS = DateUtils.DAY_IN_MILLIS;
// set when LatinIME should ignore an onUpdateSelection() callback that
// arises from operations in this class
private static boolean sLatinIMEExpectingUpdateSelection = false;
@@ -101,6 +109,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private Suggest mSuggest;
private Dictionary mDictionary;
private KeyboardSwitcher mKeyboardSwitcher;
+ private Context mContext;
private ResearchLogger() {
}
@@ -115,6 +124,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
if (ims == null) {
Log.w(TAG, "IMS is null; logging is off");
} else {
+ mContext = ims;
mFilesDir = ims.getFilesDir();
if (mFilesDir == null || !mFilesDir.exists()) {
Log.w(TAG, "IME storage directory does not exist.");
@@ -124,10 +134,29 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
mUUIDString = getUUID(prefs);
sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
prefs.registerOnSharedPreferenceChangeListener(this);
+
+ final long lastCleanupTime = prefs.getLong(PREF_LAST_CLEANUP_TIME, 0L);
+ final long now = System.currentTimeMillis();
+ if (lastCleanupTime + DURATION_BETWEEN_DIR_CLEANUP_IN_MS < now) {
+ final long timeHorizon = now - MAX_LOGFILE_AGE_IN_MS;
+ cleanupLoggingDir(mFilesDir, timeHorizon);
+ Editor e = prefs.edit();
+ e.putLong(PREF_LAST_CLEANUP_TIME, now);
+ e.apply();
+ }
}
mKeyboardSwitcher = keyboardSwitcher;
}
+ private void cleanupLoggingDir(final File dir, final long time) {
+ for (File file : dir.listFiles()) {
+ if (file.getName().startsWith(ResearchLogger.FILENAME_PREFIX) &&
+ file.lastModified() < time) {
+ file.delete();
+ }
+ }
+ }
+
private File createLogFile(File filesDir) {
final StringBuilder sb = new StringBuilder();
sb.append(FILENAME_PREFIX).append('-');
@@ -678,18 +707,31 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
private static final String[] EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL = {
"LatinIMEOnStartInputViewInternal", "uuid", "packageName", "inputType", "imeOptions",
- "fieldId", "display", "model", "prefs", "outputFormatVersion"
+ "fieldId", "display", "model", "prefs", "versionCode", "versionName", "outputFormatVersion"
};
public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo,
final SharedPreferences prefs) {
+ final ResearchLogger researchLogger = getInstance();
+ researchLogger.start();
if (editorInfo != null) {
- final Object[] values = {
- getInstance().mUUIDString, editorInfo.packageName,
- Integer.toHexString(editorInfo.inputType),
- Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId, Build.DISPLAY,
- Build.MODEL, prefs, OUTPUT_FORMAT_VERSION
- };
- getInstance().enqueueEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values);
+ final Context context = researchLogger.mContext;
+ try {
+ final PackageInfo packageInfo;
+ packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),
+ 0);
+ final Integer versionCode = packageInfo.versionCode;
+ final String versionName = packageInfo.versionName;
+ final Object[] values = {
+ researchLogger.mUUIDString, editorInfo.packageName,
+ Integer.toHexString(editorInfo.inputType),
+ Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
+ Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
+ OUTPUT_FORMAT_VERSION
+ };
+ researchLogger.enqueueEvent(EVENTKEYS_LATINIME_ONSTARTINPUTVIEWINTERNAL, values);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 31566bf13..598ef1de7 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -300,11 +300,27 @@ public class Suggest {
final ArrayList suggestionsContainer =
new ArrayList(suggestionsSet);
+ final int suggestionsCount = suggestionsContainer.size();
+ final boolean isFirstCharCapitalized = wordComposer.isAutoCapitalized();
+ // TODO: Handle the manual temporary shifted mode.
+ // TODO: Should handle TextUtils.CAP_MODE_CHARACTER.
+ final boolean isAllUpperCase = false;
+ if (isFirstCharCapitalized || isAllUpperCase) {
+ for (int i = 0; i < suggestionsCount; ++i) {
+ final SuggestedWordInfo wordInfo = suggestionsContainer.get(i);
+ final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(
+ wordInfo, mLocale, isAllUpperCase, isFirstCharCapitalized,
+ 0 /* trailingSingleQuotesCount */);
+ suggestionsContainer.set(i, transformedWordInfo);
+ }
+ }
SuggestedWordInfo.removeDups(suggestionsContainer);
+ // In the batch input mode, the most relevant suggested word should act as a "typed word"
+ // (typedWordValid=true), not as an "auto correct word" (willAutoCorrect=false).
return new SuggestedWords(suggestionsContainer,
true /* typedWordValid */,
- true /* willAutoCorrect */,
+ false /* willAutoCorrect */,
false /* isPunctuationSuggestions */,
false /* isObsoleteSuggestions */,
false /* isPrediction */);
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 25e29008e..ca9dbaf05 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -130,8 +130,13 @@ public class WordComposer {
if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) {
mPrimaryKeyCodes[newIndex] = primaryCode >= Keyboard.CODE_SPACE
? Character.toLowerCase(primaryCode) : primaryCode;
- // TODO: Set correct pointer id and time
- mInputPointers.addPointer(newIndex, keyX, keyY, 0, 0);
+ // In the batch input mode, the {@code mInputPointers} holds batch input points and
+ // shouldn't be overridden by the "typed key" coordinates
+ // (See {@link #setBatchInputWord}).
+ if (!mIsBatchMode) {
+ // TODO: Set correct pointer id and time
+ mInputPointers.addPointer(newIndex, keyX, keyY, 0, 0);
+ }
}
mIsFirstCharCapitalized = isFirstCharCapitalized(
newIndex, primaryCode, mIsFirstCharCapitalized);
@@ -144,12 +149,23 @@ public class WordComposer {
mAutoCorrection = null;
}
- // TODO: We may want to have appendBatchInputPointers() as well.
public void setBatchInputPointers(InputPointers batchPointers) {
- mInputPointers.copy(batchPointers);
+ mInputPointers.set(batchPointers);
mIsBatchMode = true;
}
+ public void setBatchInputWord(CharSequence word) {
+ reset();
+ mIsBatchMode = true;
+ final int length = word.length();
+ for (int i = 0; i < length; i = Character.offsetByCodePoints(word, i, 1)) {
+ final int codePoint = Character.codePointAt(word, i);
+ // We don't want to override the batch input points that are held in mInputPointers
+ // (See {@link #add(int,int,int)}).
+ add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
+ }
+ }
+
/**
* Internal method to retrieve reasonable proximity info for a character.
*/
@@ -161,7 +177,7 @@ public class WordComposer {
add(codePoint, x, y);
return;
}
- add(codePoint, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+ add(codePoint, NOT_A_COORDINATE, NOT_A_COORDINATE);
}
/**
diff --git a/tools/maketext/res/values-es/donottranslate-more-keys.xml b/tools/maketext/res/values-es/donottranslate-more-keys.xml
index 2077af464..586dbade8 100644
--- a/tools/maketext/res/values-es/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-es/donottranslate-more-keys.xml
@@ -69,15 +69,9 @@
ç,ć,č
- "!fixedColumnOrder!9,\",\',#,-,¡,!,¿,\\,,\?,\@,&,\\%,+,;,:,/,(,)"
- ¡
- "¡,!"
- ¿
- "¿,\?"
+ "!fixedColumnOrder!9,¡,\",\',#,-,:,!,\\,,\?,¿,\@,&,\\%,+,;,/,(,)"
- "¡"
+ "!,¡"
- "¿"
- "!"
- "\?"
+ "\?,¿"
diff --git a/tools/maketext/res/values/donottranslate-more-keys.xml b/tools/maketext/res/values/donottranslate-more-keys.xml
index 922b42d1b..7f1940a57 100644
--- a/tools/maketext/res/values/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values/donottranslate-more-keys.xml
@@ -158,7 +158,6 @@
ⁿ,∅
,
- !
\?
;
%