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 @@ ⁿ,∅ , - ! \? ; %