diff options
Diffstat (limited to 'java/src/com/android/inputmethod')
19 files changed, 391 insertions, 418 deletions
diff --git a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java index 8a2818508..c937eeeaa 100644 --- a/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/CursorAnchorInfoCompatWrapper.java @@ -41,6 +41,8 @@ public final class CursorAnchorInfoCompatWrapper { // Note that CursorAnchorInfo has been introduced in API level XX (Build.VERSION_CODE.LXX). private static final CompatUtils.ClassWrapper sCursorAnchorInfoClass; + private static final CompatUtils.ToIntMethodWrapper sGetSelectionStartMethod; + private static final CompatUtils.ToIntMethodWrapper sGetSelectionEndMethod; private static final CompatUtils.ToObjectMethodWrapper<RectF> sGetCharacterBoundsMethod; private static final CompatUtils.ToIntMethodWrapper sGetCharacterBoundsFlagsMethod; private static final CompatUtils.ToObjectMethodWrapper<CharSequence> sGetComposingTextMethod; @@ -52,10 +54,14 @@ public final class CursorAnchorInfoCompatWrapper { private static final CompatUtils.ToObjectMethodWrapper<Matrix> sGetMatrixMethod; private static final CompatUtils.ToIntMethodWrapper sGetInsertionMarkerFlagsMethod; - private static int COMPOSING_TEXT_START_DEFAULT = -1; + private static int INVALID_TEXT_INDEX = -1; static { sCursorAnchorInfoClass = CompatUtils.getClassWrapper( "android.view.inputmethod.CursorAnchorInfo"); + sGetSelectionStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod( + "getSelectionStart", INVALID_TEXT_INDEX); + sGetSelectionEndMethod = sCursorAnchorInfoClass.getPrimitiveMethod( + "getSelectionEnd", INVALID_TEXT_INDEX); sGetCharacterBoundsMethod = sCursorAnchorInfoClass.getMethod( "getCharacterBounds", (RectF)null, int.class); sGetCharacterBoundsFlagsMethod = sCursorAnchorInfoClass.getPrimitiveMethod( @@ -63,7 +69,7 @@ public final class CursorAnchorInfoCompatWrapper { sGetComposingTextMethod = sCursorAnchorInfoClass.getMethod( "getComposingText", (CharSequence)null); sGetComposingTextStartMethod = sCursorAnchorInfoClass.getPrimitiveMethod( - "getComposingTextStart", COMPOSING_TEXT_START_DEFAULT); + "getComposingTextStart", INVALID_TEXT_INDEX); sGetInsertionMarkerBaselineMethod = sCursorAnchorInfoClass.getPrimitiveMethod( "getInsertionMarkerBaseline", 0.0f); sGetInsertionMarkerBottomMethod = sCursorAnchorInfoClass.getPrimitiveMethod( @@ -105,6 +111,14 @@ public final class CursorAnchorInfoCompatWrapper { return FakeHolder.sInstance; } + public int getSelectionStart() { + return sGetSelectionStartMethod.invoke(mInstance); + } + + public int getSelectionEnd() { + return sGetSelectionEndMethod.invoke(mInstance); + } + public CharSequence getComposingText() { return sGetComposingTextMethod.invoke(mInstance); } diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 99a34bec7..863a8b7ad 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -98,6 +98,16 @@ public class Key implements Comparable<Key> { private final int mWidth; /** Height of the key, excluding the gap */ private final int mHeight; + /** + * The combined width in pixels of the horizontal gaps belonging to this key, both to the left + * and to the right. I.e., mWidth + mHorizontalGap = total width belonging to the key. + */ + private final int mHorizontalGap; + /** + * The combined height in pixels of the vertical gaps belonging to this key, both above and + * below. I.e., mHeight + mVerticalGap = total height belonging to the key. + */ + private final int mVerticalGap; /** X coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */ private final int mX; /** Y coordinate of the top-left corner of the key in the keyboard layout, excluding the gap. */ @@ -198,8 +208,10 @@ public class Key implements Comparable<Key> { final String hintLabel, final int labelFlags, final int backgroundType, final int x, final int y, final int width, final int height, final int horizontalGap, final int verticalGap) { - mHeight = height - verticalGap; mWidth = width - horizontalGap; + mHeight = height - verticalGap; + mHorizontalGap = horizontalGap; + mVerticalGap = verticalGap; mHintLabel = hintLabel; mLabelFlags = labelFlags; mBackgroundType = backgroundType; @@ -214,7 +226,7 @@ public class Key implements Comparable<Key> { mEnabled = (code != CODE_UNSPECIFIED); mIconId = iconId; // Horizontal gap is divided equally to both sides of the key. - mX = x + horizontalGap / 2; + mX = x + mHorizontalGap / 2; mY = y; mHitBox.set(x, y, x + width + 1, y + height); mKeyVisualAttributes = null; @@ -235,18 +247,21 @@ public class Key implements Comparable<Key> { */ public Key(final String keySpec, final TypedArray keyAttr, final KeyStyle style, final KeyboardParams params, final KeyboardRow row) { - final float horizontalGap = isSpacer() ? 0 : params.mHorizontalGap; + mHorizontalGap = isSpacer() ? 0 : params.mHorizontalGap; + mVerticalGap = params.mVerticalGap; + + final float horizontalGapFloat = mHorizontalGap; final int rowHeight = row.getRowHeight(); - mHeight = rowHeight - params.mVerticalGap; + mHeight = rowHeight - mVerticalGap; final float keyXPos = row.getKeyX(keyAttr); final float keyWidth = row.getKeyWidth(keyAttr, keyXPos); final int keyYPos = row.getKeyY(); // Horizontal gap is divided equally to both sides of the key. - mX = Math.round(keyXPos + horizontalGap / 2); + mX = Math.round(keyXPos + horizontalGapFloat / 2); mY = keyYPos; - mWidth = Math.round(keyWidth - horizontalGap); + mWidth = Math.round(keyWidth - horizontalGapFloat); mHitBox.set(Math.round(keyXPos), keyYPos, Math.round(keyXPos + keyWidth) + 1, keyYPos + rowHeight); // Update row to have current x coordinate. @@ -388,6 +403,8 @@ public class Key implements Comparable<Key> { mIconId = key.mIconId; mWidth = key.mWidth; mHeight = key.mHeight; + mHorizontalGap = key.mHorizontalGap; + mVerticalGap = key.mVerticalGap; mX = key.mX; mY = key.mY; mHitBox.set(key.mHitBox); @@ -788,6 +805,24 @@ public class Key implements Comparable<Key> { } /** + * The combined width in pixels of the horizontal gaps belonging to this key, both above and + * below. I.e., getWidth() + getHorizontalGap() = total width belonging to the key. + * @return Horizontal gap belonging to this key. + */ + public int getHorizontalGap() { + return mHorizontalGap; + } + + /** + * The combined height in pixels of the vertical gaps belonging to this key, both above and + * below. I.e., getHeight() + getVerticalGap() = total height belonging to the key. + * @return Vertical gap belonging to this key. + */ + public int getVerticalGap() { + return mVerticalGap; + } + + /** * Gets the x-coordinate of the top-left corner of the key in pixels, excluding the gap. * @return The x-coordinate of the top-left corner of the key in pixels, excluding the gap. */ diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java index f614b2257..315d36313 100644 --- a/java/src/com/android/inputmethod/keyboard/TextDecorator.java +++ b/java/src/com/android/inputmethod/keyboard/TextDecorator.java @@ -17,23 +17,22 @@ package com.android.inputmethod.keyboard; import android.graphics.Matrix; -import android.graphics.PointF; import android.graphics.RectF; import android.inputmethodservice.InputMethodService; import android.os.Message; import android.text.TextUtils; import android.util.Log; import android.view.View; +import android.view.inputmethod.CursorAnchorInfo; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; import javax.annotation.Nonnull; /** - * A controller class of commit/add-to-dictionary indicator (a.k.a. TextDecorator). This class + * A controller class of the add-to-dictionary indicator (a.k.a. TextDecorator). This class * is designed to be independent of UI subsystems such as {@link View}. All the UI related * operations are delegated to {@link TextDecoratorUi} via {@link TextDecoratorUiOperator}. */ @@ -41,18 +40,22 @@ public class TextDecorator { private static final String TAG = TextDecorator.class.getSimpleName(); private static final boolean DEBUG = false; - private static final int MODE_NONE = 0; - private static final int MODE_COMMIT = 1; - private static final int MODE_ADD_TO_DICTIONARY = 2; + private static final int INVALID_CURSOR_INDEX = -1; - private int mMode = MODE_NONE; + private static final int MODE_MONITOR = 0; + private static final int MODE_WAITING_CURSOR_INDEX = 1; + private static final int MODE_SHOWING_INDICATOR = 2; - private final PointF mLocalOrigin = new PointF(); - private final RectF mRelativeIndicatorBounds = new RectF(); - private final RectF mRelativeComposingTextBounds = new RectF(); + private int mMode = MODE_MONITOR; + + private String mLastComposingText = null; + private RectF mIndicatorBoundsForLastComposingText = new RectF(); + private RectF mComposingTextBoundsForLastComposingText = new RectF(); private boolean mIsFullScreenMode = false; - private SuggestedWordInfo mWaitingWord = null; + private String mWaitingWord = null; + private int mWaitingCursorStart = INVALID_CURSOR_INDEX; + private int mWaitingCursorEnd = INVALID_CURSOR_INDEX; private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null; @Nonnull @@ -63,16 +66,10 @@ public class TextDecorator { public interface Listener { /** - * Called when the user clicks the composing text to commit. - * @param wordInfo the suggested word which the user clicked on. + * Called when the user clicks the indicator to add the word into the dictionary. + * @param word the word which the user clicked on. */ - void onClickComposingTextToCommit(final SuggestedWordInfo wordInfo); - - /** - * Called when the user clicks the composing text to add the word into the dictionary. - * @param wordInfo the suggested word which the user clicked on. - */ - void onClickComposingTextToAddToDictionary(final SuggestedWordInfo wordInfo); + void onClickComposingTextToAddToDictionary(final String word); } public TextDecorator(final Listener listener) { @@ -103,46 +100,19 @@ public class TextDecorator { } /** - * Shows the "Commit" indicator and associates it with the given suggested word. - * - * <p>The effect of {@link #showCommitIndicator(SuggestedWordInfo)} and - * {@link #showAddToDictionaryIndicator(SuggestedWordInfo)} are exclusive to each other. Call - * {@link #reset()} to hide the indicator.</p> - * - * @param wordInfo the suggested word which should be associated with the indicator. This object - * will be passed back in {@link Listener#onClickComposingTextToCommit(SuggestedWordInfo)} - */ - public void showCommitIndicator(final SuggestedWordInfo wordInfo) { - if (mMode == MODE_COMMIT && wordInfo != null && - TextUtils.equals(mWaitingWord.mWord, wordInfo.mWord)) { - // Skip layout for better performance. - return; - } - mWaitingWord = wordInfo; - mMode = MODE_COMMIT; - layoutLater(); - } - - /** - * Shows the "Add to dictionary" indicator and associates it with associating the given - * suggested word. - * - * <p>The effect of {@link #showCommitIndicator(SuggestedWordInfo)} and - * {@link #showAddToDictionaryIndicator(SuggestedWordInfo)} are exclusive to each other. Call - * {@link #reset()} to hide the indicator.</p> + * Shows the "Add to dictionary" indicator and associates it with associating the given word. * - * @param wordInfo the suggested word which should be associated with the indicator. This object - * will be passed back in - * {@link Listener#onClickComposingTextToAddToDictionary(SuggestedWordInfo)}. + * @param word the word which should be associated with the indicator. This object will be + * passed back in {@link Listener#onClickComposingTextToAddToDictionary(String)}. + * @param selectionStart the cursor index (inclusive) when the indicator should be displayed. + * @param selectionEnd the cursor index (exclusive) when the indicator should be displayed. */ - public void showAddToDictionaryIndicator(final SuggestedWordInfo wordInfo) { - if (mMode == MODE_ADD_TO_DICTIONARY && wordInfo != null && - TextUtils.equals(mWaitingWord.mWord, wordInfo.mWord)) { - // Skip layout for better performance. - return; - } - mWaitingWord = wordInfo; - mMode = MODE_ADD_TO_DICTIONARY; + public void showAddToDictionaryIndicator(final String word, final int selectionStart, + final int selectionEnd) { + mWaitingWord = word; + mWaitingCursorStart = selectionStart; + mWaitingCursorEnd = selectionEnd; + mMode = MODE_WAITING_CURSOR_INDEX; layoutLater(); return; } @@ -165,18 +135,19 @@ public class TextDecorator { */ public void reset() { mWaitingWord = null; - mMode = MODE_NONE; - mLocalOrigin.set(0.0f, 0.0f); - mRelativeIndicatorBounds.set(0.0f, 0.0f, 0.0f, 0.0f); - mRelativeComposingTextBounds.set(0.0f, 0.0f, 0.0f, 0.0f); + mMode = MODE_MONITOR; + mWaitingCursorStart = INVALID_CURSOR_INDEX; + mWaitingCursorEnd = INVALID_CURSOR_INDEX; cancelLayoutInternalExpectedly("Resetting internal state."); } /** - * Must be called when the {@link InputMethodService#onUpdateCursorAnchorInfo()} is called. + * Must be called when the {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} + * is called. * * <p>CAVEAT: Currently the input method author is responsible for ignoring - * {@link InputMethodService#onUpdateCursorAnchorInfo()} called in full screen mode.</p> + * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} called in full screen + * mode.</p> * @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}. */ public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) { @@ -185,29 +156,6 @@ public class TextDecorator { layoutImmediately(); } - /** - * Hides indicator if the new composing text doesn't match the expected one. - * - * <p>Calling this method is optional but recommended whenever the new composition is passed to - * the application. The motivation of this method is to reduce the UI latency. With this method, - * we can hide the indicator without waiting the arrival of the - * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} callback, assuming that - * the application accepts the new composing text without any modification. Even if this - * assumption is false, the indicator will be shown again when - * {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is actually received. - * </p> - * - * @param newComposingText the new composing text that is being passed to the application. - */ - public void hideIndicatorIfNecessary(final CharSequence newComposingText) { - if (mMode != MODE_COMMIT && mMode != MODE_ADD_TO_DICTIONARY) { - return; - } - if (!TextUtils.equals(newComposingText, mWaitingWord.mWord)) { - mUiOperator.hideUi(); - } - } - private void cancelLayoutInternalUnexpectedly(final String message) { mUiOperator.hideUi(); Log.d(TAG, message); @@ -232,15 +180,6 @@ public class TextDecorator { } private void layoutMain() { - if (mMode != MODE_COMMIT && mMode != MODE_ADD_TO_DICTIONARY) { - if (mMode == MODE_NONE) { - cancelLayoutInternalExpectedly("Not ready for layouting."); - } else { - cancelLayoutInternalUnexpectedly("Unknown mMode=" + mMode); - } - return; - } - final CursorAnchorInfoCompatWrapper info = mCursorAnchorInfoWrapper; if (info == null) { @@ -254,104 +193,117 @@ public class TextDecorator { } final CharSequence composingText = info.getComposingText(); - if (mMode == MODE_COMMIT) { - if (composingText == null) { - cancelLayoutInternalExpectedly("composingText is null."); - return; - } + if (!TextUtils.isEmpty(composingText)) { final int composingTextStart = info.getComposingTextStart(); final int lastCharRectIndex = composingTextStart + composingText.length() - 1; final RectF lastCharRect = info.getCharacterBounds(lastCharRectIndex); - final int lastCharRectFlag = info.getCharacterBoundsFlags(lastCharRectIndex); + final int lastCharRectFlags = info.getCharacterBoundsFlags(lastCharRectIndex); final boolean hasInvisibleRegionInLastCharRect = - (lastCharRectFlag & CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION) + (lastCharRectFlags & CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION) != 0; if (lastCharRect == null || matrix == null || hasInvisibleRegionInLastCharRect) { mUiOperator.hideUi(); return; } - final RectF segmentStartCharRect = new RectF(lastCharRect); - for (int i = composingText.length() - 2; i >= 0; --i) { - final RectF charRect = info.getCharacterBounds(composingTextStart + i); - if (charRect == null) { + + // Note that the following layout information is fragile, and must be invalidated + // even when surrounding text next to the composing text is changed because it can + // affect how the composing text is rendered. + // TODO: Investigate if we can change the input logic to make the target text + // composing state so that we can retrieve the character bounds reliably. + final String composingTextString = composingText.toString(); + final float top = lastCharRect.top; + final float bottom = lastCharRect.bottom; + float left = lastCharRect.left; + float right = lastCharRect.right; + boolean useRtlLayout = false; + for (int i = composingText.length() - 1; i >= 0; --i) { + final int characterIndex = composingTextStart + i; + final RectF characterBounds = info.getCharacterBounds(characterIndex); + final int characterBoundsFlags = info.getCharacterBoundsFlags(characterIndex); + if (characterBounds == null) { break; } - if (charRect.top != segmentStartCharRect.top) { + if (characterBounds.top != top) { break; } - if (charRect.bottom != segmentStartCharRect.bottom) { + if (characterBounds.bottom != bottom) { break; } - segmentStartCharRect.set(charRect); - } - - mLocalOrigin.set(lastCharRect.right, lastCharRect.top); - mRelativeIndicatorBounds.set(lastCharRect.right, lastCharRect.top, - lastCharRect.right + lastCharRect.height(), lastCharRect.bottom); - mRelativeIndicatorBounds.offset(-mLocalOrigin.x, -mLocalOrigin.y); - - mRelativeIndicatorBounds.set(lastCharRect.right, lastCharRect.top, - lastCharRect.right + lastCharRect.height(), lastCharRect.bottom); - mRelativeIndicatorBounds.offset(-mLocalOrigin.x, -mLocalOrigin.y); - - mRelativeComposingTextBounds.set(segmentStartCharRect.left, segmentStartCharRect.top, - segmentStartCharRect.right, segmentStartCharRect.bottom); - mRelativeComposingTextBounds.offset(-mLocalOrigin.x, -mLocalOrigin.y); - - if (mWaitingWord == null) { - cancelLayoutInternalExpectedly("mWaitingText is null."); - return; - } - if (TextUtils.isEmpty(mWaitingWord.mWord)) { - cancelLayoutInternalExpectedly("mWaitingText.mWord is empty."); - return; + if ((characterBoundsFlags & CursorAnchorInfoCompatWrapper.FLAG_IS_RTL) != 0) { + // This is for both RTL text and bi-directional text. RTL languages usually mix + // RTL characters with LTR characters and in this case we should display the + // indicator on the left, while in LTR languages that normally never happens. + // TODO: Try to come up with a better algorithm. + useRtlLayout = true; + } + left = Math.min(characterBounds.left, left); + right = Math.max(characterBounds.right, right); } - if (!TextUtils.equals(composingText, mWaitingWord.mWord)) { - // This is indeed an expected situation because of the asynchronous nature of - // input method framework in Android. Note that composingText is notified from the - // application, while mWaitingWord.mWord is obtained directly from the InputLogic. - cancelLayoutInternalExpectedly( - "Composing text doesn't match the one we are waiting for."); - return; + mLastComposingText = composingTextString; + mComposingTextBoundsForLastComposingText.set(left, top, right, bottom); + // The height and width of the indicator is the same as the height of the composing + // text. + final float indicatorSize = bottom - top; + mIndicatorBoundsForLastComposingText.set(0.0f, 0.0f, indicatorSize, indicatorSize); + // The horizontal position of the indicator depends on the text direction. + final float indicatorTop = top; + final float indicatorLeft; + if (useRtlLayout) { + indicatorLeft = left - indicatorSize; + } else { + indicatorLeft = right; } - } else { - if (!mIsFullScreenMode && !TextUtils.isEmpty(composingText)) { - // This is an unexpected case. - // TODO: Document this. + mIndicatorBoundsForLastComposingText.offset(indicatorLeft, indicatorTop); + } + + final int selectionStart = info.getSelectionStart(); + final int selectionEnd = info.getSelectionEnd(); + switch (mMode) { + case MODE_MONITOR: mUiOperator.hideUi(); return; - } - // In MODE_ADD_TO_DICTIONARY, we cannot retrieve the character position at all because - // of the lack of composing text. We will use the insertion marker position instead. - if ((info.getInsertionMarkerFlags() & - CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION) != 0) { - mUiOperator.hideUi(); + case MODE_WAITING_CURSOR_INDEX: + if (selectionStart != mWaitingCursorStart || selectionEnd != mWaitingCursorEnd) { + mUiOperator.hideUi(); + return; + } + mMode = MODE_SHOWING_INDICATOR; + break; + case MODE_SHOWING_INDICATOR: + if (selectionStart != mWaitingCursorStart || selectionEnd != mWaitingCursorEnd) { + mUiOperator.hideUi(); + mMode = MODE_MONITOR; + mWaitingCursorStart = INVALID_CURSOR_INDEX; + mWaitingCursorEnd = INVALID_CURSOR_INDEX; + return; + } + break; + default: + cancelLayoutInternalUnexpectedly("Unexpected internal mode=" + mMode); return; - } - final float insertionMarkerHolizontal = info.getInsertionMarkerHorizontal(); - final float insertionMarkerTop = info.getInsertionMarkerTop(); - mLocalOrigin.set(insertionMarkerHolizontal, insertionMarkerTop); } - final RectF indicatorBounds = new RectF(mRelativeIndicatorBounds); - final RectF composingTextBounds = new RectF(mRelativeComposingTextBounds); - indicatorBounds.offset(mLocalOrigin.x, mLocalOrigin.y); - composingTextBounds.offset(mLocalOrigin.x, mLocalOrigin.y); - mUiOperator.layoutUi(mMode == MODE_COMMIT, matrix, indicatorBounds, composingTextBounds); + if (!TextUtils.equals(mLastComposingText, mWaitingWord)) { + cancelLayoutInternalUnexpectedly("mLastComposingText doesn't match mWaitingWord"); + return; + } + + if ((info.getInsertionMarkerFlags() & + CursorAnchorInfoCompatWrapper.FLAG_HAS_INVISIBLE_REGION) != 0) { + mUiOperator.hideUi(); + return; + } + + mUiOperator.layoutUi(matrix, mIndicatorBoundsForLastComposingText, + mComposingTextBoundsForLastComposingText); } private void onClickIndicator() { - if (mWaitingWord == null || TextUtils.isEmpty(mWaitingWord.mWord)) { + if (mMode != MODE_SHOWING_INDICATOR) { return; } - switch (mMode) { - case MODE_COMMIT: - mListener.onClickComposingTextToCommit(mWaitingWord); - break; - case MODE_ADD_TO_DICTIONARY: - mListener.onClickComposingTextToAddToDictionary(mWaitingWord); - break; - } + mListener.onClickComposingTextToAddToDictionary(mWaitingWord); } private final LayoutInvalidator mLayoutInvalidator = new LayoutInvalidator(this); @@ -407,10 +359,7 @@ public class TextDecorator { private final static Listener EMPTY_LISTENER = new Listener() { @Override - public void onClickComposingTextToCommit(SuggestedWordInfo wordInfo) { - } - @Override - public void onClickComposingTextToAddToDictionary(SuggestedWordInfo wordInfo) { + public void onClickComposingTextToAddToDictionary(final String word) { } }; @@ -425,8 +374,7 @@ public class TextDecorator { public void setOnClickListener(Runnable listener) { } @Override - public void layoutUi(boolean isCommitMode, Matrix matrix, RectF indicatorBounds, - RectF composingTextBounds) { + public void layoutUi(Matrix matrix, RectF indicatorBounds, RectF composingTextBounds) { } }; } diff --git a/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java b/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java index 6e215a9ca..b67d17789 100644 --- a/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java +++ b/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java @@ -46,7 +46,6 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator { private static final int VISUAL_DEBUG_HIT_AREA_COLOR = 0x80ff8000; private final RelativeLayout mLocalRootView; - private final CommitIndicatorView mCommitIndicatorView; private final AddToDictionaryIndicatorView mAddToDictionaryIndicatorView; private final PopupWindow mTouchEventWindow; private final View mTouchEventWindowClickListenerView; @@ -73,9 +72,7 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator { mLocalRootView.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); final ViewGroup contentView = getContentView(inputView); - mCommitIndicatorView = new CommitIndicatorView(context); mAddToDictionaryIndicatorView = new AddToDictionaryIndicatorView(context); - mLocalRootView.addView(mCommitIndicatorView); mLocalRootView.addView(mAddToDictionaryIndicatorView); if (contentView != null) { contentView.addView(mLocalRootView); @@ -110,17 +107,15 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator { @Override public void hideUi() { - mCommitIndicatorView.setVisibility(View.GONE); mAddToDictionaryIndicatorView.setVisibility(View.GONE); mTouchEventWindow.dismiss(); } @Override - public void layoutUi(final boolean isCommitMode, final Matrix matrix, - final RectF indicatorBounds, final RectF composingTextBounds) { + public void layoutUi(final Matrix matrix, final RectF indicatorBounds, + final RectF composingTextBounds) { final RectF indicatorBoundsInScreenCoordinates = new RectF(); matrix.mapRect(indicatorBoundsInScreenCoordinates, indicatorBounds); - mCommitIndicatorView.setBounds(indicatorBoundsInScreenCoordinates); mAddToDictionaryIndicatorView.setBounds(indicatorBoundsInScreenCoordinates); final RectF hitAreaBounds = new RectF(composingTextBounds); @@ -133,20 +128,9 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator { mLocalRootView.getLocationOnScreen(originScreen); final int viewOriginX = originScreen[0]; final int viewOriginY = originScreen[1]; - - final View toBeShown; - final View toBeHidden; - if (isCommitMode) { - toBeShown = mCommitIndicatorView; - toBeHidden = mAddToDictionaryIndicatorView; - } else { - toBeShown = mAddToDictionaryIndicatorView; - toBeHidden = mCommitIndicatorView; - } - toBeShown.setX(indicatorBoundsInScreenCoordinates.left - viewOriginX); - toBeShown.setY(indicatorBoundsInScreenCoordinates.top - viewOriginY); - toBeShown.setVisibility(View.VISIBLE); - toBeHidden.setVisibility(View.GONE); + mAddToDictionaryIndicatorView.setX(indicatorBoundsInScreenCoordinates.left - viewOriginX); + mAddToDictionaryIndicatorView.setY(indicatorBoundsInScreenCoordinates.top - viewOriginY); + mAddToDictionaryIndicatorView.setVisibility(View.VISIBLE); if (mTouchEventWindow.isShowing()) { mTouchEventWindow.update((int)hitAreaBoundsInScreenCoordinates.left - viewOriginX, @@ -239,15 +223,6 @@ public final class TextDecoratorUi implements TextDecoratorUiOperator { return windowContentView; } - private static final class CommitIndicatorView extends TextDecoratorUi.IndicatorView { - public CommitIndicatorView(final Context context) { - super(context, R.array.text_decorator_commit_indicator_path, - R.integer.text_decorator_commit_indicator_path_size, - R.color.text_decorator_commit_indicator_background_color, - R.color.text_decorator_commit_indicator_foreground_color); - } - } - private static final class AddToDictionaryIndicatorView extends TextDecoratorUi.IndicatorView { public AddToDictionaryIndicatorView(final Context context) { super(context, R.array.text_decorator_add_to_dictionary_indicator_path, diff --git a/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java b/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java index f84e12d8c..9c0b64ad4 100644 --- a/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java +++ b/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java @@ -44,12 +44,10 @@ public interface TextDecoratorUiOperator { /** * Called when the layout should be updated. - * @param isCommitMode {@code true} if the commit indicator should be shown. Show the - * add-to-dictionary indicator otherwise. * @param matrix The matrix that transforms the local coordinates into the screen coordinates. * @param indicatorBounds The bounding box of the indicator, in local coordinates. * @param composingTextBounds The bounding box of the composing text, in local coordinates. */ - void layoutUi(final boolean isCommitMode, final Matrix matrix, final RectF indicatorBounds, + void layoutUi(final Matrix matrix, final RectF indicatorBounds, final RectF composingTextBounds); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java index 0e3acff84..aae134ec6 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java @@ -3028,16 +3028,16 @@ public final class KeyboardTextsTable { /* Locale ro: Romanian */ private static final String[] TEXTS_ro = { + // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX // U+00E3: "ã" LATIN SMALL LETTER A WITH TILDE - // U+0103: "ă" LATIN SMALL LETTER A WITH BREVE // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS // U+00E6: "æ" LATIN SMALL LETTER AE // U+00E5: "å" LATIN SMALL LETTER A WITH RING ABOVE // U+0101: "ā" LATIN SMALL LETTER A WITH MACRON - /* morekeys_a */ "\u00E2,\u00E3,\u0103,\u00E0,\u00E1,\u00E4,\u00E6,\u00E5,\u0101", + /* morekeys_a */ "\u0103,\u00E2,\u00E3,\u00E0,\u00E1,\u00E4,\u00E6,\u00E5,\u0101", /* morekeys_o ~ */ null, null, null, null, /* ~ morekeys_e */ diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 2e108756e..2ae54348a 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -186,9 +186,9 @@ public final class BinaryDictionary extends Dictionary { long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions, int[][] prevWordCodePointArrays, boolean[] isBeginningOfSentenceArray, - int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores, - int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence, - float[] inOutLanguageWeight); + int prevWordCount, int[] outputSuggestionCount, int[] outputCodePoints, + int[] outputScores, int[] outputIndices, int[] outputTypes, + int[] outputAutoCommitFirstWordConfidence, float[] inOutLanguageWeight); private static native boolean addUnigramEntryNative(long dict, int[] word, int probability, int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence, boolean isNotAWord, boolean isBlacklisted, int timestamp); @@ -295,10 +295,10 @@ public final class BinaryDictionary extends Dictionary { inputPointers.getYCoordinates(), inputPointers.getTimes(), inputPointers.getPointerIds(), session.mInputCodePoints, inputSize, session.mNativeSuggestOptions.getOptions(), session.mPrevWordCodePointArrays, - session.mIsBeginningOfSentenceArray, session.mOutputSuggestionCount, - session.mOutputCodePoints, session.mOutputScores, session.mSpaceIndices, - session.mOutputTypes, session.mOutputAutoCommitFirstWordConfidence, - session.mInputOutputLanguageWeight); + session.mIsBeginningOfSentenceArray, prevWordsInfo.getPrevWordCount(), + session.mOutputSuggestionCount, session.mOutputCodePoints, session.mOutputScores, + session.mSpaceIndices, session.mOutputTypes, + session.mOutputAutoCommitFirstWordConfidence, session.mInputOutputLanguageWeight); if (inOutLanguageWeight != null) { inOutLanguageWeight[0] = session.mInputOutputLanguageWeight[0]; } @@ -358,9 +358,8 @@ public final class BinaryDictionary extends Dictionary { if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { return NOT_A_PROBABILITY; } - final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; - final boolean[] isBeginningOfSentenceArray = - new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + final int[][] prevWordCodePointArrays = new int[prevWordsInfo.getPrevWordCount()][]; + final boolean[] isBeginningOfSentenceArray = new boolean[prevWordsInfo.getPrevWordCount()]; prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); final int[] wordCodePoints = StringUtils.toCodePointArray(word); return getNgramProbabilityNative(mNativeDict, prevWordCodePointArrays, @@ -455,9 +454,8 @@ public final class BinaryDictionary extends Dictionary { if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { return false; } - final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; - final boolean[] isBeginningOfSentenceArray = - new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + final int[][] prevWordCodePointArrays = new int[prevWordsInfo.getPrevWordCount()][]; + final boolean[] isBeginningOfSentenceArray = new boolean[prevWordsInfo.getPrevWordCount()]; prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); final int[] wordCodePoints = StringUtils.toCodePointArray(word); if (!addNgramEntryNative(mNativeDict, prevWordCodePointArrays, @@ -473,9 +471,8 @@ public final class BinaryDictionary extends Dictionary { if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { return false; } - final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; - final boolean[] isBeginningOfSentenceArray = - new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + final int[][] prevWordCodePointArrays = new int[prevWordsInfo.getPrevWordCount()][]; + final boolean[] isBeginningOfSentenceArray = new boolean[prevWordsInfo.getPrevWordCount()]; prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); final int[] wordCodePoints = StringUtils.toCodePointArray(word); if (!removeNgramEntryNative(mNativeDict, prevWordCodePointArrays, diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 2f79c7662..cad9ee7d8 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -22,6 +22,8 @@ import com.android.inputmethod.latin.settings.SettingsValuesForSuggestion; import java.util.ArrayList; import java.util.Locale; +import java.util.Arrays; +import java.util.HashSet; /** * Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key @@ -65,6 +67,14 @@ public abstract class Dictionary { // The locale for this dictionary. May be null if unknown (phony dictionary for example). public final Locale mLocale; + /** + * Set out of the dictionary types listed above that are based on data specific to the user, + * e.g., the user's contacts. + */ + private static final HashSet<String> sUserSpecificDictionaryTypes = + new HashSet(Arrays.asList(new String[] { TYPE_USER_TYPED, TYPE_USER, TYPE_CONTACTS, + TYPE_USER_HISTORY, TYPE_PERSONALIZATION, TYPE_CONTEXTUAL })); + public Dictionary(final String dictType, final Locale locale) { mDictType = dictType; mLocale = locale; @@ -159,6 +169,14 @@ public abstract class Dictionary { } /** + * Whether this dictionary is based on data specific to the user, e.g., the user's contacts. + * @return Whether this dictionary is specific to the user. + */ + public boolean isUserSpecific() { + return sUserSpecificDictionaryTypes.contains(mDictType); + } + + /** * Not a true dictionary. A placeholder used to indicate suggestions that don't come from any * real dictionary. */ diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index eced45ea5..0f09daf86 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -595,8 +595,9 @@ public class DictionaryFacilitator { final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo, final SettingsValuesForSuggestion settingsValuesForSuggestion, final int sessionId) { final DictionaryGroup[] dictionaryGroups = mDictionaryGroups; - final SuggestionResults suggestionResults = - new SuggestionResults(SuggestedWords.MAX_SUGGESTIONS); + final SuggestionResults suggestionResults = new SuggestionResults( + SuggestedWords.MAX_SUGGESTIONS, + prevWordsInfo.mPrevWordsInfo[0].mIsBeginningOfSentence); final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT }; for (final DictionaryGroup dictionaryGroup : dictionaryGroups) { for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index 50a6e48e3..ffd363b5d 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -112,7 +112,7 @@ public final class InputAttributes { mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType); final boolean noMicrophone = mIsPasswordField - || InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS == variation + || InputTypeUtils.isEmailVariation(variation) || InputType.TYPE_TEXT_VARIATION_URI == variation || hasNoMicrophoneKeyOption(); mShouldShowVoiceInputKey = !noMicrophone; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 475782042..69fe6de9a 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -189,9 +189,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final int MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED = 6; private static final int MSG_RESET_CACHES = 7; private static final int MSG_WAIT_FOR_DICTIONARY_LOAD = 8; - private static final int MSG_SHOW_COMMIT_INDICATOR = 9; // Update this when adding new messages - private static final int MSG_LAST = MSG_SHOW_COMMIT_INDICATOR; + private static final int MSG_LAST = MSG_WAIT_FOR_DICTIONARY_LOAD; private static final int ARG1_NOT_GESTURE_INPUT = 0; private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; @@ -202,7 +201,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private int mDelayInMillisecondsToUpdateSuggestions; private int mDelayInMillisecondsToUpdateShiftState; - private int mDelayInMillisecondsToShowCommitIndicator; public UIHandler(final LatinIME ownerInstance) { super(ownerInstance); @@ -218,8 +216,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen R.integer.config_delay_in_milliseconds_to_update_suggestions); mDelayInMillisecondsToUpdateShiftState = res.getInteger( R.integer.config_delay_in_milliseconds_to_update_shift_state); - mDelayInMillisecondsToShowCommitIndicator = res.getInteger( - R.integer.text_decorator_delay_in_milliseconds_to_show_commit_indicator); } @Override @@ -277,14 +273,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen latinIme.getCurrentRecapitalizeState()); } break; - case MSG_SHOW_COMMIT_INDICATOR: - // Protocol of MSG_SET_COMMIT_INDICATOR_ENABLED: - // - what: MSG_SHOW_COMMIT_INDICATOR - // - arg1: not used. - // - arg2: not used. - // - obj: the Runnable object to be called back. - ((Runnable) msg.obj).run(); - break; case MSG_WAIT_FOR_DICTIONARY_LOAD: Log.i(TAG, "Timeout waiting for dictionary load"); break; @@ -385,19 +373,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen obtainMessage(MSG_UPDATE_TAIL_BATCH_INPUT_COMPLETED, suggestedWords).sendToTarget(); } - /** - * Posts a delayed task to show the commit indicator. - * - * <p>Only one task can exist in the queue. When this method is called, any prior task that - * has not yet fired will be canceled.</p> - * @param task the runnable object that will be fired when the delayed task is dispatched. - */ - public void postShowCommitIndicatorTask(final Runnable task) { - removeMessages(MSG_SHOW_COMMIT_INDICATOR); - sendMessageDelayed(obtainMessage(MSG_SHOW_COMMIT_INDICATOR, task), - mDelayInMillisecondsToShowCommitIndicator); - } - // Working variables for the following methods. private boolean mIsOrientationChanging; private boolean mPendingSuccessiveImsCallback; @@ -1516,19 +1491,23 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final boolean isEmptyApplicationSpecifiedCompletions = currentSettingsValues.isApplicationSpecifiedCompletionsOn() && suggestedWords.isEmpty(); - final boolean noSuggestionsToShow = (SuggestedWords.EMPTY == suggestedWords) + final boolean noSuggestionsFromDictionaries = (SuggestedWords.EMPTY == suggestedWords) || suggestedWords.isPunctuationSuggestions() || isEmptyApplicationSpecifiedCompletions; - if (shouldShowImportantNotice && noSuggestionsToShow) { + final boolean isBeginningOfSentencePrediction = (suggestedWords.mInputStyle + == SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION); + final boolean noSuggestionsToOverrideImportantNotice = noSuggestionsFromDictionaries + || isBeginningOfSentencePrediction; + if (shouldShowImportantNotice && noSuggestionsToOverrideImportantNotice) { if (mSuggestionStripView.maybeShowImportantNoticeTitle()) { return; } } if (currentSettingsValues.isSuggestionsEnabledPerUserSettings() - // We should clear suggestions if there is no suggestion to show. - || noSuggestionsToShow - || currentSettingsValues.isApplicationSpecifiedCompletionsOn()) { + || currentSettingsValues.isApplicationSpecifiedCompletionsOn() + // We should clear the contextual strip if there is no suggestion from dictionaries. + || noSuggestionsFromDictionaries) { mSuggestionStripView.setSuggestions(suggestedWords, SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype())); } diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java index db877ab7a..d662051d9 100644 --- a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java +++ b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java @@ -86,33 +86,30 @@ public class PrevWordsInfo { // For simplicity of implementation, elements may also be EMPTY_WORD_INFO transiently after the // WordComposer was reset and before starting a new composing word, but we should never be // calling getSuggetions* in this situation. - public WordInfo[] mPrevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + public final WordInfo[] mPrevWordsInfo; // Construct from the previous word information. public PrevWordsInfo(final WordInfo prevWordInfo) { - mPrevWordsInfo[0] = prevWordInfo; + mPrevWordsInfo = new WordInfo[] { prevWordInfo }; } // Construct from WordInfo array. n-th element represents (n+1)-th previous word's information. public PrevWordsInfo(final WordInfo[] prevWordsInfo) { - for (int i = 0; i < Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM; i++) { - mPrevWordsInfo[i] = - (prevWordsInfo.length > i) ? prevWordsInfo[i] : WordInfo.EMPTY_WORD_INFO; - } + mPrevWordsInfo = prevWordsInfo; } // Create next prevWordsInfo using current prevWordsInfo. public PrevWordsInfo getNextPrevWordsInfo(final WordInfo wordInfo) { - final WordInfo[] prevWordsInfo = new WordInfo[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; + final int nextPrevWordCount = Math.min(Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM, + mPrevWordsInfo.length + 1); + final WordInfo[] prevWordsInfo = new WordInfo[nextPrevWordCount]; prevWordsInfo[0] = wordInfo; - for (int i = 1; i < prevWordsInfo.length; i++) { - prevWordsInfo[i] = mPrevWordsInfo[i - 1]; - } + System.arraycopy(mPrevWordsInfo, 0, prevWordsInfo, 1, prevWordsInfo.length - 1); return new PrevWordsInfo(prevWordsInfo); } public boolean isValid() { - return mPrevWordsInfo[0].isValid(); + return mPrevWordsInfo.length > 0 && mPrevWordsInfo[0].isValid(); } public void outputToArray(final int[][] codePointArrays, @@ -129,6 +126,10 @@ public class PrevWordsInfo { } } + public int getPrevWordCount() { + return mPrevWordsInfo.length; + } + @Override public int hashCode() { return Arrays.hashCode(mPrevWordsInfo); diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index dc00ecc8f..d672430a1 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -252,7 +252,7 @@ public final class RichInputConnection { * See {@link InputConnection#commitText(CharSequence, int)}. */ public void commitText(final CharSequence text, final int newCursorPosition) { - commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT); + commitTextWithBackgroundColor(text, newCursorPosition, Color.TRANSPARENT, text.length()); } /** @@ -265,9 +265,11 @@ public final class RichInputConnection { * the background color. Note that this method specifies {@link BackgroundColorSpan} with * {@link Spanned#SPAN_COMPOSING} flag, meaning that the background color persists until * {@link #finishComposingText()} is called. + * @param coloredTextLength the length of text, in Java chars, which should be rendered with + * the given background color. */ public void commitTextWithBackgroundColor(final CharSequence text, final int newCursorPosition, - final int color) { + final int color, final int coloredTextLength) { if (DEBUG_BATCH_NESTING) checkBatchEdit(); if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); mCommittedTextBeforeComposingText.append(text); @@ -285,7 +287,8 @@ public final class RichInputConnection { mTempObjectForCommitText.clear(); mTempObjectForCommitText.append(text); final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(color); - mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, text.length(), + final int spanLength = Math.min(coloredTextLength, text.length()); + mTempObjectForCommitText.setSpan(backgroundColorSpan, 0, spanLength, Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); mIC.commitText(mTempObjectForCommitText, newCursorPosition); mLastCommittedTextHasBackgroundColor = true; diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 4ad5ba65e..1ecc995b2 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -188,8 +188,14 @@ public final class Suggest { suggestionsList = suggestionsContainer; } - final int inputStyle = resultsArePredictions ? SuggestedWords.INPUT_STYLE_PREDICTION : - inputStyleIfNotPrediction; + final int inputStyle; + if (resultsArePredictions) { + inputStyle = suggestionResults.mIsBeginningOfSentence + ? SuggestedWords.INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION + : SuggestedWords.INPUT_STYLE_PREDICTION; + } else { + inputStyle = inputStyleIfNotPrediction; + } callback.onGetSuggestedWords(new SuggestedWords(suggestionsList, suggestionResults.mRawSuggestions, // TODO: this first argument is lying. If this is a whitelisted word which is an @@ -244,6 +250,8 @@ public final class Suggest { // 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). + // Note that because this method is never used to get predictions, there is no need to + // modify inputType such in getSuggestedWordsForNonBatchInput. callback.onGetSuggestedWords(new SuggestedWords(suggestionsContainer, suggestionResults.mRawSuggestions, true /* typedWordValid */, diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index dcfaa3f6d..3eefafc1f 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -39,6 +39,7 @@ public class SuggestedWords { public static final int INPUT_STYLE_APPLICATION_SPECIFIED = 4; public static final int INPUT_STYLE_RECORRECTION = 5; public static final int INPUT_STYLE_PREDICTION = 6; + public static final int INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION = 7; // The maximum number of suggestions available. public static final int MAX_SUGGESTIONS = 18; @@ -80,10 +81,9 @@ public class SuggestedWords { final int inputStyle, final int sequenceNumber) { this(suggestedWordInfoList, rawSuggestions, - (suggestedWordInfoList.isEmpty() || INPUT_STYLE_PREDICTION == inputStyle) ? null + (suggestedWordInfoList.isEmpty() || isPrediction(inputStyle)) ? null : suggestedWordInfoList.get(INDEX_OF_TYPED_WORD).mWord, - typedWordValid, willAutoCorrect, isObsoleteSuggestions, inputStyle, - sequenceNumber); + typedWordValid, willAutoCorrect, isObsoleteSuggestions, inputStyle, sequenceNumber); } public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, @@ -180,6 +180,7 @@ public class SuggestedWords { return "SuggestedWords:" + " mTypedWordValid=" + mTypedWordValid + " mWillAutoCorrect=" + mWillAutoCorrect + + " mInputStyle=" + mInputStyle + " words=" + Arrays.toString(mSuggestedWordInfoList.toArray()); } @@ -386,8 +387,13 @@ public class SuggestedWords { } } + private static boolean isPrediction(final int inputStyle) { + return INPUT_STYLE_PREDICTION == inputStyle + || INPUT_STYLE_BEGINNING_OF_SENTENCE_PREDICTION == inputStyle; + } + public boolean isPrediction() { - return INPUT_STYLE_PREDICTION == mInputStyle; + return isPrediction(mInputStyle); } // SuggestedWords is an immutable object, as much as possible. We must not just remove diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 0942c078f..1f0339c48 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -28,6 +28,7 @@ import android.util.Log; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorInfo; import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper; @@ -91,12 +92,8 @@ public final class InputLogic { private final TextDecorator mTextDecorator = new TextDecorator(new TextDecorator.Listener() { @Override - public void onClickComposingTextToCommit(SuggestedWordInfo wordInfo) { - mLatinIME.pickSuggestionManually(wordInfo); - } - @Override - public void onClickComposingTextToAddToDictionary(SuggestedWordInfo wordInfo) { - mLatinIME.addWordToUserDictionary(wordInfo.mWord); + public void onClickComposingTextToAddToDictionary(final String word) { + mLatinIME.addWordToUserDictionary(word); mLatinIME.dismissAddToDictionaryHint(); } }); @@ -171,6 +168,7 @@ public final class InputLogic { mConnection.requestCursorUpdates(true /* enableMonitor */, true /* requestImmediateCallback */); } + mTextDecorator.reset(); } } @@ -334,17 +332,8 @@ public final class InputLogic { } final boolean shouldShowAddToDictionaryHint = shouldShowAddToDictionaryHint(suggestionInfo); - final boolean shouldShowAddToDictionaryIndicator = - shouldShowAddToDictionaryHint && settingsValues.mShouldShowUiToAcceptTypedWord; - final int backgroundColor; - if (shouldShowAddToDictionaryIndicator) { - backgroundColor = settingsValues.mTextHighlightColorForAddToDictionaryIndicator; - } else { - backgroundColor = Color.TRANSPARENT; - } - commitChosenWordWithBackgroundColor(settingsValues, suggestion, - LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR, - backgroundColor); + commitChosenWord(settingsValues, suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK, + LastComposedWord.NOT_A_SEPARATOR); mConnection.endBatchEdit(); // Don't allow cancellation of manual pick mLastComposedWord.deactivate(); @@ -359,9 +348,6 @@ public final class InputLogic { // That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE. handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE); } - if (shouldShowAddToDictionaryIndicator) { - mTextDecorator.showAddToDictionaryIndicator(suggestionInfo); - } StatsUtils.onPickSuggestionManually(mSuggestedWords, suggestionInfo); return inputTransaction; @@ -433,6 +419,9 @@ public final class InputLogic { mRecapitalizeStatus.enable(); // We moved the cursor and need to invalidate the indicator right now. mTextDecorator.reset(); + // Remaining background color that was used for the add-to-dictionary indicator should be + // removed. + mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); // We moved the cursor. If we are touching a word, we need to resume suggestion. mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */, true /* shouldDelay */); @@ -511,7 +500,9 @@ public final class InputLogic { handler.cancelUpdateSuggestionStrip(); ++mAutoCommitSequenceNumber; mConnection.beginBatchEdit(); - if (mWordComposer.isComposingWord()) { + if (!mWordComposer.isComposingWord()) { + mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); + } else { if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the batch input at the current cursor position. @@ -630,42 +621,6 @@ public final class InputLogic { } mSuggestedWords = suggestedWords; final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect; - if (shouldShowCommitIndicator(suggestedWords, settingsValues)) { - // typedWordInfo is never null here. - final int textBackgroundColor = settingsValues.mTextHighlightColorForCommitIndicator; - final SuggestedWordInfo typedWordInfo = suggestedWords.getTypedWordInfoOrNull(); - handler.postShowCommitIndicatorTask(new Runnable() { - @Override - public void run() { - // TODO: This needs to be refactored to ensure that mWordComposer is accessed - // only from the UI thread. - if (!mWordComposer.isComposingWord()) { - mTextDecorator.reset(); - return; - } - final SuggestedWordInfo currentTypedWordInfo = - mSuggestedWords.getTypedWordInfoOrNull(); - if (currentTypedWordInfo == null) { - mTextDecorator.reset(); - return; - } - if (!currentTypedWordInfo.equals(typedWordInfo)) { - // Suggested word has been changed. This task is obsolete. - mTextDecorator.reset(); - return; - } - // TODO: As with the above TODO comment, this operation must be performed only - // on the UI thread too. Needs to be refactored. - setComposingTextInternalWithBackgroundColor(typedWordInfo.mWord, - 1 /* newCursorPosition */, textBackgroundColor); - mTextDecorator.showCommitIndicator(typedWordInfo); - } - }); - } else { - // Note: It is OK to not cancel previous postShowCommitIndicatorTask() here. Having a - // cancellation mechanism could improve performance a bit though. - mTextDecorator.reset(); - } // Put a blue underline to a word in TextView which will be auto-corrected. if (mIsAutoCorrectionIndicatorOn != newAutoCorrectionIndicator @@ -843,13 +798,14 @@ public final class InputLogic { final InputTransaction inputTransaction, // TODO: remove this argument final LatinIME.UIHandler handler) { - // In case the "add to dictionary" hint was still displayed. - // TODO: Do we really need to check if we have composing text here? - if (!mWordComposer.isComposingWord() && - mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) { - mSuggestionStripViewAccessor.dismissAddToDictionaryHint(); + if (!mWordComposer.isComposingWord()) { mConnection.removeBackgroundColorFromHighlightedTextIfNecessary(); - mTextDecorator.reset(); + // In case the "add to dictionary" hint was still displayed. + // TODO: Do we really need to check if we have composing text here? + if (mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) { + mSuggestionStripViewAccessor.dismissAddToDictionaryHint(); + mTextDecorator.reset(); + } } final int codePoint = event.mCodePoint; @@ -1108,7 +1064,7 @@ public final class InputLogic { inputTransaction.setRequiresUpdateSuggestions(); } else { if (mLastComposedWord.canRevertCommit()) { - revertCommit(inputTransaction); + revertCommit(inputTransaction, inputTransaction.mSettingsValues); StatsUtils.onRevertAutoCorrect(); return; } @@ -1609,14 +1565,19 @@ public final class InputLogic { * This is triggered upon pressing backspace just after a commit with auto-correction. * * @param inputTransaction The transaction in progress. + * @param settingsValues the current values of the settings. */ - private void revertCommit(final InputTransaction inputTransaction) { + private void revertCommit(final InputTransaction inputTransaction, + final SettingsValues settingsValues) { final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord; + final String originallyTypedWordString = + originallyTypedWord != null ? originallyTypedWord.toString() : ""; final CharSequence committedWord = mLastComposedWord.mCommittedWord; final String committedWordString = committedWord.toString(); final int cancelLength = committedWord.length(); + final String separatorString = mLastComposedWord.mSeparatorString; // We want java chars, not codepoints for the following. - final int separatorLength = mLastComposedWord.mSeparatorString.length(); + final int separatorLength = separatorString.length(); // TODO: should we check our saved separator against the actual contents of the text view? final int deleteLength = cancelLength + separatorLength; if (DebugFlags.DEBUG_ENABLED) { @@ -1635,7 +1596,7 @@ public final class InputLogic { if (!TextUtils.isEmpty(committedWord)) { mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString); } - final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString; + final String stringToCommit = originallyTypedWord + separatorString; final SpannableString textToCommit = new SpannableString(stringToCommit); if (committedWord instanceof SpannableString) { final SpannableString committedWordWithSuggestionSpans = (SpannableString)committedWord; @@ -1672,23 +1633,53 @@ public final class InputLogic { suggestions.toArray(new String[suggestions.size()]), 0 /* flags */), 0 /* start */, lastCharIndex /* end */, 0 /* flags */); } + + final boolean shouldShowAddToDictionaryForTypedWord = + shouldShowAddToDictionaryForTypedWord(mLastComposedWord, settingsValues); + if (inputTransaction.mSettingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces) { // For languages with spaces, we revert to the typed string, but the cursor is still // after the separator so we don't resume suggestions. If the user wants to correct // the word, they have to press backspace again. - mConnection.commitText(textToCommit, 1); + if (shouldShowAddToDictionaryForTypedWord) { + mConnection.commitTextWithBackgroundColor(textToCommit, 1, + settingsValues.mTextHighlightColorForAddToDictionaryIndicator, + originallyTypedWordString.length()); + } else { + mConnection.commitText(textToCommit, 1); + } } else { // For languages without spaces, we revert the typed string but the cursor is flush // with the typed word, so we need to resume suggestions right away. final int[] codePoints = StringUtils.toCodePointArray(stringToCommit); mWordComposer.setComposingWord(codePoints, mLatinIME.getCoordinatesForCurrentKeyboard(codePoints)); - setComposingTextInternal(textToCommit, 1); + if (shouldShowAddToDictionaryForTypedWord) { + setComposingTextInternalWithBackgroundColor(textToCommit, 1, + settingsValues.mTextHighlightColorForAddToDictionaryIndicator, + originallyTypedWordString.length()); + } else { + setComposingTextInternal(textToCommit, 1); + } } // Don't restart suggestion yet. We'll restart if the user deletes the separator. mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; - // We have a separator between the word and the cursor: we should show predictions. - inputTransaction.setRequiresUpdateSuggestions(); + + if (shouldShowAddToDictionaryForTypedWord) { + // Due to the API limitation as of L, we cannot reliably retrieve the reverted text + // when the separator causes line breaking. Until this API limitation is addressed in + // the framework, show the indicator only when the separator doesn't contain + // line-breaking characters. + if (!StringUtils.hasLineBreakCharacter(separatorString)) { + mTextDecorator.showAddToDictionaryIndicator(originallyTypedWordString, + mConnection.getExpectedSelectionStart(), + mConnection.getExpectedSelectionEnd()); + } + mSuggestionStripViewAccessor.showAddToDictionaryHint(originallyTypedWordString); + } else { + // We have a separator between the word and the cursor: we should show predictions. + inputTransaction.setRequiresUpdateSuggestions(); + } } /** @@ -2114,9 +2105,7 @@ public final class InputLogic { } /** - * Commits the chosen word to the text field and saves it for later retrieval. This is a - * synonym of {@code commitChosenWordWithBackgroundColor(settingsValues, chosenWord, - * commitType, separatorString, Color.TRANSPARENT}. + * Commits the chosen word to the text field and saves it for later retrieval. * * @param settingsValues the current values of the settings. * @param chosenWord the word we want to commit. @@ -2125,23 +2114,6 @@ public final class InputLogic { */ private void commitChosenWord(final SettingsValues settingsValues, final String chosenWord, final int commitType, final String separatorString) { - commitChosenWordWithBackgroundColor(settingsValues, chosenWord, commitType, separatorString, - Color.TRANSPARENT); - } - - /** - * Commits the chosen word to the text field and saves it for later retrieval. - * - * @param settingsValues the current values of the settings. - * @param chosenWord the word we want to commit. - * @param commitType the type of the commit, as one of LastComposedWord.COMMIT_TYPE_* - * @param separatorString the separator that's causing the commit, or NOT_A_SEPARATOR if none. - * @param backgroundColor the background color to be specified with the committed text. Pass - * {@link Color#TRANSPARENT} to not specify the background color. - */ - private void commitChosenWordWithBackgroundColor(final SettingsValues settingsValues, - final String chosenWord, final int commitType, final String separatorString, - final int backgroundColor) { final SuggestedWords suggestedWords = mSuggestedWords; final CharSequence chosenWordWithSuggestions = SuggestionSpanUtils.getTextWithSuggestionSpan(mLatinIME, chosenWord, @@ -2151,7 +2123,7 @@ public final class InputLogic { // information from the 1st previous word. final PrevWordsInfo prevWordsInfo = mConnection.getPrevWordsInfoFromNthPreviousWord( settingsValues.mSpacingAndPunctuations, mWordComposer.isComposingWord() ? 2 : 1); - mConnection.commitTextWithBackgroundColor(chosenWordWithSuggestions, 1, backgroundColor); + mConnection.commitText(chosenWordWithSuggestions, 1); // Add the word to the user history dictionary performAdditionToUserHistoryDictionary(settingsValues, chosenWord, prevWordsInfo); // TODO: figure out here if this is an auto-correct or if the best word is actually @@ -2235,7 +2207,7 @@ public final class InputLogic { private void setComposingTextInternal(final CharSequence newComposingText, final int newCursorPosition) { setComposingTextInternalWithBackgroundColor(newComposingText, newCursorPosition, - Color.TRANSPARENT); + Color.TRANSPARENT, newComposingText.length()); } /** @@ -2251,9 +2223,11 @@ public final class InputLogic { * @param newCursorPosition the new cursor position * @param backgroundColor the background color to be set to the composing text. Set * {@link Color#TRANSPARENT} to disable the background color. + * @param coloredTextLength the length of text, in Java chars, which should be rendered with + * the given background color. */ private void setComposingTextInternalWithBackgroundColor(final CharSequence newComposingText, - final int newCursorPosition, final int backgroundColor) { + final int newCursorPosition, final int backgroundColor, final int coloredTextLength) { final CharSequence composingTextToBeSet; if (backgroundColor == Color.TRANSPARENT) { composingTextToBeSet = newComposingText; @@ -2261,7 +2235,8 @@ public final class InputLogic { final SpannableString spannable = new SpannableString(newComposingText); final BackgroundColorSpan backgroundColorSpan = new BackgroundColorSpan(backgroundColor); - spannable.setSpan(backgroundColorSpan, 0, spannable.length(), + final int spanLength = Math.min(coloredTextLength, spannable.length()); + spannable.setSpan(backgroundColorSpan, 0, spanLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); composingTextToBeSet = spannable; } @@ -2283,7 +2258,8 @@ public final class InputLogic { } /** - * Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo} is called. + * Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo(CursorAnchorInfo)} is + * called. * @param info The wrapper object with which we can access cursor/anchor info. */ public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) { @@ -2307,12 +2283,12 @@ public final class InputLogic { } /** - * Returns whether the commit indicator should be shown or not. - * @param suggestedWords the suggested word that is being displayed. + * Returns whether the add to dictionary indicator should be shown or not. + * @param lastComposedWord the last composed word information. * @param settingsValues the current settings value. * @return {@code true} if the commit indicator should be shown. */ - private boolean shouldShowCommitIndicator(final SuggestedWords suggestedWords, + private boolean shouldShowAddToDictionaryForTypedWord(final LastComposedWord lastComposedWord, final SettingsValues settingsValues) { if (!mConnection.isCursorAnchorInfoMonitorEnabled()) { // We cannot help in this case because we are heavily relying on this new API. @@ -2321,24 +2297,16 @@ public final class InputLogic { if (!settingsValues.mShouldShowUiToAcceptTypedWord) { return false; } - final SuggestedWordInfo typedWordInfo = suggestedWords.getTypedWordInfoOrNull(); - if (typedWordInfo == null) { + if (TextUtils.isEmpty(lastComposedWord.mTypedWord)) { return false; } - if (suggestedWords.mInputStyle != SuggestedWords.INPUT_STYLE_TYPING){ + if (TextUtils.equals(lastComposedWord.mTypedWord, lastComposedWord.mCommittedWord)) { return false; } - if (settingsValues.mShowCommitIndicatorOnlyForAutoCorrection - && !suggestedWords.mWillAutoCorrect) { + if (!mDictionaryFacilitator.isUserDictionaryEnabled()) { return false; } - // TODO: Calling shouldShowAddToDictionaryHint(typedWordInfo) multiple times should be fine - // in terms of performance, but we can do better. One idea is to make SuggestedWords include - // a boolean that tells whether the word is a dictionary word or not. - if (settingsValues.mShowCommitIndicatorOnlyForOutOfVocabulary - && !shouldShowAddToDictionaryHint(typedWordInfo)) { - return false; - } - return true; + return !mDictionaryFacilitator.isValidWord(lastComposedWord.mTypedWord, + true /* ignoreCase */); } } diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index 57610221c..3339ab57f 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -102,10 +102,7 @@ public class SettingsValues { new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE]; // TextDecorator - public final int mTextHighlightColorForCommitIndicator; public final int mTextHighlightColorForAddToDictionaryIndicator; - public final boolean mShowCommitIndicatorOnlyForAutoCorrection; - public final boolean mShowCommitIndicatorOnlyForOutOfVocabulary; // Debug settings public final boolean mIsInternal; @@ -183,12 +180,6 @@ public class SettingsValues { mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs); AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray( prefs, mAdditionalFeaturesSettingValues); - mShowCommitIndicatorOnlyForAutoCorrection = res.getBoolean( - R.bool.text_decorator_only_for_auto_correction); - mShowCommitIndicatorOnlyForOutOfVocabulary = res.getBoolean( - R.bool.text_decorator_only_for_out_of_vocabulary); - mTextHighlightColorForCommitIndicator = res.getColor( - R.color.text_decorator_commit_indicator_text_highlight_color); mTextHighlightColorForAddToDictionaryIndicator = res.getColor( R.color.text_decorator_add_to_dictionary_indicator_text_highlight_color); mIsInternal = Settings.isInternal(prefs); @@ -443,12 +434,6 @@ public class SettingsValues { sb.append("" + (null == awu ? "null" : awu.toString())); sb.append("\n mAdditionalFeaturesSettingValues = "); sb.append("" + Arrays.toString(mAdditionalFeaturesSettingValues)); - sb.append("\n mShowCommitIndicatorOnlyForAutoCorrection = "); - sb.append("" + mShowCommitIndicatorOnlyForAutoCorrection); - sb.append("\n mShowCommitIndicatorOnlyForOutOfVocabulary = "); - sb.append("" + mShowCommitIndicatorOnlyForOutOfVocabulary); - sb.append("\n mTextHighlightColorForCommitIndicator = "); - sb.append("" + mTextHighlightColorForCommitIndicator); sb.append("\n mTextHighlightColorForAddToDictionaryIndicator = "); sb.append("" + mTextHighlightColorForAddToDictionaryIndicator); sb.append("\n mIsInternal = "); diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java index 55557de9d..bbcef990d 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -37,6 +37,14 @@ public final class StringUtils { private static final String EMPTY_STRING = ""; + private static final char CHAR_LINE_FEED = 0X000A; + private static final char CHAR_VERTICAL_TAB = 0X000B; + private static final char CHAR_FORM_FEED = 0X000C; + private static final char CHAR_CARRIAGE_RETURN = 0X000D; + private static final char CHAR_NEXT_LINE = 0X0085; + private static final char CHAR_LINE_SEPARATOR = 0X2028; + private static final char CHAR_PARAGRAPH_SEPARATOR = 0X2029; + private StringUtils() { // This utility class is not publicly instantiable. } @@ -594,4 +602,30 @@ public final class StringUtils { return sb + "]"; } } + + /** + * Returns whether the last composed word contains line-breaking character (e.g. CR or LF). + * @param text the text to be examined. + * @return {@code true} if the last composed word contains line-breaking separator. + */ + @UsedForTesting + public static boolean hasLineBreakCharacter(final String text) { + if (TextUtils.isEmpty(text)) { + return false; + } + for (int i = text.length() - 1; i >= 0; --i) { + final char c = text.charAt(i); + switch (c) { + case CHAR_LINE_FEED: + case CHAR_VERTICAL_TAB: + case CHAR_FORM_FEED: + case CHAR_CARRIAGE_RETURN: + case CHAR_NEXT_LINE: + case CHAR_LINE_SEPARATOR: + case CHAR_PARAGRAPH_SEPARATOR: + return true; + } + } + return false; + } } diff --git a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java index eaa5743d4..d6f644228 100644 --- a/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java +++ b/java/src/com/android/inputmethod/latin/utils/SuggestionResults.java @@ -22,7 +22,6 @@ import com.android.inputmethod.latin.define.ProductionFlags; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; -import java.util.Locale; import java.util.TreeSet; /** @@ -31,14 +30,17 @@ import java.util.TreeSet; */ public final class SuggestionResults extends TreeSet<SuggestedWordInfo> { public final ArrayList<SuggestedWordInfo> mRawSuggestions; + // TODO: Instead of a boolean , we may want to include the context of this suggestion results, + // such as {@link PrevWordsInfo}. + public final boolean mIsBeginningOfSentence; private final int mCapacity; - public SuggestionResults(final int capacity) { - this(sSuggestedWordInfoComparator, capacity); + public SuggestionResults(final int capacity, final boolean isBeginningOfSentence) { + this(sSuggestedWordInfoComparator, capacity, isBeginningOfSentence); } - public SuggestionResults(final Comparator<SuggestedWordInfo> comparator, - final int capacity) { + private SuggestionResults(final Comparator<SuggestedWordInfo> comparator, + final int capacity, final boolean isBeginningOfSentence) { super(comparator); mCapacity = capacity; if (ProductionFlags.INCLUDE_RAW_SUGGESTIONS) { @@ -46,6 +48,7 @@ public final class SuggestionResults extends TreeSet<SuggestedWordInfo> { } else { mRawSuggestions = null; } + mIsBeginningOfSentence = isBeginningOfSentence; } @Override |