diff options
Diffstat (limited to 'java/src')
15 files changed, 401 insertions, 300 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java index 10fb9fef4..216a825e0 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java @@ -158,7 +158,7 @@ public final class AccessibilityUtils { * @param typedWord the currently typed word */ public void setAutoCorrection(final SuggestedWords suggestedWords, final String typedWord) { - if (suggestedWords != null && suggestedWords.mWillAutoCorrect) { + if (suggestedWords.mWillAutoCorrect) { mAutoCorrectionWord = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION); mTypedWord = typedWord; } else { diff --git a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java index a8fab8855..dec739d39 100644 --- a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java @@ -20,6 +20,9 @@ import android.view.View; import java.lang.reflect.Method; +// TODO: Use {@link android.support.v4.view.ViewCompat} instead of this utility class. +// Currently {@link #getPaddingEnd(View)} and {@link #setPaddingRelative(View,int,int,int,int)} +// are missing from android-support-v4 static library in KitKat SDK. public final class ViewCompatUtils { // Note that View.LAYOUT_DIRECTION_LTR and View.LAYOUT_DIRECTION_RTL have been introduced in // API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1). diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java index 8dea908e8..5bde668e1 100644 --- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java +++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java @@ -23,12 +23,13 @@ import android.content.SharedPreferences; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Color; import android.graphics.Rect; import android.os.Build; +import android.os.CountDownTimer; import android.preference.PreferenceManager; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; -import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; import android.util.Pair; @@ -58,6 +59,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; /** * View class to implement Emoji palettes. @@ -752,9 +754,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange } } - // TODO: Do the same things done in PointerTracker private static class DeleteKeyOnTouchListener implements OnTouchListener { - private static final long MAX_REPEAT_COUNT_TIME = 30 * DateUtils.SECOND_IN_MILLIS; + private static final long MAX_REPEAT_COUNT_TIME = TimeUnit.SECONDS.toMillis(30); private final int mDeleteKeyPressedBackgroundColor; private final long mKeyRepeatStartTimeout; private final long mKeyRepeatInterval; @@ -765,80 +766,117 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange res.getColor(R.color.emoji_key_pressed_background_color); mKeyRepeatStartTimeout = res.getInteger(R.integer.config_key_repeat_start_timeout); mKeyRepeatInterval = res.getInteger(R.integer.config_key_repeat_interval); + mTimer = new CountDownTimer(MAX_REPEAT_COUNT_TIME, mKeyRepeatInterval) { + @Override + public void onTick(long millisUntilFinished) { + final long elapsed = MAX_REPEAT_COUNT_TIME - millisUntilFinished; + if (elapsed < mKeyRepeatStartTimeout) { + return; + } + onKeyRepeat(); + } + @Override + public void onFinish() { + onKeyRepeat(); + } + }; } + /** Key-repeat state. */ + private static final int KEY_REPEAT_STATE_INITIALIZED = 0; + // The key is touched but auto key-repeat is not started yet. + private static final int KEY_REPEAT_STATE_KEY_DOWN = 1; + // At least one key-repeat event has already been triggered and the key is not released. + private static final int KEY_REPEAT_STATE_KEY_REPEAT = 2; + private KeyboardActionListener mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER; - private DummyRepeatKeyRepeatTimer mTimer; - private synchronized void startRepeat() { - if (mTimer != null) { - abortRepeat(); - } - mTimer = new DummyRepeatKeyRepeatTimer(); - mTimer.start(); - } + // TODO: Do the same things done in PointerTracker + private final CountDownTimer mTimer; + private int mState = KEY_REPEAT_STATE_INITIALIZED; + private int mRepeatCount = 0; - private synchronized void abortRepeat() { - mTimer.abort(); - mTimer = null; + public void setKeyboardActionListener(final KeyboardActionListener listener) { + mKeyboardActionListener = listener; } - // TODO: Remove - // This function is mimicking the repeat code in PointerTracker. - // Specifically referring to PointerTracker#startRepeatKey and PointerTracker#onKeyRepeat. - private class DummyRepeatKeyRepeatTimer extends Thread { - public boolean mAborted = false; - - @Override - public void run() { - int repeatCount = 1; - int timeCount = 0; - while (timeCount < MAX_REPEAT_COUNT_TIME && !mAborted) { - if (timeCount > mKeyRepeatStartTimeout) { - pressDelete(repeatCount); - } - timeCount += mKeyRepeatInterval; - ++repeatCount; - try { - Thread.sleep(mKeyRepeatInterval); - } catch (InterruptedException e) { - } + @Override + public boolean onTouch(final View v, final MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + onTouchDown(v); + return true; + case MotionEvent.ACTION_MOVE: + final float x = event.getX(); + final float y = event.getY(); + if (x < 0.0f || v.getWidth() < x || y < 0.0f || v.getHeight() < y) { + // Stop generating key events once the finger moves away from the view area. + onTouchCanceled(v); } + return true; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + onTouchUp(v); + return true; } - - public void abort() { - mAborted = true; - } + return false; } - public void pressDelete(final int repeatCount) { + private void handleKeyDown() { mKeyboardActionListener.onPressKey( - Constants.CODE_DELETE, repeatCount, true /* isSinglePointer */); + Constants.CODE_DELETE, mRepeatCount, true /* isSinglePointer */); + } + + private void handleKeyUp() { mKeyboardActionListener.onCodeInput( Constants.CODE_DELETE, NOT_A_COORDINATE, NOT_A_COORDINATE); mKeyboardActionListener.onReleaseKey( Constants.CODE_DELETE, false /* withSliding */); + ++mRepeatCount; } - public void setKeyboardActionListener(final KeyboardActionListener listener) { - mKeyboardActionListener = listener; + private void onTouchDown(final View v) { + mTimer.cancel(); + mRepeatCount = 0; + handleKeyDown(); + v.setBackgroundColor(mDeleteKeyPressedBackgroundColor); + mState = KEY_REPEAT_STATE_KEY_DOWN; + mTimer.start(); } - @Override - public boolean onTouch(final View v, final MotionEvent event) { - switch(event.getAction()) { - case MotionEvent.ACTION_DOWN: - v.setBackgroundColor(mDeleteKeyPressedBackgroundColor); - pressDelete(0 /* repeatCount */); - startRepeat(); - return true; - case MotionEvent.ACTION_UP: - v.setBackgroundColor(0); - abortRepeat(); - return true; + private void onTouchUp(final View v) { + mTimer.cancel(); + if (mState == KEY_REPEAT_STATE_KEY_DOWN) { + handleKeyUp(); + } + v.setBackgroundColor(Color.TRANSPARENT); + mState = KEY_REPEAT_STATE_INITIALIZED; + } + + private void onTouchCanceled(final View v) { + mTimer.cancel(); + v.setBackgroundColor(Color.TRANSPARENT); + mState = KEY_REPEAT_STATE_INITIALIZED; + } + + // Called by {@link #mTimer} in the UI thread as an auto key-repeat signal. + private void onKeyRepeat() { + switch (mState) { + case KEY_REPEAT_STATE_INITIALIZED: + // Basically this should not happen. + break; + case KEY_REPEAT_STATE_KEY_DOWN: + // Do not call {@link #handleKeyDown} here because it has already been called + // in {@link #onTouchDown}. + handleKeyUp(); + mState = KEY_REPEAT_STATE_KEY_REPEAT; + break; + case KEY_REPEAT_STATE_KEY_REPEAT: + handleKeyDown(); + handleKeyUp(); + break; } - return false; } } } diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index bc1383aff..4fd3bac2f 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -23,6 +23,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.CoordinateUtils; /** * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard @@ -217,4 +218,20 @@ public class Keyboard { final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1)); return mProximityInfo.getNearestKeys(adjustedX, adjustedY); } + + public int[] getCoordinates(final int[] codePoints) { + final int length = codePoints.length; + final int[] coordinates = CoordinateUtils.newCoordinateArray(length); + for (int i = 0; i < length; ++i) { + final Key key = getKey(codePoints[i]); + if (null != key) { + CoordinateUtils.setXYInArray(coordinates, i, + key.getX() + key.getWidth() / 2, key.getY() + key.getHeight() / 2); + } else { + CoordinateUtils.setXYInArray(coordinates, i, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); + } + } + return coordinates; + } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index a4253bb3b..ba7503dae 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -59,7 +59,6 @@ import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.InputMethodServiceCompatUtils; import com.android.inputmethod.dictionarypack.DictionaryPackConstants; -import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.KeyboardId; @@ -790,7 +789,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mSuggestionStripView != null) { // This will set the punctuation suggestions if next word suggestion is off; // otherwise it will clear the suggestion strip. - setPunctuationSuggestions(); + setNeutralSuggestionStrip(); } // Sometimes, while rotating, for some reason the framework tells the app we are not @@ -889,10 +888,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.finishInput(); // Notify ResearchLogger if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, - // TODO[IL]: mInputLogic.mConnection should be private - mInputLogic.mConnection.getExpectedSelectionStart(), - mInputLogic.mConnection.getExpectedSelectionEnd(), getCurrentInputConnection()); + ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput); } } @@ -916,63 +912,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen composingSpanEnd, mInputLogic.mConnection); } - final boolean selectionChanged = oldSelStart != newSelStart || oldSelEnd != newSelEnd; - - // if composingSpanStart and composingSpanEnd are -1, it means there is no composing - // span in the view - we can use that to narrow down whether the cursor was moved - // by us or not. If we are composing a word but there is no composing span, then - // we know for sure the cursor moved while we were composing and we should reset - // the state. TODO: rescind this policy: the framework never removes the composing - // span on its own accord while editing. This test is useless. - final boolean noComposingSpan = composingSpanStart == -1 && composingSpanEnd == -1; - // If the keyboard is not visible, we don't need to do all the housekeeping work, as it // will be reset when the keyboard shows up anyway. // TODO: revisit this when LatinIME supports hardware keyboards. // NOTE: the test harness subclasses LatinIME and overrides isInputViewShown(). // TODO: find a better way to simulate actual execution. - if (isInputViewShown() && !mInputLogic.mConnection.isBelatedExpectedUpdate(oldSelStart, - newSelStart, oldSelEnd, newSelEnd)) { - // TODO: the following is probably better done in resetEntireInputState(). - // it should only happen when the cursor moved, and the very purpose of the - // test below is to narrow down whether this happened or not. Likewise with - // the call to updateShiftState. - // We set this to NONE because after a cursor move, we don't want the space - // state-related special processing to kick in. - mInputLogic.mSpaceState = SpaceState.NONE; - - // TODO: is it still necessary to test for composingSpan related stuff? - final boolean selectionChangedOrSafeToReset = selectionChanged - || (!mInputLogic.mWordComposer.isComposingWord()) || noComposingSpan; - final boolean hasOrHadSelection = (oldSelStart != oldSelEnd - || newSelStart != newSelEnd); - final int moveAmount = newSelStart - oldSelStart; - if (selectionChangedOrSafeToReset && (hasOrHadSelection - || !mInputLogic.mWordComposer.moveCursorByAndReturnIfInsideComposingWord( - moveAmount))) { - // If we are composing a word and moving the cursor, we would want to set a - // suggestion span for recorrection to work correctly. Unfortunately, that - // would involve the keyboard committing some new text, which would move the - // cursor back to where it was. Latin IME could then fix the position of the cursor - // again, but the asynchronous nature of the calls results in this wreaking havoc - // with selection on double tap and the like. - // Another option would be to send suggestions each time we set the composing - // text, but that is probably too expensive to do, so we decided to leave things - // as is. - mInputLogic.resetEntireInputState(mSettings.getCurrent(), newSelStart, newSelEnd); - } else { - // resetEntireInputState calls resetCachesUponCursorMove, but forcing the - // composition to end. But in all cases where we don't reset the entire input - // state, we still want to tell the rich input connection about the new cursor - // position so that it can update its caches. - mInputLogic.mConnection.resetCachesUponCursorMoveAndReturnSuccess( - newSelStart, newSelEnd, false /* shouldFinishComposition */); - } - - // We moved the cursor. If we are touching a word, we need to resume suggestion. - mHandler.postResumeSuggestions(); - // Reset the last recapitalization. - mInputLogic.mRecapitalizeStatus.deactivate(); + if (isInputViewShown() && + mInputLogic.onUpdateSelection(mSettings.getCurrent(), oldSelStart, oldSelEnd, + newSelStart, newSelEnd, composingSpanStart, composingSpanEnd)) { mKeyboardSwitcher.updateShiftState(); } @@ -1043,7 +990,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } if (!mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()) return; if (applicationSpecifiedCompletions == null) { - clearSuggestionStrip(); + setNeutralSuggestionStrip(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_onDisplayCompletions(null); } @@ -1058,7 +1005,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final SuggestedWords suggestedWords = new SuggestedWords( applicationSuggestedWords, false /* typedWordValid */, - false /* hasAutoCorrectionCandidate */, + false /* willAutoCorrect */, false /* isPunctuationSuggestions */, false /* isObsoleteSuggestions */, false /* isPrediction */); @@ -1187,7 +1134,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // the right layout. // TODO[IL]: Remove this, pass the input logic to the keyboard switcher instead? public int getCurrentAutoCapsState() { - return mInputLogic.getCurrentAutoCapsState(null /* optionalSettingsValues */); + return mInputLogic.getCurrentAutoCapsState(mSettings.getCurrent()); } // Called from the KeyboardSwitcher which needs to know recaps state to display @@ -1206,23 +1153,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * @return x,y coordinates for this keyboard, as a flattened array. */ public int[] getCoordinatesForCurrentKeyboard(final int[] codePoints) { - return getCoordinatesForKeyboard(codePoints, mKeyboardSwitcher.getKeyboard()); - } - - public static int[] getCoordinatesForKeyboard(final int[] codePoints, final Keyboard keyboard) { - final int length = codePoints.length; - final int[] coordinates = CoordinateUtils.newCoordinateArray(length); - Key key; - for (int i = 0; i < length; ++i) { - if (keyboard != null && (key = keyboard.getKey(codePoints[i])) != null) { - CoordinateUtils.setXYInArray(coordinates, i, - key.getX() + key.getWidth() / 2, key.getY() + key.getHeight() / 2); - } else { - CoordinateUtils.setXYInArray(coordinates, i, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } + final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); + if (null == keyboard) { + return CoordinateUtils.newCoordinateArray(codePoints.length, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); + } else { + return keyboard.getCoordinates(codePoints); } - return coordinates; } // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is @@ -1299,8 +1236,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { codeToSend = codePoint; } - mInputLogic.onCodeInput(codeToSend, keyX, keyY, mHandler, mKeyboardSwitcher, - mSubtypeSwitcher); + if (Constants.CODE_SHORTCUT == codePoint) { + mSubtypeSwitcher.switchToShortcutIME(this); + // Still call the *#onCodeInput methods for readability. + } + mInputLogic.onCodeInput(codeToSend, keyX, keyY, mSettings.getCurrent(), + mHandler, mKeyboardSwitcher); mKeyboardSwitcher.onCodeInput(codePoint); } @@ -1389,12 +1330,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // TODO[IL]: Define a clear interface for this - public void clearSuggestionStrip() { - setSuggestedWords(SuggestedWords.EMPTY); - setAutoCorrectionIndicator(false); - } - - // TODO[IL]: Define a clear interface for this public void setSuggestedWords(final SuggestedWords words) { mInputLogic.mSuggestedWords = words; if (mSuggestionStripView != null) { @@ -1477,10 +1412,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final SuggestedWords punctuationList = mSettings.getCurrent().mSpacingAndPunctuations.mSuggestPuncList; final SuggestedWords oldSuggestedWords = previousSuggestedWords == punctuationList - ? SuggestedWords.EMPTY : previousSuggestedWords; - if (TextUtils.isEmpty(typedWord)) { - return oldSuggestedWords; - } + ? SuggestedWords.EMPTY : previousSuggestedWords; final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions = SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, oldSuggestedWords); return new SuggestedWords(typedWordAndPreviousSuggestions, @@ -1492,39 +1424,39 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } } - private void showSuggestionStripWithTypedWord(final SuggestedWords suggestedWords, + private void showSuggestionStripWithTypedWord(final SuggestedWords sourceSuggestedWords, final String typedWord) { - if (suggestedWords.isEmpty()) { - // No auto-correction is available, clear the cached values. - AccessibilityUtils.getInstance().setAutoCorrection(null, null); - clearSuggestionStrip(); - return; - } - final String autoCorrection; - if (suggestedWords.mWillAutoCorrect) { - autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION); - } else { - // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD) - // because it may differ from mWordComposer.mTypedWord. - autoCorrection = typedWord; - } - mInputLogic.mWordComposer.setAutoCorrection(autoCorrection); - setSuggestedWords(suggestedWords); - setAutoCorrectionIndicator(suggestedWords.mWillAutoCorrect); - setSuggestionStripShown(isSuggestionsStripVisible()); - // An auto-correction is available, cache it in accessibility code so - // we can be speak it if the user touches a key that will insert it. - AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords, typedWord); + // TODO: refactor this + final SuggestedWords suggestedWords = + sourceSuggestedWords.isEmpty() ? SuggestedWords.EMPTY : sourceSuggestedWords; + if (suggestedWords.isEmpty()) { + // No auto-correction is available, clear the cached values. + AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords, typedWord); + setSuggestedWords(suggestedWords); + setAutoCorrectionIndicator(false); + return; + } + final String autoCorrection; + if (suggestedWords.mWillAutoCorrect) { + autoCorrection = suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION); + } else { + // We can't use suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD) + // because it may differ from mWordComposer.mTypedWord. + autoCorrection = typedWord; + } + mInputLogic.mWordComposer.setAutoCorrection(autoCorrection); + setSuggestedWords(suggestedWords); + setAutoCorrectionIndicator(suggestedWords.mWillAutoCorrect); + setSuggestionStripShown(isSuggestionsStripVisible()); + // An auto-correction is available, cache it in accessibility code so + // we can be speak it if the user touches a key that will insert it. + AccessibilityUtils.getInstance().setAutoCorrection(suggestedWords, typedWord); } // TODO[IL]: Define a clean interface for this public void showSuggestionStrip(final SuggestedWords suggestedWords) { - if (suggestedWords.isEmpty()) { - clearSuggestionStrip(); - return; - } - showSuggestionStripWithTypedWord(suggestedWords, - suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)); + showSuggestionStripWithTypedWord(suggestedWords, suggestedWords.isEmpty() ? null + : suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD)); } // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} @@ -1623,10 +1555,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // TODO[IL]: Define a clean interface for this - public void setPunctuationSuggestions() { + // This will show either an empty suggestion strip (if prediction is enabled) or + // punctuation suggestions (if it's disabled). + public void setNeutralSuggestionStrip() { final SettingsValues currentSettings = mSettings.getCurrent(); if (currentSettings.mBigramPredictionEnabled) { - clearSuggestionStrip(); + setSuggestedWords(SuggestedWords.EMPTY); } else { setSuggestedWords(currentSettings.mSpacingAndPunctuations.mSuggestPuncList); } diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 325a0d981..0d0b7a160 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -815,6 +815,17 @@ public final class RichInputConnection { } /** + * Looks at the text just before the cursor to find out if we are inside a double quote. + * + * As with #textBeforeCursorLooksLikeURL, this is dependent on how much text we have cached. + * However this won't be a concrete problem in most situations, as the cache is almost always + * long enough for this use. + */ + public boolean isInsideDoubleQuoteOrAfterDigit() { + return StringUtils.isInsideDoubleQuoteOrAfterDigit(mCommittedTextBeforeComposingText); + } + + /** * Try to get the text from the editor to expose lies the framework may have been * telling us. Concretely, when the device rotates, the frameworks tells us about where the * cursor used to be initially in the editor at the time it first received the focus; this diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 6cd84016e..125976932 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -16,9 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.StringUtils; @@ -276,24 +273,6 @@ public final class WordComposer { } /** - * Add a dummy key by retrieving reasonable coordinates - */ - // TODO: make this private or remove it entirely. Right now it's used in the tests - @UsedForTesting - public void addKeyInfo(final int codePoint, final Keyboard keyboard) { - final int x, y; - final Key key; - if (keyboard != null && (key = keyboard.getKey(codePoint)) != null) { - x = key.getX() + key.getWidth() / 2; - y = key.getY() + key.getHeight() / 2; - } else { - x = Constants.NOT_A_COORDINATE; - y = Constants.NOT_A_COORDINATE; - } - add(codePoint, x, y); - } - - /** * Set the currently composing word to the one passed as an argument. * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity. * @param codePoints the code points to set as the composing word. diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 6e9bdc34a..43d75330d 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -36,14 +36,12 @@ import com.android.inputmethod.latin.LastComposedWord; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.RichInputConnection; -import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.Suggest; import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; import com.android.inputmethod.latin.define.ProductionFlag; -import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.settings.SettingsValues; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; import com.android.inputmethod.latin.utils.AsyncResultHolder; @@ -181,6 +179,67 @@ public final class InputLogic { } /** + * Consider an update to the cursor position. Evaluate whether this update has happened as + * part of normal typing or whether it was an explicit cursor move by the user. In any case, + * do the necessary adjustments. + * @param settingsValues the current settings + * @param oldSelStart old selection start + * @param oldSelEnd old selection end + * @param newSelStart new selection start + * @param newSelEnd new selection end + * @param composingSpanStart composing span start + * @param composingSpanEnd composing span end + * @return whether the cursor has moved as a result of user interaction. + */ + public boolean onUpdateSelection(final SettingsValues settingsValues, + final int oldSelStart, final int oldSelEnd, + final int newSelStart, final int newSelEnd, + final int composingSpanStart, final int composingSpanEnd) { + if (mConnection.isBelatedExpectedUpdate(oldSelStart, newSelStart, oldSelEnd, newSelEnd)) { + return false; + } + // TODO: the following is probably better done in resetEntireInputState(). + // it should only happen when the cursor moved, and the very purpose of the + // test below is to narrow down whether this happened or not. Likewise with + // the call to updateShiftState. + // We set this to NONE because after a cursor move, we don't want the space + // state-related special processing to kick in. + mSpaceState = SpaceState.NONE; + + final boolean selectionChangedOrSafeToReset = + oldSelStart != newSelStart || oldSelEnd != newSelEnd // selection changed + || !mWordComposer.isComposingWord(); // safe to reset + final boolean hasOrHadSelection = (oldSelStart != oldSelEnd || newSelStart != newSelEnd); + final int moveAmount = newSelStart - oldSelStart; + if (selectionChangedOrSafeToReset && (hasOrHadSelection + || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { + // If we are composing a word and moving the cursor, we would want to set a + // suggestion span for recorrection to work correctly. Unfortunately, that + // would involve the keyboard committing some new text, which would move the + // cursor back to where it was. Latin IME could then fix the position of the cursor + // again, but the asynchronous nature of the calls results in this wreaking havoc + // with selection on double tap and the like. + // Another option would be to send suggestions each time we set the composing + // text, but that is probably too expensive to do, so we decided to leave things + // as is. + resetEntireInputState(settingsValues, newSelStart, newSelEnd); + } else { + // resetEntireInputState calls resetCachesUponCursorMove, but forcing the + // composition to end. But in all cases where we don't reset the entire input + // state, we still want to tell the rich input connection about the new cursor + // position so that it can update its caches. + mConnection.resetCachesUponCursorMoveAndReturnSuccess( + newSelStart, newSelEnd, false /* shouldFinishComposition */); + } + + // We moved the cursor. If we are touching a word, we need to resume suggestion. + mLatinIME.mHandler.postResumeSuggestions(); + // Reset the last recapitalization. + mRecapitalizeStatus.deactivate(); + return true; + } + + /** * React to a code input. It may be a code point to insert, or a symbolic value that influences * the keyboard behavior. * @@ -192,13 +251,12 @@ public final class InputLogic { * @param y the y-coordinate where the user pressed the key, or NOT_A_COORDINATE. */ public void onCodeInput(final int code, final int x, final int y, - // TODO: remove these three arguments - final LatinIME.UIHandler handler, - final KeyboardSwitcher keyboardSwitcher, final SubtypeSwitcher subtypeSwitcher) { + final SettingsValues settingsValues, + // TODO: remove these two arguments + final LatinIME.UIHandler handler, final KeyboardSwitcher keyboardSwitcher) { if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.latinIME_onCodeInput(code, x, y); } - final SettingsValues settingsValues = Settings.getInstance().getCurrent(); final long when = SystemClock.uptimeMillis(); if (code != Constants.CODE_DELETE || when > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) { @@ -247,7 +305,8 @@ public final class InputLogic { onSettingsKeyPressed(); break; case Constants.CODE_SHORTCUT: - subtypeSwitcher.switchToShortcutIME(mLatinIME); + // We need to switch to the shortcut IME. This is handled by LatinIME since the + // input logic has no business with IME switching. break; case Constants.CODE_ACTION_NEXT: performEditorAction(EditorInfo.IME_ACTION_NEXT); @@ -602,8 +661,21 @@ public final class InputLogic { final boolean swapWeakSpace = maybeStripSpace(settingsValues, codePoint, spaceState, isFromSuggestionStrip); - if (SpaceState.PHANTOM == spaceState && - settingsValues.isUsuallyPrecededBySpace(codePoint)) { + final boolean isInsideDoubleQuoteOrAfterDigit = Constants.CODE_DOUBLE_QUOTE == codePoint + && mConnection.isInsideDoubleQuoteOrAfterDigit(); + + final boolean needsPrecedingSpace; + if (SpaceState.PHANTOM != spaceState) { + needsPrecedingSpace = false; + } else if (Constants.CODE_DOUBLE_QUOTE == codePoint) { + // Double quotes behave like they are usually preceded by space iff we are + // not inside a double quote or after a digit. + needsPrecedingSpace = !isInsideDoubleQuoteOrAfterDigit; + } else { + needsPrecedingSpace = settingsValues.isUsuallyPrecededBySpace(codePoint); + } + + if (needsPrecedingSpace) { promotePhantomSpace(settingsValues); } if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { @@ -630,14 +702,17 @@ public final class InputLogic { if (swapWeakSpace) { swapSwapperAndSpace(keyboardSwitcher); mSpaceState = SpaceState.SWAP_PUNCTUATION; - } else if (SpaceState.PHANTOM == spaceState - && settingsValues.isUsuallyFollowedBySpace(codePoint)) { + } else if ((SpaceState.PHANTOM == spaceState + && settingsValues.isUsuallyFollowedBySpace(codePoint)) + || (Constants.CODE_DOUBLE_QUOTE == codePoint + && isInsideDoubleQuoteOrAfterDigit)) { // If we are in phantom space state, and the user presses a separator, we want to // stay in phantom space state so that the next keypress has a chance to add the // space. For example, if I type "Good dat", pick "day" from the suggestion strip // then insert a comma and go on to typing the next word, I want the space to be // inserted automatically before the next word, the same way it is when I don't - // input the comma. + // input the comma. A double quote behaves like it's usually followed by space if + // we're inside a double quote. // The case is a little different if the separator is a space stripper. Such a // separator does not normally need a space on the right (that's the difference // between swappers and strippers), so we should not stay in phantom space state if @@ -647,7 +722,7 @@ public final class InputLogic { // Set punctuation right away. onUpdateSelection will fire but tests whether it is // already displayed or not, so it's okay. - mLatinIME.setPunctuationSuggestions(); + mLatinIME.setNeutralSuggestionStrip(); } keyboardSwitcher.updateShiftState(); @@ -1000,7 +1075,7 @@ public final class InputLogic { } if (!mWordComposer.isComposingWord() && !settingsValues.mBigramPredictionEnabled) { - mLatinIME.setPunctuationSuggestions(); + mLatinIME.setNeutralSuggestionStrip(); return; } @@ -1256,15 +1331,10 @@ public final class InputLogic { * This is called from the KeyboardSwitcher (through a trampoline in LatinIME) because it * needs to know auto caps state to display the right layout. * - * @param optionalSettingsValues settings values, or null if we should just get the current ones - * from the singleton. + * @param settingsValues the relevant settings values * @return a caps mode from TextUtils.CAP_MODE_* or Constants.TextUtils.CAP_MODE_OFF. */ - public int getCurrentAutoCapsState(final SettingsValues optionalSettingsValues) { - // If we are in a batch edit, we need to use the same settings values as the outside - // code, that will pass it to us. Otherwise, we can just take the current values. - final SettingsValues settingsValues = null != optionalSettingsValues - ? optionalSettingsValues : Settings.getInstance().getCurrent(); + public int getCurrentAutoCapsState(final SettingsValues settingsValues) { if (!settingsValues.mAutoCap) return Constants.TextUtils.CAP_MODE_OFF; final EditorInfo ei = getCurrentInputEditorInfo(); @@ -1390,11 +1460,7 @@ public final class InputLogic { final int newSelStart, final int newSelEnd) { final boolean shouldFinishComposition = mWordComposer.isComposingWord(); resetComposingState(true /* alsoResetLastComposedWord */); - if (settingsValues.mBigramPredictionEnabled) { - mLatinIME.clearSuggestionStrip(); - } else { - mLatinIME.setSuggestedWords(settingsValues.mSpacingAndPunctuations.mSuggestPuncList); - } + mLatinIME.setNeutralSuggestionStrip(); mConnection.resetCachesUponCursorMoveAndReturnSuccess(newSelStart, newSelEnd, shouldFinishComposition); } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 826e36d86..3947019ca 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -28,12 +28,13 @@ import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; import com.android.inputmethod.compat.SuggestionsInfoCompatUtils; +import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer; +import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.LocaleUtils; import com.android.inputmethod.latin.utils.StringUtils; @@ -314,9 +315,14 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { } final WordComposer composer = new WordComposer(); final int[] codePoints = StringUtils.toCodePointArray(text); - composer.setComposingWord(codePoints, - LatinIME.getCoordinatesForKeyboard(codePoints, dictInfo.mKeyboard), - null /* previousWord */); + final int[] coordinates; + if (null == dictInfo.mKeyboard) { + coordinates = CoordinateUtils.newCoordinateArray(codePoints.length, + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); + } else { + coordinates = dictInfo.mKeyboard.getCoordinates(codePoints); + } + composer.setComposingWord(codePoints, coordinates, null /* previousWord */); // TODO: make a spell checker option to block offensive words or not final ArrayList<SuggestedWordInfo> suggestions = dictInfo.mDictionary.getSuggestions(composer, prevWord, diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index f836e61cb..af04de435 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -38,7 +38,6 @@ import android.text.style.StyleSpan; import android.text.style.UnderlineSpan; import android.util.AttributeSet; import android.view.Gravity; -import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -100,10 +99,6 @@ final class SuggestionStripLayoutHelper { private static final int AUTO_CORRECT_UNDERLINE = 0x02; private static final int VALID_TYPED_WORD_BOLD = 0x04; - private final TextView mWordToSaveView; - private final TextView mLeftwardsArrowView; - private final TextView mHintToSaveView; - public SuggestionStripLayoutHelper(final Context context, final AttributeSet attrs, final int defStyle, final ArrayList<TextView> wordViews, final ArrayList<View> dividerViews, final ArrayList<TextView> debugInfoViews) { @@ -157,11 +152,6 @@ final class SuggestionStripLayoutHelper { R.dimen.config_more_suggestions_bottom_gap); mMoreSuggestionsRowHeight = res.getDimensionPixelSize( R.dimen.config_more_suggestions_row_height); - - final LayoutInflater inflater = LayoutInflater.from(context); - mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); - mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); - mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null); } public int getMaxMoreSuggestionsRow() { @@ -466,54 +456,30 @@ final class SuggestionStripLayoutHelper { mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip); } - public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView, - final int stripWidth, final CharSequence hintText, final OnClickListener listener) { + public void layoutAddToDictionaryHint(final String word, final ViewGroup addToDictionaryStrip, + final int stripWidth, final CharSequence hintText) { final int width = stripWidth - mDividerWidth - mPadding * 2; - final TextView wordView = mWordToSaveView; + final TextView wordView = (TextView)addToDictionaryStrip.findViewById(R.id.word_to_save); wordView.setTextColor(mColorTypedWord); final int wordWidth = (int)(width * mCenterSuggestionWeight); - final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint()); + final CharSequence wordToSave = getEllipsizedText(word, wordWidth, wordView.getPaint()); final float wordScaleX = wordView.getTextScaleX(); - // {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word - // will be extracted at {@link #getAddToDictionaryWord()}. - wordView.setTag(word); - wordView.setText(text); + wordView.setText(wordToSave); wordView.setTextScaleX(wordScaleX); - stripView.addView(wordView); setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); - stripView.addView(mDividerViews.get(0)); - - final TextView leftArrowView = mLeftwardsArrowView; - leftArrowView.setTextColor(mColorAutoCorrect); - leftArrowView.setText(LEFTWARDS_ARROW); - stripView.addView(leftArrowView); - - final TextView hintView = mHintToSaveView; + final TextView hintView = (TextView)addToDictionaryStrip.findViewById( + R.id.hint_add_to_dictionary); hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL); hintView.setTextColor(mColorAutoCorrect); - final int hintWidth = width - wordWidth - leftArrowView.getWidth(); - final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint()); - hintView.setText(hintText); + final int hintWidth = width - wordWidth; + final String hintWithArrow = LEFTWARDS_ARROW + hintText; + final float hintScaleX = getTextScaleX(hintWithArrow, hintWidth, hintView.getPaint()); + hintView.setText(hintWithArrow); hintView.setTextScaleX(hintScaleX); - stripView.addView(hintView); setLayoutWeight( hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT); - - wordView.setOnClickListener(listener); - leftArrowView.setOnClickListener(listener); - hintView.setOnClickListener(listener); - } - - public String getAddToDictionaryWord() { - // String tag is set at - // {@link #layoutAddToDictionaryHint(String,ViewGroup,int,CharSequence,OnClickListener}. - return (String)mWordToSaveView.getTag(); - } - - public boolean isAddToDictionaryShowing(final View v) { - return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView; } private static void setLayoutWeight(final View v, final float weight, final int height) { diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index 88b57f93b..32552ebe7 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -56,6 +56,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick static final boolean DBG = LatinImeLogger.sDBG; private final ViewGroup mSuggestionsStrip; + private final ViewGroup mAddToDictionaryStrip; MainKeyboardView mMainKeyboardView; private final View mMoreSuggestionsContainer; @@ -70,6 +71,32 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; private final SuggestionStripLayoutHelper mLayoutHelper; + private final StripVisibilityGroup mStripVisibilityGroup; + + private static class StripVisibilityGroup { + private final View mSuggestionsStrip; + private final View mAddToDictionaryStrip; + + public StripVisibilityGroup(final View suggestionsStrip, final View addToDictionaryStrip) { + mSuggestionsStrip = suggestionsStrip; + mAddToDictionaryStrip = addToDictionaryStrip; + showSuggestionsStrip(); + } + + public void showSuggestionsStrip() { + mSuggestionsStrip.setVisibility(VISIBLE); + mAddToDictionaryStrip.setVisibility(INVISIBLE); + } + + public void showAddToDictionaryStrip() { + mSuggestionsStrip.setVisibility(INVISIBLE); + mAddToDictionaryStrip.setVisibility(VISIBLE); + } + + public boolean isShowingAddToDictionaryStrip() { + return mAddToDictionaryStrip.getVisibility() == VISIBLE; + } + } /** * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user. @@ -88,6 +115,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick inflater.inflate(R.layout.suggestions_strip, this); mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip); + mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip); + mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mAddToDictionaryStrip); + for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) { final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null); word.setOnClickListener(this); @@ -137,14 +167,16 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } public boolean isShowingAddToDictionaryHint() { - return mSuggestionsStrip.getChildCount() > 0 - && mLayoutHelper.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0)); + return mStripVisibilityGroup.isShowingAddToDictionaryStrip(); } public void showAddToDictionaryHint(final String word, final CharSequence hintText) { - clear(); - mLayoutHelper.layoutAddToDictionaryHint( - word, mSuggestionsStrip, getWidth(), hintText, this); + mLayoutHelper.layoutAddToDictionaryHint(word, mAddToDictionaryStrip, getWidth(), hintText); + // {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word + // will be extracted at {@link #onClick(View)}. + mAddToDictionaryStrip.setTag(word); + mAddToDictionaryStrip.setOnClickListener(this); + mStripVisibilityGroup.showAddToDictionaryStrip(); } public boolean dismissAddToDictionaryHint() { @@ -157,8 +189,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick public void clear() { mSuggestionsStrip.removeAllViews(); - removeAllViews(); - addView(mSuggestionsStrip); + mStripVisibilityGroup.showSuggestionsStrip(); dismissMoreSuggestionsPanel(); } @@ -302,26 +333,26 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick @Override public void onClick(final View view) { - if (mLayoutHelper.isAddToDictionaryShowing(view)) { - mListener.addWordToUserDictionary(mLayoutHelper.getAddToDictionaryWord()); + final Object tag = view.getTag(); + // {@link String} tag is set at {@link #showAddToDictionaryHint(String,CharSequence)}. + if (tag instanceof String) { + final String wordToSave = (String)tag; + mListener.addWordToUserDictionary(wordToSave); clear(); return; } - final Object tag = view.getTag(); - // Integer tag is set at + // {@link Integer} tag is set at // {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and // {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup} - if (!(tag instanceof Integer)) { - return; - } - final int index = (Integer) tag; - if (index >= mSuggestedWords.size()) { - return; + if (tag instanceof Integer) { + final int index = (Integer) tag; + if (index >= mSuggestedWords.size()) { + return; + } + final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index); + mListener.pickSuggestionManually(index, wordInfo); } - - final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index); - mListener.pickSuggestionManually(index, wordInfo); } @Override diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java index 91a63501a..87df013a6 100644 --- a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java @@ -53,6 +53,15 @@ public final class CoordinateUtils { return new int[ELEMENT_SIZE * arraySize]; } + public static int[] newCoordinateArray(final int arraySize, + final int defaultX, final int defaultY) { + final int[] result = new int[ELEMENT_SIZE * arraySize]; + for (int i = 0; i < arraySize; ++i) { + setXYInArray(result, i, defaultX, defaultY); + } + return result; + } + public static int xFromArray(final int[] coordsArray, final int index) { return coordsArray[ELEMENT_SIZE * index + INDEX_X]; } @@ -79,8 +88,4 @@ public final class CoordinateUtils { coordsArray[baseIndex + INDEX_X] = coords[INDEX_X]; coordsArray[baseIndex + INDEX_Y] = coords[INDEX_Y]; } - - public static void copyArray(final int[] destination, final int[] source) { - System.arraycopy(source, 0, destination, 0, source.length); - } } diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java index b3c787e44..306735779 100644 --- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java @@ -26,6 +26,7 @@ import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.AssetFileAddress; import com.android.inputmethod.latin.BinaryDictionaryGetter; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; @@ -368,11 +369,14 @@ public class DictionaryInfoUtils { return dictList; } - @UsedForTesting public static boolean looksValidForDictionaryInsertion(final CharSequence text, final SpacingAndPunctuations spacingAndPunctuations) { if (TextUtils.isEmpty(text)) return false; final int length = text.length(); + // TODO: Make this test "length > Constants.DICTIONARY_MAX_WORD_LENGTH". + if (length >= Constants.DICTIONARY_MAX_WORD_LENGTH) { + return false; + } int i = 0; int digitCount = 0; while (i < length) { diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java index 6f15b11bf..b154623ae 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -348,7 +348,7 @@ public final class StringUtils { boolean hasPeriod = false; int codePoint = 0; while (i > 0) { - codePoint = Character.codePointBefore(text, i); + codePoint = Character.codePointBefore(text, i); if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') { // Handwavy heuristic to see if that's a URL character. Anything between period // and z. This includes all lower- and upper-case ascii letters, period, @@ -387,6 +387,48 @@ public final class StringUtils { return false; } + /** + * Examines the string and returns whether we're inside a double quote. + * + * This is used to decide whether we should put an automatic space before or after a double + * quote character. If we're inside a quotation, then we want to close it, so we want a space + * after and not before. Otherwise, we want to open the quotation, so we want a space before + * and not after. Exception: after a digit, we never want a space because the "inch" or + * "minutes" use cases is dominant after digits. + * In the practice, we determine whether we are in a quotation or not by finding the previous + * double quote character, and looking at whether it's followed by whitespace. If so, that + * was a closing quotation mark, so we're not inside a double quote. If it's not followed + * by whitespace, then it was an opening quotation mark, and we're inside a quotation. + * + * @param text the text to examine. + * @return whether we're inside a double quote. + */ + public static boolean isInsideDoubleQuoteOrAfterDigit(final CharSequence text) { + int i = text.length(); + if (0 == i) return false; + int codePoint = Character.codePointBefore(text, i); + if (Character.isDigit(codePoint)) return true; + int prevCodePoint = 0; + while (i > 0) { + codePoint = Character.codePointBefore(text, i); + if (Constants.CODE_DOUBLE_QUOTE == codePoint) { + // If we see a double quote followed by whitespace, then that + // was a closing quote. + if (Character.isWhitespace(prevCodePoint)) return false; + } + if (Character.isWhitespace(codePoint) && Constants.CODE_DOUBLE_QUOTE == prevCodePoint) { + // If we see a double quote preceded by whitespace, then that + // was an opening quote. No need to continue seeking. + return true; + } + i -= Character.charCount(codePoint); + prevCodePoint = codePoint; + } + // We reached the start of text. If the first char is a double quote, then we're inside + // a double quote. Otherwise we're not. + return Constants.CODE_DOUBLE_QUOTE == codePoint; + } + public static boolean isEmptyStringOrWhiteSpaces(final String s) { final int N = codePointCount(s); for (int i = 0; i < N; ++i) { diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 2a8148d02..b1f54c0b4 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -1117,14 +1117,13 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private static final LogStatement LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL = new LogStatement("LatinIMEOnFinishInputViewInternal", false, false, "isTextTruncated", "text"); - public static void latinIME_onFinishInputViewInternal(final boolean finishingInput, - final int savedSelectionStart, final int savedSelectionEnd, final InputConnection ic) { + public static void latinIME_onFinishInputViewInternal(final boolean finishingInput) { // The finishingInput flag is set in InputMethodService. It is true if called from // doFinishInput(), which can be called as part of doStartInput(). This can happen at times // when the IME is not closing, such as when powering up. The finishinInput flag is false // if called from finishViews(), which is called from hideWindow() and onDestroy(). These // are the situations in which we want to finish up the researchLog. - if (ic != null && !finishingInput) { + if (!finishingInput) { final ResearchLogger researchLogger = getInstance(); // Assume that OUTPUT_ENTIRE_BUFFER is only true when we don't care about privacy (e.g. // during a live user test), so the normal isPotentiallyPrivate and |