aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
authorYohei Yukawa <yukawa@google.com>2014-08-24 20:08:55 -0700
committerYohei Yukawa <yukawa@google.com>2014-08-26 21:20:27 -0700
commitbea17c49ec23bf0f646cb548445c7756aa50d233 (patch)
tree7a58b51ad1ea0bec791b167d6209bb3c3dc90ec8 /java/src
parent8380f921f7edaeea2033a1e967a14941400fe246 (diff)
downloadlatinime-bea17c49ec23bf0f646cb548445c7756aa50d233.tar.gz
latinime-bea17c49ec23bf0f646cb548445c7756aa50d233.tar.xz
latinime-bea17c49ec23bf0f646cb548445c7756aa50d233.zip
Introduce commit/add-to-dictionary indicators
This CL introduces commit/add-to-dictionary indicators. Note that the text is not yet highlighted when the commit indicator is displayed. It will be addressed in subsequent CLs. Change-Id: I7e9b0fcfdc0776a50a1d8cfb41ee0add813317dd
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/keyboard/TextDecorator.java425
-rw-r--r--java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java259
-rw-r--r--java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java55
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java49
-rw-r--r--java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java144
-rw-r--r--java/src/com/android/inputmethod/latin/settings/SettingsValues.java22
6 files changed, 939 insertions, 15 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/TextDecorator.java b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
new file mode 100644
index 000000000..0eb8b443a
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/TextDecorator.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+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
+ * 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}.
+ */
+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 int mMode = MODE_NONE;
+
+ private final PointF mLocalOrigin = new PointF();
+ private final RectF mRelativeIndicatorBounds = new RectF();
+ private final RectF mRelativeComposingTextBounds = new RectF();
+
+ private boolean mIsFullScreenMode = false;
+ private SuggestedWordInfo mWaitingWord = null;
+ private CursorAnchorInfoCompatWrapper mCursorAnchorInfoWrapper = null;
+
+ @Nonnull
+ private final Listener mListener;
+
+ @Nonnull
+ private TextDecoratorUiOperator mUiOperator = EMPTY_UI_OPERATOR;
+
+ public interface Listener {
+ /**
+ * Called when the user clicks the composing text to commit.
+ * @param wordInfo the suggested 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);
+ }
+
+ public TextDecorator(final Listener listener) {
+ mListener = (listener != null) ? listener : EMPTY_LISTENER;
+ }
+
+ /**
+ * Sets the UI operator for {@link TextDecorator}. Any user visible operations will be
+ * delegated to the associated UI operator.
+ * @param uiOperator the UI operator to be associated.
+ */
+ public void setUiOperator(final TextDecoratorUiOperator uiOperator) {
+ mUiOperator.disposeUi();
+ mUiOperator = uiOperator;
+ mUiOperator.setOnClickListener(getOnClickHandler());
+ }
+
+ private final Runnable mDefaultOnClickHandler = new Runnable() {
+ @Override
+ public void run() {
+ onClickIndicator();
+ }
+ };
+
+ @UsedForTesting
+ final Runnable getOnClickHandler() {
+ return mDefaultOnClickHandler;
+ }
+
+ /**
+ * 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>
+ *
+ * @param wordInfo the suggested word which should be associated with the indicator. This object
+ * will be passed back in
+ * {@link Listener#onClickComposingTextToAddToDictionary(SuggestedWordInfo)}.
+ */
+ 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;
+ layoutLater();
+ return;
+ }
+
+ /**
+ * Must be called when the input method is about changing to for from the full screen mode.
+ * @param fullScreenMode {@code true} if the input method is entering the full screen mode.
+ * {@code false} is the input method is finishing the full screen mode.
+ */
+ public void notifyFullScreenMode(final boolean fullScreenMode) {
+ final boolean currentFullScreenMode = mIsFullScreenMode;
+ if (!currentFullScreenMode && fullScreenMode) {
+ // Currently full screen mode is not supported.
+ // TODO: Support full screen mode.
+ hideIndicator();
+ }
+ mIsFullScreenMode = fullScreenMode;
+ }
+
+ /**
+ * Resets previous requests and makes indicator invisible.
+ */
+ 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);
+ cancelLayoutInternalExpectedly("Resetting internal state.");
+ }
+
+ /**
+ * Must be called when the {@link InputMethodService#onUpdateCursorAnchorInfo()} is called.
+ *
+ * <p>CAVEAT: Currently the input method author is responsible for ignoring
+ * {@link InputMethodService#onUpdateCursorAnchorInfo()} called in full screen mode.</p>
+ * @param info the compatibility wrapper object for the received {@link CursorAnchorInfo}.
+ */
+ public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
+ if (mIsFullScreenMode) {
+ // TODO: Consider to call InputConnection#requestCursorAnchorInfo to disable the
+ // event callback to suppress unnecessary event callbacks.
+ return;
+ }
+ mCursorAnchorInfoWrapper = info;
+ // Do not use layoutLater() to minimize the latency.
+ layoutImmediately();
+ }
+
+ private void hideIndicator() {
+ mUiOperator.hideUi();
+ }
+
+ private void cancelLayoutInternalUnexpectedly(final String message) {
+ hideIndicator();
+ Log.d(TAG, message);
+ }
+
+ private void cancelLayoutInternalExpectedly(final String message) {
+ hideIndicator();
+ if (DEBUG) {
+ Log.d(TAG, message);
+ }
+ }
+
+ private void layoutLater() {
+ mLayoutInvalidator.invalidateLayout();
+ }
+
+
+ private void layoutImmediately() {
+ // Clear pending layout requests.
+ mLayoutInvalidator.cancelInvalidateLayout();
+ layoutMain();
+ }
+
+ private void layoutMain() {
+ if (mIsFullScreenMode) {
+ cancelLayoutInternalUnexpectedly("Full screen mode isn't yet supported.");
+ return;
+ }
+
+ 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) {
+ cancelLayoutInternalExpectedly("CursorAnchorInfo isn't available.");
+ return;
+ }
+
+ final Matrix matrix = info.getMatrix();
+ if (matrix == null) {
+ cancelLayoutInternalUnexpectedly("Matrix is null");
+ }
+
+ final CharSequence composingText = info.getComposingText();
+ if (mMode == MODE_COMMIT) {
+ if (composingText == null) {
+ cancelLayoutInternalExpectedly("composingText is null.");
+ return;
+ }
+ final int composingTextStart = info.getComposingTextStart();
+ final int lastCharRectIndex = composingTextStart + composingText.length() - 1;
+ final RectF lastCharRect = info.getCharacterRect(lastCharRectIndex);
+ final int lastCharRectFlag = info.getCharacterRectFlags(lastCharRectIndex);
+ final int lastCharRectType =
+ lastCharRectFlag & CursorAnchorInfoCompatWrapper.CHARACTER_RECT_TYPE_MASK;
+ if (lastCharRect == null || matrix == null || lastCharRectType !=
+ CursorAnchorInfoCompatWrapper.CHARACTER_RECT_TYPE_FULLY_VISIBLE) {
+ hideIndicator();
+ return;
+ }
+ final RectF segmentStartCharRect = new RectF(lastCharRect);
+ for (int i = composingText.length() - 2; i >= 0; --i) {
+ final RectF charRect = info.getCharacterRect(composingTextStart + i);
+ if (charRect == null) {
+ break;
+ }
+ if (charRect.top != segmentStartCharRect.top) {
+ break;
+ }
+ if (charRect.bottom != segmentStartCharRect.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 (!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;
+ }
+ } else {
+ if (!TextUtils.isEmpty(composingText)) {
+ // This is an unexpected case.
+ // TODO: Document this.
+ hideIndicator();
+ 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.isInsertionMarkerClipped()) {
+ hideIndicator();
+ 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);
+ }
+
+ private void onClickIndicator() {
+ if (mWaitingWord == null || TextUtils.isEmpty(mWaitingWord.mWord)) {
+ return;
+ }
+ switch (mMode) {
+ case MODE_COMMIT:
+ mListener.onClickComposingTextToCommit(mWaitingWord);
+ break;
+ case MODE_ADD_TO_DICTIONARY:
+ mListener.onClickComposingTextToAddToDictionary(mWaitingWord);
+ break;
+ }
+ }
+
+ private final LayoutInvalidator mLayoutInvalidator = new LayoutInvalidator(this);
+
+ /**
+ * Used for managing pending layout tasks for {@link TextDecorator#layoutLater()}.
+ */
+ private static final class LayoutInvalidator {
+ private final HandlerImpl mHandler;
+ public LayoutInvalidator(final TextDecorator ownerInstance) {
+ mHandler = new HandlerImpl(ownerInstance);
+ }
+
+ private static final int MSG_LAYOUT = 0;
+
+ private static final class HandlerImpl
+ extends LeakGuardHandlerWrapper<TextDecorator> {
+ public HandlerImpl(final TextDecorator ownerInstance) {
+ super(ownerInstance);
+ }
+
+ @Override
+ public void handleMessage(final Message msg) {
+ final TextDecorator owner = getOwnerInstance();
+ if (owner == null) {
+ return;
+ }
+ switch (msg.what) {
+ case MSG_LAYOUT:
+ owner.layoutMain();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Puts a layout task into the scheduler. Does nothing if one or more layout tasks are
+ * already scheduled.
+ */
+ public void invalidateLayout() {
+ if (!mHandler.hasMessages(MSG_LAYOUT)) {
+ mHandler.obtainMessage(MSG_LAYOUT).sendToTarget();
+ }
+ }
+
+ /**
+ * Clears the pending layout tasks.
+ */
+ public void cancelInvalidateLayout() {
+ mHandler.removeMessages(MSG_LAYOUT);
+ }
+ }
+
+ private final static Listener EMPTY_LISTENER = new Listener() {
+ @Override
+ public void onClickComposingTextToCommit(SuggestedWordInfo wordInfo) {
+ }
+ @Override
+ public void onClickComposingTextToAddToDictionary(SuggestedWordInfo wordInfo) {
+ }
+ };
+
+ private final static TextDecoratorUiOperator EMPTY_UI_OPERATOR = new TextDecoratorUiOperator() {
+ @Override
+ public void disposeUi() {
+ }
+ @Override
+ public void hideUi() {
+ }
+ @Override
+ public void setOnClickListener(Runnable listener) {
+ }
+ @Override
+ public void layoutUi(boolean isCommitMode, 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
new file mode 100644
index 000000000..6e215a9ca
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/TextDecoratorUi.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.graphics.drawable.ColorDrawable;
+import android.inputmethodservice.InputMethodService;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewParent;
+import android.widget.PopupWindow;
+import android.widget.RelativeLayout;
+
+import com.android.inputmethod.latin.R;
+
+/**
+ * Used as the UI component of {@link TextDecorator}.
+ */
+public final class TextDecoratorUi implements TextDecoratorUiOperator {
+ private static final boolean VISUAL_DEBUG = false;
+ 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;
+ private final float mHitAreaMarginInPixels;
+
+ /**
+ * This constructor is designed to be called from {@link InputMethodService#setInputView(View)}.
+ * Other usages are not supported.
+ *
+ * @param context the context of the input method.
+ * @param inputView the view that is passed to {@link InputMethodService#setInputView(View)}.
+ */
+ public TextDecoratorUi(final Context context, final View inputView) {
+ final Resources resources = context.getResources();
+ final int hitAreaMarginInDP = resources.getInteger(
+ R.integer.text_decorator_hit_area_margin_in_dp);
+ mHitAreaMarginInPixels = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ hitAreaMarginInDP, resources.getDisplayMetrics());
+
+ mLocalRootView = new RelativeLayout(context);
+ mLocalRootView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ // TODO: Use #setBackground(null) for API Level >= 16.
+ 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);
+ }
+
+ // This popup window is used to avoid the limitation that the input method is not able to
+ // observe the touch events happening outside of InputMethodService.Insets#touchableRegion.
+ // We don't use this popup window for rendering the UI for performance reasons though.
+ mTouchEventWindow = new PopupWindow(context);
+ if (VISUAL_DEBUG) {
+ mTouchEventWindow.setBackgroundDrawable(new ColorDrawable(VISUAL_DEBUG_HIT_AREA_COLOR));
+ } else {
+ mTouchEventWindow.setBackgroundDrawable(null);
+ }
+ mTouchEventWindowClickListenerView = new View(context);
+ mTouchEventWindow.setContentView(mTouchEventWindowClickListenerView);
+ }
+
+ @Override
+ public void disposeUi() {
+ if (mLocalRootView != null) {
+ final ViewParent parent = mLocalRootView.getParent();
+ if (parent != null && parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(mLocalRootView);
+ }
+ mLocalRootView.removeAllViews();
+ }
+ if (mTouchEventWindow != null) {
+ mTouchEventWindow.dismiss();
+ }
+ }
+
+ @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) {
+ final RectF indicatorBoundsInScreenCoordinates = new RectF();
+ matrix.mapRect(indicatorBoundsInScreenCoordinates, indicatorBounds);
+ mCommitIndicatorView.setBounds(indicatorBoundsInScreenCoordinates);
+ mAddToDictionaryIndicatorView.setBounds(indicatorBoundsInScreenCoordinates);
+
+ final RectF hitAreaBounds = new RectF(composingTextBounds);
+ hitAreaBounds.union(indicatorBounds);
+ final RectF hitAreaBoundsInScreenCoordinates = new RectF();
+ matrix.mapRect(hitAreaBoundsInScreenCoordinates, hitAreaBounds);
+ hitAreaBoundsInScreenCoordinates.inset(-mHitAreaMarginInPixels, -mHitAreaMarginInPixels);
+
+ final int[] originScreen = new int[2];
+ 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);
+
+ if (mTouchEventWindow.isShowing()) {
+ mTouchEventWindow.update((int)hitAreaBoundsInScreenCoordinates.left - viewOriginX,
+ (int)hitAreaBoundsInScreenCoordinates.top - viewOriginY,
+ (int)hitAreaBoundsInScreenCoordinates.width(),
+ (int)hitAreaBoundsInScreenCoordinates.height());
+ } else {
+ mTouchEventWindow.setWidth((int)hitAreaBoundsInScreenCoordinates.width());
+ mTouchEventWindow.setHeight((int)hitAreaBoundsInScreenCoordinates.height());
+ mTouchEventWindow.showAtLocation(mLocalRootView, Gravity.NO_GRAVITY,
+ (int)hitAreaBoundsInScreenCoordinates.left - viewOriginX,
+ (int)hitAreaBoundsInScreenCoordinates.top - viewOriginY);
+ }
+ }
+
+ @Override
+ public void setOnClickListener(final Runnable listener) {
+ mTouchEventWindowClickListenerView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View arg0) {
+ listener.run();
+ }
+ });
+ }
+
+ private static class IndicatorView extends View {
+ private final Path mPath;
+ private final Path mTmpPath = new Path();
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private final Matrix mMatrix = new Matrix();
+ private final int mBackgroundColor;
+ private final int mForegroundColor;
+ private final RectF mBounds = new RectF();
+ public IndicatorView(Context context, final int pathResourceId,
+ final int sizeResourceId, final int backgroundColorResourceId,
+ final int foregroundColroResourceId) {
+ super(context);
+ final Resources resources = context.getResources();
+ mPath = createPath(resources, pathResourceId, sizeResourceId);
+ mBackgroundColor = resources.getColor(backgroundColorResourceId);
+ mForegroundColor = resources.getColor(foregroundColroResourceId);
+ }
+
+ public void setBounds(final RectF rect) {
+ mBounds.set(rect);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ mPaint.setColor(mBackgroundColor);
+ mPaint.setStyle(Paint.Style.FILL);
+ canvas.drawRect(0.0f, 0.0f, mBounds.width(), mBounds.height(), mPaint);
+
+ mMatrix.reset();
+ mMatrix.postScale(mBounds.width(), mBounds.height());
+ mPath.transform(mMatrix, mTmpPath);
+ mPaint.setColor(mForegroundColor);
+ canvas.drawPath(mTmpPath, mPaint);
+ }
+
+ private static Path createPath(final Resources resources, final int pathResourceId,
+ final int sizeResourceId) {
+ final int size = resources.getInteger(sizeResourceId);
+ final float normalizationFactor = 1.0f / size;
+ final int[] array = resources.getIntArray(pathResourceId);
+
+ final Path path = new Path();
+ for (int i = 0; i < array.length; i += 2) {
+ if (i == 0) {
+ path.moveTo(array[i] * normalizationFactor, array[i + 1] * normalizationFactor);
+ } else {
+ path.lineTo(array[i] * normalizationFactor, array[i + 1] * normalizationFactor);
+ }
+ }
+ path.close();
+ return path;
+ }
+ }
+
+ private static ViewGroup getContentView(final View view) {
+ final View rootView = view.getRootView();
+ if (rootView == null) {
+ return null;
+ }
+
+ final ViewGroup windowContentView = (ViewGroup)rootView.findViewById(android.R.id.content);
+ if (windowContentView == null) {
+ return null;
+ }
+ 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,
+ R.integer.text_decorator_add_to_dictionary_indicator_path_size,
+ R.color.text_decorator_add_to_dictionary_indicator_background_color,
+ R.color.text_decorator_add_to_dictionary_indicator_foreground_color);
+ }
+ }
+} \ No newline at end of file
diff --git a/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java b/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java
new file mode 100644
index 000000000..f84e12d8c
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/TextDecoratorUiOperator.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.RectF;
+
+/**
+ * This interface defines how UI operations required for {@link TextDecorator} are delegated to
+ * the actual UI implementation class.
+ */
+public interface TextDecoratorUiOperator {
+ /**
+ * Called to notify that the UI is ready to be disposed.
+ */
+ void disposeUi();
+
+ /**
+ * Called when the UI should become invisible.
+ */
+ void hideUi();
+
+ /**
+ * Called to set the new click handler.
+ * @param onClickListener the callback object whose {@link Runnable#run()} should be called when
+ * the indicator is clicked.
+ */
+ void setOnClickListener(final Runnable onClickListener);
+
+ /**
+ * 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,
+ final RectF composingTextBounds);
+}
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index aebc7101a..dcafd83a5 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -69,6 +69,7 @@ import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.MainKeyboardView;
+import com.android.inputmethod.keyboard.TextDecoratorUi;
import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.define.DebugFlags;
@@ -183,8 +184,9 @@ 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_WAIT_FOR_DICTIONARY_LOAD;
+ private static final int MSG_LAST = MSG_SHOW_COMMIT_INDICATOR;
private static final int ARG1_NOT_GESTURE_INPUT = 0;
private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
@@ -195,6 +197,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
private int mDelayInMillisecondsToUpdateSuggestions;
private int mDelayInMillisecondsToUpdateShiftState;
+ private int mDelayInMillisecondsToShowCommitIndicator;
public UIHandler(final LatinIME ownerInstance) {
super(ownerInstance);
@@ -206,10 +209,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
final Resources res = latinIme.getResources();
- mDelayInMillisecondsToUpdateSuggestions =
- res.getInteger(R.integer.config_delay_in_milliseconds_to_update_suggestions);
- mDelayInMillisecondsToUpdateShiftState =
- res.getInteger(R.integer.config_delay_in_milliseconds_to_update_shift_state);
+ mDelayInMillisecondsToUpdateSuggestions = res.getInteger(
+ 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
@@ -258,7 +263,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
case MSG_RESET_CACHES:
final SettingsValues settingsValues = latinIme.mSettings.getCurrent();
if (latinIme.mInputLogic.retryResetCachesAndReturnSuccess(
- msg.arg1 == 1 /* tryResumeSuggestions */,
+ msg.arg1 == ARG1_TRUE /* tryResumeSuggestions */,
msg.arg2 /* remainingTries */, this /* handler */)) {
// If we were able to reset the caches, then we can reload the keyboard.
// Otherwise, we'll do it when we can.
@@ -267,6 +272,14 @@ 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;
@@ -367,6 +380,19 @@ 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;
@@ -717,6 +743,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
if (hasSuggestionStripView()) {
mSuggestionStripView.setListener(this, view);
}
+ mInputLogic.setTextDecoratorUi(new TextDecoratorUi(this, view));
}
@Override
@@ -972,9 +999,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// @Override
public void onUpdateCursorAnchorInfo(final CursorAnchorInfo info) {
if (ProductionFlags.ENABLE_CURSOR_ANCHOR_INFO_CALLBACK) {
- final CursorAnchorInfoCompatWrapper wrapper =
- CursorAnchorInfoCompatWrapper.fromObject(info);
- // TODO: Implement here
+ mInputLogic.onUpdateCursorAnchorInfo(CursorAnchorInfoCompatWrapper.fromObject(info));
}
}
@@ -1178,6 +1203,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// In fullscreen mode, no need to have extra space to show the key preview.
// If not, we should have extra space above the keyboard to show the key preview.
mKeyPreviewBackingView.setVisibility(isFullscreenMode() ? View.GONE : View.VISIBLE);
+ mInputLogic.onUpdateFullscreenMode(isFullscreenMode());
}
private int getCurrentAutoCapsState() {
@@ -1221,6 +1247,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
wordToEdit = word;
}
mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit);
+ mInputLogic.onAddWordToUserDictionary();
}
// Callback for the {@link SuggestionStripView}, to call when the important notice strip is
@@ -1409,7 +1436,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
}
private void setSuggestedWords(final SuggestedWords suggestedWords) {
- mInputLogic.setSuggestedWords(suggestedWords);
+ final SettingsValues currentSettingsValues = mSettings.getCurrent();
+ mInputLogic.setSuggestedWords(suggestedWords, currentSettingsValues, mHandler);
// TODO: Modify this when we support suggestions with hard keyboard
if (!hasSuggestionStripView()) {
return;
@@ -1418,7 +1446,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
return;
}
- final SettingsValues currentSettingsValues = mSettings.getCurrent();
final boolean shouldShowImportantNotice =
ImportantNoticeUtils.shouldShowImportantNotice(this);
final boolean shouldShowSuggestionCandidates =
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index a6978809a..0f2ba53d5 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.latin.inputlogic;
import android.graphics.Color;
+import android.inputmethodservice.InputMethodService;
import android.os.SystemClock;
import android.text.SpannableString;
import android.text.TextUtils;
@@ -27,11 +28,14 @@ import android.view.KeyEvent;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.EditorInfo;
+import com.android.inputmethod.compat.CursorAnchorInfoCompatWrapper;
import com.android.inputmethod.compat.SuggestionSpanUtils;
import com.android.inputmethod.event.Event;
import com.android.inputmethod.event.InputTransaction;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.keyboard.TextDecorator;
+import com.android.inputmethod.keyboard.TextDecoratorUiOperator;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.Dictionary;
import com.android.inputmethod.latin.DictionaryFacilitator;
@@ -81,6 +85,18 @@ public final class InputLogic {
public final Suggest mSuggest;
private final DictionaryFacilitator mDictionaryFacilitator;
+ 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);
+ mLatinIME.dismissAddToDictionaryHint();
+ }
+ });
+
public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
// This has package visibility so it can be accessed from InputLogicHandler.
/* package */ final WordComposer mWordComposer;
@@ -303,8 +319,18 @@ public final class InputLogic {
return inputTransaction;
}
- commitChosenWord(settingsValues, suggestion,
- LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR);
+ 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);
mConnection.endBatchEdit();
// Don't allow cancellation of manual pick
mLastComposedWord.deactivate();
@@ -312,13 +338,16 @@ public final class InputLogic {
mSpaceState = SpaceState.PHANTOM;
inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW);
- if (shouldShowAddToDictionaryHint(suggestionInfo)) {
+ if (shouldShowAddToDictionaryHint) {
mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion);
} else {
// If we're not showing the "Touch again to save", then update the suggestion strip.
// That's going to be predictions (or punctuation suggestions), so INPUT_STYLE_NONE.
handler.postUpdateSuggestionStrip(SuggestedWords.INPUT_STYLE_NONE);
}
+ if (shouldShowAddToDictionaryIndicator) {
+ mTextDecorator.showAddToDictionaryIndicator(suggestionInfo);
+ }
return inputTransaction;
}
@@ -386,6 +415,8 @@ public final class InputLogic {
// The cursor has been moved : we now accept to perform recapitalization
mRecapitalizeStatus.enable();
+ // We moved the cursor and need to invalidate the indicator right now.
+ mTextDecorator.reset();
// We moved the cursor. If we are touching a word, we need to resume suggestion.
mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */,
true /* shouldDelay */);
@@ -561,7 +592,8 @@ public final class InputLogic {
// TODO: on the long term, this method should become private, but it will be difficult.
// Especially, how do we deal with InputMethodService.onDisplayCompletions?
- public void setSuggestedWords(final SuggestedWords suggestedWords) {
+ public void setSuggestedWords(final SuggestedWords suggestedWords,
+ final SettingsValues settingsValues, final LatinIME.UIHandler handler) {
if (SuggestedWords.EMPTY != suggestedWords) {
final String autoCorrection;
if (suggestedWords.mWillAutoCorrect) {
@@ -575,6 +607,38 @@ public final class InputLogic {
}
mSuggestedWords = suggestedWords;
final boolean newAutoCorrectionIndicator = suggestedWords.mWillAutoCorrect;
+ if (shouldShowCommitIndicator(suggestedWords, settingsValues)) {
+ // typedWordInfo is never null here.
+ 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;
+ }
+ 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
&& mWordComposer.isComposingWord()) {
@@ -756,6 +820,8 @@ public final class InputLogic {
if (!mWordComposer.isComposingWord() &&
mSuggestionStripViewAccessor.isShowingAddToDictionaryHint()) {
mSuggestionStripViewAccessor.dismissAddToDictionaryHint();
+ mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
+ mTextDecorator.reset();
}
final int codePoint = event.mCodePoint;
@@ -2108,4 +2174,74 @@ public final class InputLogic {
settingsValues.mAutoCorrectionEnabledPerUserSettings,
inputStyle, sequenceNumber, callback);
}
+
+ //////////////////////////////////////////////////////////////////////////////////////////////
+ // Following methods are tentatively placed in this class for the integration with
+ // TextDecorator.
+ // TODO: Decouple things that are not related to the input logic.
+ //////////////////////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Sets the UI operator for {@link TextDecorator}.
+ * @param uiOperator the UI operator which should be associated with {@link TextDecorator}.
+ */
+ public void setTextDecoratorUi(final TextDecoratorUiOperator uiOperator) {
+ mTextDecorator.setUiOperator(uiOperator);
+ }
+
+ /**
+ * Must be called from {@link InputMethodService#onUpdateCursorAnchorInfo} is called.
+ * @param info The wrapper object with which we can access cursor/anchor info.
+ */
+ public void onUpdateCursorAnchorInfo(final CursorAnchorInfoCompatWrapper info) {
+ mTextDecorator.onUpdateCursorAnchorInfo(info);
+ }
+
+ /**
+ * Must be called when {@link InputMethodService#updateFullscreenMode} is called.
+ * @param isFullscreen {@code true} if the input method is in full-screen mode.
+ */
+ public void onUpdateFullscreenMode(final boolean isFullscreen) {
+ mTextDecorator.notifyFullScreenMode(isFullscreen);
+ }
+
+ /**
+ * Must be called from {@link LatinIME#addWordToUserDictionary(String)}.
+ */
+ public void onAddWordToUserDictionary() {
+ mConnection.removeBackgroundColorFromHighlightedTextIfNecessary();
+ mTextDecorator.reset();
+ }
+
+ /**
+ * Returns whether the commit indicator should be shown or not.
+ * @param suggestedWords the suggested word that is being displayed.
+ * @param settingsValues the current settings value.
+ * @return {@code true} if the commit indicator should be shown.
+ */
+ private boolean shouldShowCommitIndicator(final SuggestedWords suggestedWords,
+ final SettingsValues settingsValues) {
+ if (!settingsValues.mShouldShowUiToAcceptTypedWord) {
+ return false;
+ }
+ final SuggestedWordInfo typedWordInfo = suggestedWords.getTypedWordInfoOrNull();
+ if (typedWordInfo == null) {
+ return false;
+ }
+ if (suggestedWords.mInputStyle != SuggestedWords.INPUT_STYLE_TYPING){
+ return false;
+ }
+ if (settingsValues.mShowCommitIndicatorOnlyForAutoCorrection
+ && !suggestedWords.mWillAutoCorrect) {
+ 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;
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 1cd7b391a..dc2eda9bc 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -95,6 +95,12 @@ public final class SettingsValues {
public final int[] mAdditionalFeaturesSettingValues =
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;
public final int mKeyPreviewShowUpDuration;
@@ -163,6 +169,14 @@ public final 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);
mKeyPreviewShowUpDuration = Settings.readKeyPreviewAnimationDuration(
prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION,
@@ -396,6 +410,14 @@ public final 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 = ");
sb.append("" + mIsInternal);
sb.append("\n mKeyPreviewShowUpDuration = ");