aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/org/kelar/inputmethod/latin/suggestions
diff options
context:
space:
mode:
authorAmin Bandali <bandali@kelar.org>2024-12-16 21:45:41 -0500
committerAmin Bandali <bandali@kelar.org>2025-01-11 14:17:35 -0500
commite9a0e66716dab4dd3184d009d8920de1961efdfa (patch)
tree02dcc096643d74645bf28459c2834c3d4a2ad7f2 /java/src/org/kelar/inputmethod/latin/suggestions
parentfb3b9360d70596d7e921de8bf7d3ca99564a077e (diff)
downloadlatinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.gz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.tar.xz
latinime-e9a0e66716dab4dd3184d009d8920de1961efdfa.zip
Rename to Kelar Keyboard (org.kelar.inputmethod.latin)
Diffstat (limited to 'java/src/org/kelar/inputmethod/latin/suggestions')
-rw-r--r--java/src/org/kelar/inputmethod/latin/suggestions/MoreSuggestions.java268
-rw-r--r--java/src/org/kelar/inputmethod/latin/suggestions/MoreSuggestionsView.java117
-rw-r--r--java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java650
-rw-r--r--java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripView.java491
-rw-r--r--java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java27
5 files changed, 1553 insertions, 0 deletions
diff --git a/java/src/org/kelar/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/org/kelar/inputmethod/latin/suggestions/MoreSuggestions.java
new file mode 100644
index 000000000..5ea6ccd99
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2011 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 org.kelar.inputmethod.latin.suggestions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+
+import org.kelar.inputmethod.keyboard.Key;
+import org.kelar.inputmethod.keyboard.Keyboard;
+import org.kelar.inputmethod.keyboard.internal.KeyboardBuilder;
+import org.kelar.inputmethod.keyboard.internal.KeyboardIconsSet;
+import org.kelar.inputmethod.keyboard.internal.KeyboardParams;
+import org.kelar.inputmethod.latin.R;
+import org.kelar.inputmethod.latin.SuggestedWords;
+import org.kelar.inputmethod.latin.common.Constants;
+import org.kelar.inputmethod.latin.utils.TypefaceUtils;
+
+public final class MoreSuggestions extends Keyboard {
+ public final SuggestedWords mSuggestedWords;
+
+ MoreSuggestions(final MoreSuggestionsParam params, final SuggestedWords suggestedWords) {
+ super(params);
+ mSuggestedWords = suggestedWords;
+ }
+
+ private static final class MoreSuggestionsParam extends KeyboardParams {
+ private final int[] mWidths = new int[SuggestedWords.MAX_SUGGESTIONS];
+ private final int[] mRowNumbers = new int[SuggestedWords.MAX_SUGGESTIONS];
+ private final int[] mColumnOrders = new int[SuggestedWords.MAX_SUGGESTIONS];
+ private final int[] mNumColumnsInRow = new int[SuggestedWords.MAX_SUGGESTIONS];
+ private static final int MAX_COLUMNS_IN_ROW = 3;
+ private int mNumRows;
+ public Drawable mDivider;
+ public int mDividerWidth;
+
+ public MoreSuggestionsParam() {
+ super();
+ }
+
+ public int layout(final SuggestedWords suggestedWords, final int fromIndex,
+ final int maxWidth, final int minWidth, final int maxRow, final Paint paint,
+ final Resources res) {
+ clearKeys();
+ mDivider = res.getDrawable(R.drawable.more_suggestions_divider);
+ mDividerWidth = mDivider.getIntrinsicWidth();
+ final float padding = res.getDimension(
+ R.dimen.config_more_suggestions_key_horizontal_padding);
+
+ int row = 0;
+ int index = fromIndex;
+ int rowStartIndex = fromIndex;
+ final int size = Math.min(suggestedWords.size(), SuggestedWords.MAX_SUGGESTIONS);
+ while (index < size) {
+ final String word;
+ if (isIndexSubjectToAutoCorrection(suggestedWords, index)) {
+ // INDEX_OF_AUTO_CORRECTION and INDEX_OF_TYPED_WORD got swapped.
+ word = suggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD);
+ } else {
+ word = suggestedWords.getLabel(index);
+ }
+ // TODO: Should take care of text x-scaling.
+ mWidths[index] = (int)(TypefaceUtils.getStringWidth(word, paint) + padding);
+ final int numColumn = index - rowStartIndex + 1;
+ final int columnWidth =
+ (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
+ if (numColumn > MAX_COLUMNS_IN_ROW
+ || !fitInWidth(rowStartIndex, index + 1, columnWidth)) {
+ if ((row + 1) >= maxRow) {
+ break;
+ }
+ mNumColumnsInRow[row] = index - rowStartIndex;
+ rowStartIndex = index;
+ row++;
+ }
+ mColumnOrders[index] = index - rowStartIndex;
+ mRowNumbers[index] = row;
+ index++;
+ }
+ mNumColumnsInRow[row] = index - rowStartIndex;
+ mNumRows = row + 1;
+ mBaseWidth = mOccupiedWidth = Math.max(
+ minWidth, calcurateMaxRowWidth(fromIndex, index));
+ mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
+ return index - fromIndex;
+ }
+
+ private boolean fitInWidth(final int startIndex, final int endIndex, final int width) {
+ for (int index = startIndex; index < endIndex; index++) {
+ if (mWidths[index] > width)
+ return false;
+ }
+ return true;
+ }
+
+ private int calcurateMaxRowWidth(final int startIndex, final int endIndex) {
+ int maxRowWidth = 0;
+ int index = startIndex;
+ for (int row = 0; row < mNumRows; row++) {
+ final int numColumnInRow = mNumColumnsInRow[row];
+ int maxKeyWidth = 0;
+ while (index < endIndex && mRowNumbers[index] == row) {
+ maxKeyWidth = Math.max(maxKeyWidth, mWidths[index]);
+ index++;
+ }
+ maxRowWidth = Math.max(maxRowWidth,
+ maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
+ }
+ return maxRowWidth;
+ }
+
+ private static final int[][] COLUMN_ORDER_TO_NUMBER = {
+ { 0 }, // center
+ { 1, 0 }, // right-left
+ { 1, 0, 2 }, // center-left-right
+ };
+
+ public int getNumColumnInRow(final int index) {
+ return mNumColumnsInRow[mRowNumbers[index]];
+ }
+
+ public int getColumnNumber(final int index) {
+ final int columnOrder = mColumnOrders[index];
+ final int numColumn = getNumColumnInRow(index);
+ return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
+ }
+
+ public int getX(final int index) {
+ final int columnNumber = getColumnNumber(index);
+ return columnNumber * (getWidth(index) + mDividerWidth);
+ }
+
+ public int getY(final int index) {
+ final int row = mRowNumbers[index];
+ return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
+ }
+
+ public int getWidth(final int index) {
+ final int numColumnInRow = getNumColumnInRow(index);
+ return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
+ }
+
+ public void markAsEdgeKey(final Key key, final int index) {
+ final int row = mRowNumbers[index];
+ if (row == 0)
+ key.markAsBottomEdge(this);
+ if (row == mNumRows - 1)
+ key.markAsTopEdge(this);
+
+ final int numColumnInRow = mNumColumnsInRow[row];
+ final int column = getColumnNumber(index);
+ if (column == 0)
+ key.markAsLeftEdge(this);
+ if (column == numColumnInRow - 1)
+ key.markAsRightEdge(this);
+ }
+ }
+
+ static boolean isIndexSubjectToAutoCorrection(final SuggestedWords suggestedWords,
+ final int index) {
+ return suggestedWords.mWillAutoCorrect && index == SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+ }
+
+ public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
+ private final MoreSuggestionsView mPaneView;
+ private SuggestedWords mSuggestedWords;
+ private int mFromIndex;
+ private int mToIndex;
+
+ public Builder(final Context context, final MoreSuggestionsView paneView) {
+ super(context, new MoreSuggestionsParam());
+ mPaneView = paneView;
+ }
+
+ public Builder layout(final SuggestedWords suggestedWords, final int fromIndex,
+ final int maxWidth, final int minWidth, final int maxRow,
+ final Keyboard parentKeyboard) {
+ final int xmlId = R.xml.kbd_suggestions_pane_template;
+ load(xmlId, parentKeyboard.mId);
+ mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
+ mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
+ final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow,
+ mPaneView.newLabelPaint(null /* key */), mResources);
+ mFromIndex = fromIndex;
+ mToIndex = fromIndex + count;
+ mSuggestedWords = suggestedWords;
+ return this;
+ }
+
+ @Override
+ public MoreSuggestions build() {
+ final MoreSuggestionsParam params = mParams;
+ for (int index = mFromIndex; index < mToIndex; index++) {
+ final int x = params.getX(index);
+ final int y = params.getY(index);
+ final int width = params.getWidth(index);
+ final String word;
+ final String info;
+ if (isIndexSubjectToAutoCorrection(mSuggestedWords, index)) {
+ // INDEX_OF_AUTO_CORRECTION and INDEX_OF_TYPED_WORD got swapped.
+ word = mSuggestedWords.getLabel(SuggestedWords.INDEX_OF_TYPED_WORD);
+ info = mSuggestedWords.getDebugString(SuggestedWords.INDEX_OF_TYPED_WORD);
+ } else {
+ word = mSuggestedWords.getLabel(index);
+ info = mSuggestedWords.getDebugString(index);
+ }
+ final Key key = new MoreSuggestionKey(word, info, index, params);
+ params.markAsEdgeKey(key, index);
+ params.onAddKey(key);
+ final int columnNumber = params.getColumnNumber(index);
+ final int numColumnInRow = params.getNumColumnInRow(index);
+ if (columnNumber < numColumnInRow - 1) {
+ final Divider divider = new Divider(params, params.mDivider, x + width, y,
+ params.mDividerWidth, params.mDefaultRowHeight);
+ params.onAddKey(divider);
+ }
+ }
+ return new MoreSuggestions(params, mSuggestedWords);
+ }
+ }
+
+ static final class MoreSuggestionKey extends Key {
+ public final int mSuggestedWordIndex;
+
+ public MoreSuggestionKey(final String word, final String info, final int index,
+ final MoreSuggestionsParam params) {
+ super(word /* label */, KeyboardIconsSet.ICON_UNDEFINED, Constants.CODE_OUTPUT_TEXT,
+ word /* outputText */, info, 0 /* labelFlags */, Key.BACKGROUND_TYPE_NORMAL,
+ params.getX(index), params.getY(index), params.getWidth(index),
+ params.mDefaultRowHeight, params.mHorizontalGap, params.mVerticalGap);
+ mSuggestedWordIndex = index;
+ }
+ }
+
+ private static final class Divider extends Key.Spacer {
+ private final Drawable mIcon;
+
+ public Divider(final KeyboardParams params, final Drawable icon, final int x,
+ final int y, final int width, final int height) {
+ super(params, x, y, width, height);
+ mIcon = icon;
+ }
+
+ @Override
+ public Drawable getIcon(final KeyboardIconsSet iconSet, final int alpha) {
+ // KeyboardIconsSet and alpha are unused. Use the icon that has been passed to the
+ // constructor.
+ // TODO: Drawable itself should have an alpha value.
+ mIcon.setAlpha(128);
+ return mIcon;
+ }
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/org/kelar/inputmethod/latin/suggestions/MoreSuggestionsView.java
new file mode 100644
index 000000000..a899c9a1d
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/latin/suggestions/MoreSuggestionsView.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011 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 org.kelar.inputmethod.latin.suggestions;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import org.kelar.inputmethod.keyboard.Key;
+import org.kelar.inputmethod.keyboard.Keyboard;
+import org.kelar.inputmethod.keyboard.KeyboardActionListener;
+import org.kelar.inputmethod.keyboard.MoreKeysKeyboardView;
+import org.kelar.inputmethod.latin.R;
+import org.kelar.inputmethod.latin.SuggestedWords;
+import org.kelar.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import org.kelar.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionKey;
+
+/**
+ * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting
+ * key presses and touch movements.
+ */
+public final class MoreSuggestionsView extends MoreKeysKeyboardView {
+ private static final String TAG = MoreSuggestionsView.class.getSimpleName();
+
+ public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter {
+ public abstract void onSuggestionSelected(final SuggestedWordInfo info);
+ }
+
+ private boolean mIsInModalMode;
+
+ public MoreSuggestionsView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, R.attr.moreKeysKeyboardViewStyle);
+ }
+
+ public MoreSuggestionsView(final Context context, final AttributeSet attrs,
+ final int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ // TODO: Remove redundant override method.
+ @Override
+ public void setKeyboard(final Keyboard keyboard) {
+ super.setKeyboard(keyboard);
+ mIsInModalMode = false;
+ // With accessibility mode off, {@link #mAccessibilityDelegate} is set to null at the
+ // above {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call.
+ // With accessibility mode on, {@link #mAccessibilityDelegate} is set to a
+ // {@link MoreKeysKeyboardAccessibilityDelegate} object at the above
+ // {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call.
+ if (mAccessibilityDelegate != null) {
+ mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_suggestions);
+ mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_suggestions);
+ }
+ }
+
+ @Override
+ protected int getDefaultCoordX() {
+ final MoreSuggestions pane = (MoreSuggestions)getKeyboard();
+ return pane.mOccupiedWidth / 2;
+ }
+
+ public void updateKeyboardGeometry(final int keyHeight) {
+ updateKeyDrawParams(keyHeight);
+ }
+
+ public void setModalMode() {
+ mIsInModalMode = true;
+ // Set vertical correction to zero (Reset more keys keyboard sliding allowance
+ // {@link R#dimen.config_more_keys_keyboard_slide_allowance}).
+ mKeyDetector.setKeyboard(getKeyboard(), -getPaddingLeft(), -getPaddingTop());
+ }
+
+ public boolean isInModalMode() {
+ return mIsInModalMode;
+ }
+
+ @Override
+ protected void onKeyInput(final Key key, final int x, final int y) {
+ if (!(key instanceof MoreSuggestionKey)) {
+ Log.e(TAG, "Expected key is MoreSuggestionKey, but found "
+ + key.getClass().getName());
+ return;
+ }
+ final Keyboard keyboard = getKeyboard();
+ if (!(keyboard instanceof MoreSuggestions)) {
+ Log.e(TAG, "Expected keyboard is MoreSuggestions, but found "
+ + keyboard.getClass().getName());
+ return;
+ }
+ final SuggestedWords suggestedWords = ((MoreSuggestions)keyboard).mSuggestedWords;
+ final int index = ((MoreSuggestionKey)key).mSuggestedWordIndex;
+ if (index < 0 || index >= suggestedWords.size()) {
+ Log.e(TAG, "Selected suggestion has an illegal index: " + index);
+ return;
+ }
+ if (!(mListener instanceof MoreSuggestionsListener)) {
+ Log.e(TAG, "Expected mListener is MoreSuggestionsListener, but found "
+ + mListener.getClass().getName());
+ return;
+ }
+ ((MoreSuggestionsListener)mListener).onSuggestionSelected(suggestedWords.getInfo(index));
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
new file mode 100644
index 000000000..6e95de414
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -0,0 +1,650 @@
+/*
+ * Copyright (C) 2013 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 org.kelar.inputmethod.latin.suggestions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.CharacterStyle;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.kelar.inputmethod.accessibility.AccessibilityUtils;
+import org.kelar.inputmethod.annotations.UsedForTesting;
+import org.kelar.inputmethod.latin.PunctuationSuggestions;
+import org.kelar.inputmethod.latin.R;
+import org.kelar.inputmethod.latin.SuggestedWords;
+import org.kelar.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import org.kelar.inputmethod.latin.settings.Settings;
+import org.kelar.inputmethod.latin.settings.SettingsValues;
+import org.kelar.inputmethod.latin.utils.ResourceUtils;
+import org.kelar.inputmethod.latin.utils.ViewLayoutUtils;
+
+import java.util.ArrayList;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+final class SuggestionStripLayoutHelper {
+ private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
+ private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
+ private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
+ private static final int PUNCTUATIONS_IN_STRIP = 5;
+ private static final float MIN_TEXT_XSCALE = 0.70f;
+
+ public final int mPadding;
+ public final int mDividerWidth;
+ public final int mSuggestionsStripHeight;
+ private final int mSuggestionsCountInStrip;
+ public final int mMoreSuggestionsRowHeight;
+ private int mMaxMoreSuggestionsRow;
+ public final float mMinMoreSuggestionsWidth;
+ public final int mMoreSuggestionsBottomGap;
+ private boolean mMoreSuggestionsAvailable;
+
+ // The index of these {@link ArrayList} is the position in the suggestion strip. The indices
+ // increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
+ // The position of the most important suggestion is in {@link #mCenterPositionInStrip}
+ private final ArrayList<TextView> mWordViews;
+ private final ArrayList<View> mDividerViews;
+ private final ArrayList<TextView> mDebugInfoViews;
+
+ private final int mColorValidTypedWord;
+ private final int mColorTypedWord;
+ private final int mColorAutoCorrect;
+ private final int mColorSuggested;
+ private final float mAlphaObsoleted;
+ private final float mCenterSuggestionWeight;
+ private final int mCenterPositionInStrip;
+ private final int mTypedWordPositionWhenAutocorrect;
+ private final Drawable mMoreSuggestionsHint;
+ private static final String MORE_SUGGESTIONS_HINT = "\u2026";
+
+ private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
+ private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
+
+ private final int mSuggestionStripOptions;
+ // These constants are the flag values of
+ // {@link R.styleable#SuggestionStripView_suggestionStripOptions} attribute.
+ private static final int AUTO_CORRECT_BOLD = 0x01;
+ private static final int AUTO_CORRECT_UNDERLINE = 0x02;
+ private static final int VALID_TYPED_WORD_BOLD = 0x04;
+
+ public SuggestionStripLayoutHelper(final Context context, final AttributeSet attrs,
+ final int defStyle, final ArrayList<TextView> wordViews,
+ final ArrayList<View> dividerViews, final ArrayList<TextView> debugInfoViews) {
+ mWordViews = wordViews;
+ mDividerViews = dividerViews;
+ mDebugInfoViews = debugInfoViews;
+
+ final TextView wordView = wordViews.get(0);
+ final View dividerView = dividerViews.get(0);
+ mPadding = wordView.getCompoundPaddingLeft() + wordView.getCompoundPaddingRight();
+ dividerView.measure(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ mDividerWidth = dividerView.getMeasuredWidth();
+
+ final Resources res = wordView.getResources();
+ mSuggestionsStripHeight = res.getDimensionPixelSize(
+ R.dimen.config_suggestions_strip_height);
+
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripView);
+ mSuggestionStripOptions = a.getInt(
+ R.styleable.SuggestionStripView_suggestionStripOptions, 0);
+ mAlphaObsoleted = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_alphaObsoleted, 1.0f);
+ mColorValidTypedWord = a.getColor(R.styleable.SuggestionStripView_colorValidTypedWord, 0);
+ mColorTypedWord = a.getColor(R.styleable.SuggestionStripView_colorTypedWord, 0);
+ mColorAutoCorrect = a.getColor(R.styleable.SuggestionStripView_colorAutoCorrect, 0);
+ mColorSuggested = a.getColor(R.styleable.SuggestionStripView_colorSuggested, 0);
+ mSuggestionsCountInStrip = a.getInt(
+ R.styleable.SuggestionStripView_suggestionsCountInStrip,
+ DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
+ mCenterSuggestionWeight = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_centerSuggestionPercentile,
+ DEFAULT_CENTER_SUGGESTION_PERCENTILE);
+ mMaxMoreSuggestionsRow = a.getInt(
+ R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
+ DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
+ mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
+ R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
+ a.recycle();
+
+ mMoreSuggestionsHint = getMoreSuggestionsHint(res,
+ res.getDimension(R.dimen.config_more_suggestions_hint_text_size),
+ mColorAutoCorrect);
+ mCenterPositionInStrip = mSuggestionsCountInStrip / 2;
+ // Assuming there are at least three suggestions. Also, note that the suggestions are
+ // laid out according to script direction, so this is left of the center for LTR scripts
+ // and right of the center for RTL scripts.
+ mTypedWordPositionWhenAutocorrect = mCenterPositionInStrip - 1;
+ mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
+ R.dimen.config_more_suggestions_bottom_gap);
+ mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
+ R.dimen.config_more_suggestions_row_height);
+ }
+
+ public int getMaxMoreSuggestionsRow() {
+ return mMaxMoreSuggestionsRow;
+ }
+
+ private int getMoreSuggestionsHeight() {
+ return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
+ }
+
+ public void setMoreSuggestionsHeight(final int remainingHeight) {
+ final int currentHeight = getMoreSuggestionsHeight();
+ if (currentHeight <= remainingHeight) {
+ return;
+ }
+
+ mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
+ / mMoreSuggestionsRowHeight;
+ }
+
+ private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize,
+ final int color) {
+ final Paint paint = new Paint();
+ paint.setAntiAlias(true);
+ paint.setTextAlign(Align.CENTER);
+ paint.setTextSize(textSize);
+ paint.setColor(color);
+ final Rect bounds = new Rect();
+ paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds);
+ final int width = Math.round(bounds.width() + 0.5f);
+ final int height = Math.round(bounds.height() + 0.5f);
+ final Bitmap buffer = Bitmap.createBitmap(width, (height * 3 / 2), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(buffer);
+ canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint);
+ BitmapDrawable bitmapDrawable = new BitmapDrawable(res, buffer);
+ bitmapDrawable.setTargetDensity(canvas);
+ return bitmapDrawable;
+ }
+
+ private CharSequence getStyledSuggestedWord(final SuggestedWords suggestedWords,
+ final int indexInSuggestedWords) {
+ if (indexInSuggestedWords >= suggestedWords.size()) {
+ return null;
+ }
+ final String word = suggestedWords.getLabel(indexInSuggestedWords);
+ // TODO: don't use the index to decide whether this is the auto-correction/typed word, as
+ // this is brittle
+ final boolean isAutoCorrection = suggestedWords.mWillAutoCorrect
+ && indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+ final boolean isTypedWordValid = suggestedWords.mTypedWordValid
+ && indexInSuggestedWords == SuggestedWords.INDEX_OF_TYPED_WORD;
+ if (!isAutoCorrection && !isTypedWordValid) {
+ return word;
+ }
+
+ final Spannable spannedWord = new SpannableString(word);
+ final int options = mSuggestionStripOptions;
+ if ((isAutoCorrection && (options & AUTO_CORRECT_BOLD) != 0)
+ || (isTypedWordValid && (options & VALID_TYPED_WORD_BOLD) != 0)) {
+ addStyleSpan(spannedWord, BOLD_SPAN);
+ }
+ if (isAutoCorrection && (options & AUTO_CORRECT_UNDERLINE) != 0) {
+ addStyleSpan(spannedWord, UNDERLINE_SPAN);
+ }
+ return spannedWord;
+ }
+
+ /**
+ * Convert an index of {@link SuggestedWords} to position in the suggestion strip.
+ * @param indexInSuggestedWords the index of {@link SuggestedWords}.
+ * @param suggestedWords the suggested words list
+ * @return Non-negative integer of the position in the suggestion strip.
+ * Negative integer if the word of the index shouldn't be shown on the suggestion strip.
+ */
+ private int getPositionInSuggestionStrip(final int indexInSuggestedWords,
+ final SuggestedWords suggestedWords) {
+ final SettingsValues settingsValues = Settings.getInstance().getCurrent();
+ final boolean shouldOmitTypedWord = shouldOmitTypedWord(suggestedWords.mInputStyle,
+ settingsValues.mGestureFloatingPreviewTextEnabled,
+ settingsValues.mShouldShowLxxSuggestionUi);
+ return getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords.mWillAutoCorrect,
+ settingsValues.mShouldShowLxxSuggestionUi && shouldOmitTypedWord,
+ mCenterPositionInStrip, mTypedWordPositionWhenAutocorrect);
+ }
+
+ @UsedForTesting
+ static boolean shouldOmitTypedWord(final int inputStyle,
+ final boolean gestureFloatingPreviewTextEnabled,
+ final boolean shouldShowUiToAcceptTypedWord) {
+ final boolean omitTypedWord = (inputStyle == SuggestedWords.INPUT_STYLE_TYPING)
+ || (inputStyle == SuggestedWords.INPUT_STYLE_TAIL_BATCH)
+ || (inputStyle == SuggestedWords.INPUT_STYLE_UPDATE_BATCH
+ && gestureFloatingPreviewTextEnabled);
+ return shouldShowUiToAcceptTypedWord && omitTypedWord;
+ }
+
+ @UsedForTesting
+ static int getPositionInSuggestionStrip(final int indexInSuggestedWords,
+ final boolean willAutoCorrect, final boolean omitTypedWord,
+ final int centerPositionInStrip, final int typedWordPositionWhenAutoCorrect) {
+ if (omitTypedWord) {
+ if (indexInSuggestedWords == SuggestedWords.INDEX_OF_TYPED_WORD) {
+ // Ignore.
+ return -1;
+ }
+ if (indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION) {
+ // Center in the suggestion strip.
+ return centerPositionInStrip;
+ }
+ // If neither of those, the order in the suggestion strip is left of the center first
+ // then right of the center, to both edges of the suggestion strip.
+ // For example, center-1, center+1, center-2, center+2, and so on.
+ final int n = indexInSuggestedWords;
+ final int offsetFromCenter = (n % 2) == 0 ? -(n / 2) : (n / 2);
+ final int positionInSuggestionStrip = centerPositionInStrip + offsetFromCenter;
+ return positionInSuggestionStrip;
+ }
+ final int indexToDisplayMostImportantSuggestion;
+ final int indexToDisplaySecondMostImportantSuggestion;
+ if (willAutoCorrect) {
+ indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+ indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
+ } else {
+ indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
+ indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+ }
+ if (indexInSuggestedWords == indexToDisplayMostImportantSuggestion) {
+ // Center in the suggestion strip.
+ return centerPositionInStrip;
+ }
+ if (indexInSuggestedWords == indexToDisplaySecondMostImportantSuggestion) {
+ // Center-1.
+ return typedWordPositionWhenAutoCorrect;
+ }
+ // If neither of those, the order in the suggestion strip is right of the center first
+ // then left of the center, to both edges of the suggestion strip.
+ // For example, Center+1, center-2, center+2, center-3, and so on.
+ final int n = indexInSuggestedWords + 1;
+ final int offsetFromCenter = (n % 2) == 0 ? -(n / 2) : (n / 2);
+ final int positionInSuggestionStrip = centerPositionInStrip + offsetFromCenter;
+ return positionInSuggestionStrip;
+ }
+
+ private int getSuggestionTextColor(final SuggestedWords suggestedWords,
+ final int indexInSuggestedWords) {
+ // Use identity for strings, not #equals : it's the typed word if it's the same object
+ final boolean isTypedWord = suggestedWords.getInfo(indexInSuggestedWords).isKindOf(
+ SuggestedWordInfo.KIND_TYPED);
+
+ final int color;
+ if (indexInSuggestedWords == SuggestedWords.INDEX_OF_AUTO_CORRECTION
+ && suggestedWords.mWillAutoCorrect) {
+ color = mColorAutoCorrect;
+ } else if (isTypedWord && suggestedWords.mTypedWordValid) {
+ color = mColorValidTypedWord;
+ } else if (isTypedWord) {
+ color = mColorTypedWord;
+ } else {
+ color = mColorSuggested;
+ }
+ if (suggestedWords.mIsObsoleteSuggestions && !isTypedWord) {
+ return applyAlpha(color, mAlphaObsoleted);
+ }
+ return color;
+ }
+
+ private static int applyAlpha(final int color, final float alpha) {
+ final int newAlpha = (int)(Color.alpha(color) * alpha);
+ return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
+ }
+
+ private static void addDivider(final ViewGroup stripView, final View dividerView) {
+ stripView.addView(dividerView);
+ final LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams)dividerView.getLayoutParams();
+ params.gravity = Gravity.CENTER;
+ }
+
+ /**
+ * Layout suggestions to the suggestions strip. And returns the start index of more
+ * suggestions.
+ *
+ * @param suggestedWords suggestions to be shown in the suggestions strip.
+ * @param stripView the suggestions strip view.
+ * @param placerView the view where the debug info will be placed.
+ * @return the start index of more suggestions.
+ */
+ public int layoutAndReturnStartIndexOfMoreSuggestions(
+ final Context context,
+ final SuggestedWords suggestedWords,
+ final ViewGroup stripView,
+ final ViewGroup placerView) {
+ if (suggestedWords.isPunctuationSuggestions()) {
+ return layoutPunctuationsAndReturnStartIndexOfMoreSuggestions(
+ (PunctuationSuggestions)suggestedWords, stripView);
+ }
+
+ final int wordCountToShow = suggestedWords.getWordCountToShow(
+ Settings.getInstance().getCurrent().mShouldShowLxxSuggestionUi);
+ final int startIndexOfMoreSuggestions = setupWordViewsAndReturnStartIndexOfMoreSuggestions(
+ suggestedWords, mSuggestionsCountInStrip);
+ final TextView centerWordView = mWordViews.get(mCenterPositionInStrip);
+ final int stripWidth = stripView.getWidth();
+ final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth);
+ if (wordCountToShow == 1 || getTextScaleX(centerWordView.getText(), centerWidth,
+ centerWordView.getPaint()) < MIN_TEXT_XSCALE) {
+ // Layout only the most relevant suggested word at the center of the suggestion strip
+ // by consolidating all slots in the strip.
+ final int countInStrip = 1;
+ mMoreSuggestionsAvailable = (wordCountToShow > countInStrip);
+ layoutWord(context, mCenterPositionInStrip, stripWidth - mPadding);
+ stripView.addView(centerWordView);
+ setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT);
+ if (SuggestionStripView.DBG) {
+ layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth);
+ }
+ final Integer lastIndex = (Integer)centerWordView.getTag();
+ return (lastIndex == null ? 0 : lastIndex) + 1;
+ }
+
+ final int countInStrip = mSuggestionsCountInStrip;
+ mMoreSuggestionsAvailable = (wordCountToShow > countInStrip);
+ @SuppressWarnings("unused")
+ int x = 0;
+ for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
+ if (positionInStrip != 0) {
+ final View divider = mDividerViews.get(positionInStrip);
+ // Add divider if this isn't the left most suggestion in suggestions strip.
+ addDivider(stripView, divider);
+ x += divider.getMeasuredWidth();
+ }
+
+ final int width = getSuggestionWidth(positionInStrip, stripWidth);
+ final TextView wordView = layoutWord(context, positionInStrip, width);
+ stripView.addView(wordView);
+ setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ x += wordView.getMeasuredWidth();
+
+ if (SuggestionStripView.DBG) {
+ layoutDebugInfo(positionInStrip, placerView, x);
+ }
+ }
+ return startIndexOfMoreSuggestions;
+ }
+
+ /**
+ * Format appropriately the suggested word in {@link #mWordViews} specified by
+ * <code>positionInStrip</code>. When the suggested word doesn't exist, the corresponding
+ * {@link TextView} will be disabled and never respond to user interaction. The suggested word
+ * may be shrunk or ellipsized to fit in the specified width.
+ *
+ * The <code>positionInStrip</code> argument is the index in the suggestion strip. The indices
+ * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
+ * The position of the most important suggestion is in {@link #mCenterPositionInStrip}. This
+ * usually doesn't match the index in <code>suggedtedWords</code> -- see
+ * {@link #getPositionInSuggestionStrip(int,SuggestedWords)}.
+ *
+ * @param positionInStrip the position in the suggestion strip.
+ * @param width the maximum width for layout in pixels.
+ * @return the {@link TextView} containing the suggested word appropriately formatted.
+ */
+ private TextView layoutWord(final Context context, final int positionInStrip, final int width) {
+ final TextView wordView = mWordViews.get(positionInStrip);
+ final CharSequence word = wordView.getText();
+ if (positionInStrip == mCenterPositionInStrip && mMoreSuggestionsAvailable) {
+ // TODO: This "more suggestions hint" should have a nicely designed icon.
+ wordView.setCompoundDrawablesWithIntrinsicBounds(
+ null, null, null, mMoreSuggestionsHint);
+ // HACK: Align with other TextViews that have no compound drawables.
+ wordView.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
+ } else {
+ wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+ }
+ // {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack.
+ // Use a simple {@link String} to avoid the issue.
+ wordView.setContentDescription(
+ TextUtils.isEmpty(word)
+ ? context.getResources().getString(R.string.spoken_empty_suggestion)
+ : word.toString());
+ final CharSequence text = getEllipsizedTextWithSettingScaleX(
+ word, width, wordView.getPaint());
+ final float scaleX = wordView.getTextScaleX();
+ wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
+ wordView.setTextScaleX(scaleX);
+ // A <code>wordView</code> should be disabled when <code>word</code> is empty in order to
+ // make it unclickable.
+ // With accessibility touch exploration on, <code>wordView</code> should be enabled even
+ // when it is empty to avoid announcing as "disabled".
+ wordView.setEnabled(!TextUtils.isEmpty(word)
+ || AccessibilityUtils.getInstance().isTouchExplorationEnabled());
+ return wordView;
+ }
+
+ private void layoutDebugInfo(final int positionInStrip, final ViewGroup placerView,
+ final int x) {
+ final TextView debugInfoView = mDebugInfoViews.get(positionInStrip);
+ final CharSequence debugInfo = debugInfoView.getText();
+ if (debugInfo == null) {
+ return;
+ }
+ placerView.addView(debugInfoView);
+ debugInfoView.measure(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+ final int infoWidth = debugInfoView.getMeasuredWidth();
+ final int y = debugInfoView.getMeasuredHeight();
+ ViewLayoutUtils.placeViewAt(
+ debugInfoView, x - infoWidth, y, infoWidth, debugInfoView.getMeasuredHeight());
+ }
+
+ private int getSuggestionWidth(final int positionInStrip, final int maxWidth) {
+ final int paddings = mPadding * mSuggestionsCountInStrip;
+ final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
+ final int availableWidth = maxWidth - paddings - dividers;
+ return (int)(availableWidth * getSuggestionWeight(positionInStrip));
+ }
+
+ private float getSuggestionWeight(final int positionInStrip) {
+ if (positionInStrip == mCenterPositionInStrip) {
+ return mCenterSuggestionWeight;
+ }
+ // TODO: Revisit this for cases of 5 or more suggestions
+ return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1);
+ }
+
+ private int setupWordViewsAndReturnStartIndexOfMoreSuggestions(
+ final SuggestedWords suggestedWords, final int maxSuggestionInStrip) {
+ // Clear all suggestions first
+ for (int positionInStrip = 0; positionInStrip < maxSuggestionInStrip; ++positionInStrip) {
+ final TextView wordView = mWordViews.get(positionInStrip);
+ wordView.setText(null);
+ wordView.setTag(null);
+ // Make this inactive for touches in {@link #layoutWord(int,int)}.
+ if (SuggestionStripView.DBG) {
+ mDebugInfoViews.get(positionInStrip).setText(null);
+ }
+ }
+ int count = 0;
+ int indexInSuggestedWords;
+ for (indexInSuggestedWords = 0; indexInSuggestedWords < suggestedWords.size()
+ && count < maxSuggestionInStrip; indexInSuggestedWords++) {
+ final int positionInStrip =
+ getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords);
+ if (positionInStrip < 0) {
+ continue;
+ }
+ final TextView wordView = mWordViews.get(positionInStrip);
+ // {@link TextView#getTag()} is used to get the index in suggestedWords at
+ // {@link SuggestionStripView#onClick(View)}.
+ wordView.setTag(indexInSuggestedWords);
+ wordView.setText(getStyledSuggestedWord(suggestedWords, indexInSuggestedWords));
+ wordView.setTextColor(getSuggestionTextColor(suggestedWords, indexInSuggestedWords));
+ if (SuggestionStripView.DBG) {
+ mDebugInfoViews.get(positionInStrip).setText(
+ suggestedWords.getDebugString(indexInSuggestedWords));
+ }
+ count++;
+ }
+ return indexInSuggestedWords;
+ }
+
+ private int layoutPunctuationsAndReturnStartIndexOfMoreSuggestions(
+ final PunctuationSuggestions punctuationSuggestions, final ViewGroup stripView) {
+ final int countInStrip = Math.min(punctuationSuggestions.size(), PUNCTUATIONS_IN_STRIP);
+ for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
+ if (positionInStrip != 0) {
+ // Add divider if this isn't the left most suggestion in suggestions strip.
+ addDivider(stripView, mDividerViews.get(positionInStrip));
+ }
+
+ final TextView wordView = mWordViews.get(positionInStrip);
+ final String punctuation = punctuationSuggestions.getLabel(positionInStrip);
+ // {@link TextView#getTag()} is used to get the index in suggestedWords at
+ // {@link SuggestionStripView#onClick(View)}.
+ wordView.setTag(positionInStrip);
+ wordView.setText(punctuation);
+ wordView.setContentDescription(punctuation);
+ wordView.setTextScaleX(1.0f);
+ wordView.setCompoundDrawables(null, null, null, null);
+ wordView.setTextColor(mColorAutoCorrect);
+ stripView.addView(wordView);
+ setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight);
+ }
+ mMoreSuggestionsAvailable = (punctuationSuggestions.size() > countInStrip);
+ return countInStrip;
+ }
+
+ public void layoutImportantNotice(final View importantNoticeStrip,
+ final String importantNoticeTitle) {
+ final TextView titleView = (TextView)importantNoticeStrip.findViewById(
+ R.id.important_notice_title);
+ final int width = titleView.getWidth() - titleView.getPaddingLeft()
+ - titleView.getPaddingRight();
+ titleView.setTextColor(mColorAutoCorrect);
+ titleView.setText(importantNoticeTitle); // TextView.setText() resets text scale x to 1.0.
+ final float titleScaleX = getTextScaleX(importantNoticeTitle, width, titleView.getPaint());
+ titleView.setTextScaleX(titleScaleX);
+ }
+
+ static void setLayoutWeight(final View v, final float weight, final int height) {
+ final ViewGroup.LayoutParams lp = v.getLayoutParams();
+ if (lp instanceof LinearLayout.LayoutParams) {
+ final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
+ llp.weight = weight;
+ llp.width = 0;
+ llp.height = height;
+ }
+ }
+
+ private static float getTextScaleX(@Nullable final CharSequence text, final int maxWidth,
+ final TextPaint paint) {
+ paint.setTextScaleX(1.0f);
+ final int width = getTextWidth(text, paint);
+ if (width <= maxWidth || maxWidth <= 0) {
+ return 1.0f;
+ }
+ return maxWidth / (float) width;
+ }
+
+ @Nullable
+ private static CharSequence getEllipsizedTextWithSettingScaleX(
+ @Nullable final CharSequence text, final int maxWidth, @Nonnull final TextPaint paint) {
+ if (text == null) {
+ return null;
+ }
+ final float scaleX = getTextScaleX(text, maxWidth, paint);
+ if (scaleX >= MIN_TEXT_XSCALE) {
+ paint.setTextScaleX(scaleX);
+ return text;
+ }
+
+ // <code>text</code> must be ellipsized with minimum text scale x.
+ paint.setTextScaleX(MIN_TEXT_XSCALE);
+ final boolean hasBoldStyle = hasStyleSpan(text, BOLD_SPAN);
+ final boolean hasUnderlineStyle = hasStyleSpan(text, UNDERLINE_SPAN);
+ // TextUtils.ellipsize erases any span object existed after ellipsized point.
+ // We have to restore these spans afterward.
+ final CharSequence ellipsizedText = TextUtils.ellipsize(
+ text, paint, maxWidth, TextUtils.TruncateAt.MIDDLE);
+ if (!hasBoldStyle && !hasUnderlineStyle) {
+ return ellipsizedText;
+ }
+ final Spannable spannableText = (ellipsizedText instanceof Spannable)
+ ? (Spannable)ellipsizedText : new SpannableString(ellipsizedText);
+ if (hasBoldStyle) {
+ addStyleSpan(spannableText, BOLD_SPAN);
+ }
+ if (hasUnderlineStyle) {
+ addStyleSpan(spannableText, UNDERLINE_SPAN);
+ }
+ return spannableText;
+ }
+
+ private static boolean hasStyleSpan(@Nullable final CharSequence text,
+ final CharacterStyle style) {
+ if (text instanceof Spanned) {
+ return ((Spanned)text).getSpanStart(style) >= 0;
+ }
+ return false;
+ }
+
+ private static void addStyleSpan(@Nonnull final Spannable text, final CharacterStyle style) {
+ text.removeSpan(style);
+ text.setSpan(style, 0, text.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+
+ private static int getTextWidth(@Nullable final CharSequence text, final TextPaint paint) {
+ if (TextUtils.isEmpty(text)) {
+ return 0;
+ }
+ final int length = text.length();
+ final float[] widths = new float[length];
+ final int count;
+ final Typeface savedTypeface = paint.getTypeface();
+ try {
+ paint.setTypeface(getTextTypeface(text));
+ count = paint.getTextWidths(text, 0, length, widths);
+ } finally {
+ paint.setTypeface(savedTypeface);
+ }
+ int width = 0;
+ for (int i = 0; i < count; i++) {
+ width += Math.round(widths[i] + 0.5f);
+ }
+ return width;
+ }
+
+ private static Typeface getTextTypeface(@Nullable final CharSequence text) {
+ return hasStyleSpan(text, BOLD_SPAN) ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT;
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripView.java
new file mode 100644
index 000000000..9e75a8f8d
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2011 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 org.kelar.inputmethod.latin.suggestions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import androidx.core.view.ViewCompat;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.GestureDetector;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ImageButton;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import org.kelar.inputmethod.accessibility.AccessibilityUtils;
+import org.kelar.inputmethod.keyboard.Keyboard;
+import org.kelar.inputmethod.keyboard.MainKeyboardView;
+import org.kelar.inputmethod.keyboard.MoreKeysPanel;
+import org.kelar.inputmethod.latin.AudioAndHapticFeedbackManager;
+import org.kelar.inputmethod.latin.R;
+import org.kelar.inputmethod.latin.SuggestedWords;
+import org.kelar.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import org.kelar.inputmethod.latin.common.Constants;
+import org.kelar.inputmethod.latin.define.DebugFlags;
+import org.kelar.inputmethod.latin.settings.Settings;
+import org.kelar.inputmethod.latin.settings.SettingsValues;
+import org.kelar.inputmethod.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener;
+import org.kelar.inputmethod.latin.utils.ImportantNoticeUtils;
+
+import java.util.ArrayList;
+
+public final class SuggestionStripView extends RelativeLayout implements OnClickListener,
+ OnLongClickListener {
+ public interface Listener {
+ public void showImportantNoticeContents();
+ public void pickSuggestionManually(SuggestedWordInfo word);
+ public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat);
+ }
+
+ static final boolean DBG = DebugFlags.DEBUG_ENABLED;
+ private static final float DEBUG_INFO_TEXT_SIZE_IN_DIP = 6.0f;
+
+ private final ViewGroup mSuggestionsStrip;
+ private final ImageButton mVoiceKey;
+ private final View mImportantNoticeStrip;
+ MainKeyboardView mMainKeyboardView;
+
+ private final View mMoreSuggestionsContainer;
+ private final MoreSuggestionsView mMoreSuggestionsView;
+ private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
+
+ private final ArrayList<TextView> mWordViews = new ArrayList<>();
+ private final ArrayList<TextView> mDebugInfoViews = new ArrayList<>();
+ private final ArrayList<View> mDividerViews = new ArrayList<>();
+
+ Listener mListener;
+ private SuggestedWords mSuggestedWords = SuggestedWords.getEmptyInstance();
+ private int mStartIndexOfMoreSuggestions;
+
+ private final SuggestionStripLayoutHelper mLayoutHelper;
+ private final StripVisibilityGroup mStripVisibilityGroup;
+
+ private static class StripVisibilityGroup {
+ private final View mSuggestionStripView;
+ private final View mSuggestionsStrip;
+ private final View mImportantNoticeStrip;
+
+ public StripVisibilityGroup(final View suggestionStripView,
+ final ViewGroup suggestionsStrip, final View importantNoticeStrip) {
+ mSuggestionStripView = suggestionStripView;
+ mSuggestionsStrip = suggestionsStrip;
+ mImportantNoticeStrip = importantNoticeStrip;
+ showSuggestionsStrip();
+ }
+
+ public void setLayoutDirection(final boolean isRtlLanguage) {
+ final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL
+ : ViewCompat.LAYOUT_DIRECTION_LTR;
+ ViewCompat.setLayoutDirection(mSuggestionStripView, layoutDirection);
+ ViewCompat.setLayoutDirection(mSuggestionsStrip, layoutDirection);
+ ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection);
+ }
+
+ public void showSuggestionsStrip() {
+ mSuggestionsStrip.setVisibility(VISIBLE);
+ mImportantNoticeStrip.setVisibility(INVISIBLE);
+ }
+
+ public void showImportantNoticeStrip() {
+ mSuggestionsStrip.setVisibility(INVISIBLE);
+ mImportantNoticeStrip.setVisibility(VISIBLE);
+ }
+
+ public boolean isShowingImportantNoticeStrip() {
+ return mImportantNoticeStrip.getVisibility() == VISIBLE;
+ }
+ }
+
+ /**
+ * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user.
+ * @param context
+ * @param attrs
+ */
+ public SuggestionStripView(final Context context, final AttributeSet attrs) {
+ this(context, attrs, R.attr.suggestionStripViewStyle);
+ }
+
+ public SuggestionStripView(final Context context, final AttributeSet attrs,
+ final int defStyle) {
+ super(context, attrs, defStyle);
+
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ inflater.inflate(R.layout.suggestions_strip, this);
+
+ mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
+ mVoiceKey = (ImageButton)findViewById(R.id.suggestions_strip_voice_key);
+ mImportantNoticeStrip = findViewById(R.id.important_notice_strip);
+ mStripVisibilityGroup = new StripVisibilityGroup(this, mSuggestionsStrip,
+ mImportantNoticeStrip);
+
+ for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) {
+ final TextView word = new TextView(context, null, R.attr.suggestionWordStyle);
+ word.setContentDescription(getResources().getString(R.string.spoken_empty_suggestion));
+ word.setOnClickListener(this);
+ word.setOnLongClickListener(this);
+ mWordViews.add(word);
+ final View divider = inflater.inflate(R.layout.suggestion_divider, null);
+ mDividerViews.add(divider);
+ final TextView info = new TextView(context, null, R.attr.suggestionWordStyle);
+ info.setTextColor(Color.WHITE);
+ info.setTextSize(TypedValue.COMPLEX_UNIT_DIP, DEBUG_INFO_TEXT_SIZE_IN_DIP);
+ mDebugInfoViews.add(info);
+ }
+
+ mLayoutHelper = new SuggestionStripLayoutHelper(
+ context, attrs, defStyle, mWordViews, mDividerViews, mDebugInfoViews);
+
+ mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
+ mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
+ .findViewById(R.id.more_suggestions_view);
+ mMoreSuggestionsBuilder = new MoreSuggestions.Builder(context, mMoreSuggestionsView);
+
+ final Resources res = context.getResources();
+ mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset(
+ R.dimen.config_more_suggestions_modal_tolerance);
+ mMoreSuggestionsSlidingDetector = new GestureDetector(
+ context, mMoreSuggestionsSlidingListener);
+
+ final TypedArray keyboardAttr = context.obtainStyledAttributes(attrs,
+ R.styleable.Keyboard, defStyle, R.style.SuggestionStripView);
+ final Drawable iconVoice = keyboardAttr.getDrawable(R.styleable.Keyboard_iconShortcutKey);
+ keyboardAttr.recycle();
+ mVoiceKey.setImageDrawable(iconVoice);
+ mVoiceKey.setOnClickListener(this);
+ }
+
+ /**
+ * A connection back to the input method.
+ * @param listener
+ */
+ public void setListener(final Listener listener, final View inputView) {
+ mListener = listener;
+ mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view);
+ }
+
+ public void updateVisibility(final boolean shouldBeVisible, final boolean isFullscreenMode) {
+ final int visibility = shouldBeVisible ? VISIBLE : (isFullscreenMode ? GONE : INVISIBLE);
+ setVisibility(visibility);
+ final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent();
+ mVoiceKey.setVisibility(currentSettingsValues.mShowsVoiceInputKey ? VISIBLE : INVISIBLE);
+ }
+
+ public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) {
+ clear();
+ mStripVisibilityGroup.setLayoutDirection(isRtlLanguage);
+ mSuggestedWords = suggestedWords;
+ mStartIndexOfMoreSuggestions = mLayoutHelper.layoutAndReturnStartIndexOfMoreSuggestions(
+ getContext(), mSuggestedWords, mSuggestionsStrip, this);
+ mStripVisibilityGroup.showSuggestionsStrip();
+ }
+
+ public void setMoreSuggestionsHeight(final int remainingHeight) {
+ mLayoutHelper.setMoreSuggestionsHeight(remainingHeight);
+ }
+
+ // This method checks if we should show the important notice (checks on permanent storage if
+ // it has been shown once already or not, and if in the setup wizard). If applicable, it shows
+ // the notice. In all cases, it returns true if it was shown, false otherwise.
+ public boolean maybeShowImportantNoticeTitle() {
+ final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent();
+ if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext(), currentSettingsValues)) {
+ return false;
+ }
+ if (getWidth() <= 0) {
+ return false;
+ }
+ final String importantNoticeTitle = ImportantNoticeUtils.getSuggestContactsNoticeTitle(
+ getContext());
+ if (TextUtils.isEmpty(importantNoticeTitle)) {
+ return false;
+ }
+ if (isShowingMoreSuggestionPanel()) {
+ dismissMoreSuggestionsPanel();
+ }
+ mLayoutHelper.layoutImportantNotice(mImportantNoticeStrip, importantNoticeTitle);
+ mStripVisibilityGroup.showImportantNoticeStrip();
+ mImportantNoticeStrip.setOnClickListener(this);
+ return true;
+ }
+
+ public void clear() {
+ mSuggestionsStrip.removeAllViews();
+ removeAllDebugInfoViews();
+ mStripVisibilityGroup.showSuggestionsStrip();
+ dismissMoreSuggestionsPanel();
+ }
+
+ private void removeAllDebugInfoViews() {
+ // The debug info views may be placed as children views of this {@link SuggestionStripView}.
+ for (final View debugInfoView : mDebugInfoViews) {
+ final ViewParent parent = debugInfoView.getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup)parent).removeView(debugInfoView);
+ }
+ }
+ }
+
+ private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() {
+ @Override
+ public void onSuggestionSelected(final SuggestedWordInfo wordInfo) {
+ mListener.pickSuggestionManually(wordInfo);
+ dismissMoreSuggestionsPanel();
+ }
+
+ @Override
+ public void onCancelInput() {
+ dismissMoreSuggestionsPanel();
+ }
+ };
+
+ private final MoreKeysPanel.Controller mMoreSuggestionsController =
+ new MoreKeysPanel.Controller() {
+ @Override
+ public void onDismissMoreKeysPanel() {
+ mMainKeyboardView.onDismissMoreKeysPanel();
+ }
+
+ @Override
+ public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
+ mMainKeyboardView.onShowMoreKeysPanel(panel);
+ }
+
+ @Override
+ public void onCancelMoreKeysPanel() {
+ dismissMoreSuggestionsPanel();
+ }
+ };
+
+ public boolean isShowingMoreSuggestionPanel() {
+ return mMoreSuggestionsView.isShowingInParent();
+ }
+
+ public void dismissMoreSuggestionsPanel() {
+ mMoreSuggestionsView.dismissMoreKeysPanel();
+ }
+
+ @Override
+ public boolean onLongClick(final View view) {
+ AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
+ Constants.NOT_A_CODE, this);
+ return showMoreSuggestions();
+ }
+
+ boolean showMoreSuggestions() {
+ final Keyboard parentKeyboard = mMainKeyboardView.getKeyboard();
+ if (parentKeyboard == null) {
+ return false;
+ }
+ final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper;
+ if (mSuggestedWords.size() <= mStartIndexOfMoreSuggestions) {
+ return false;
+ }
+ final int stripWidth = getWidth();
+ final View container = mMoreSuggestionsContainer;
+ final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
+ final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
+ builder.layout(mSuggestedWords, mStartIndexOfMoreSuggestions, maxWidth,
+ (int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth),
+ layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard);
+ mMoreSuggestionsView.setKeyboard(builder.build());
+ container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
+ final int pointX = stripWidth / 2;
+ final int pointY = -layoutHelper.mMoreSuggestionsBottomGap;
+ moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY,
+ mMoreSuggestionsListener);
+ mOriginX = mLastX;
+ mOriginY = mLastY;
+ for (int i = 0; i < mStartIndexOfMoreSuggestions; i++) {
+ mWordViews.get(i).setPressed(false);
+ }
+ return true;
+ }
+
+ // Working variables for {@link onInterceptTouchEvent(MotionEvent)} and
+ // {@link onTouchEvent(MotionEvent)}.
+ private int mLastX;
+ private int mLastY;
+ private int mOriginX;
+ private int mOriginY;
+ private final int mMoreSuggestionsModalTolerance;
+ private boolean mNeedsToTransformTouchEventToHoverEvent;
+ private boolean mIsDispatchingHoverEventToMoreSuggestions;
+ private final GestureDetector mMoreSuggestionsSlidingDetector;
+ private final GestureDetector.OnGestureListener mMoreSuggestionsSlidingListener =
+ new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onScroll(MotionEvent down, MotionEvent me, float deltaX, float deltaY) {
+ if (down == null) {
+ return false;
+ }
+ final float dy = me.getY() - down.getY();
+ if (deltaY > 0 && dy < 0) {
+ return showMoreSuggestions();
+ }
+ return false;
+ }
+ };
+
+ @Override
+ public boolean onInterceptTouchEvent(final MotionEvent me) {
+ if (mStripVisibilityGroup.isShowingImportantNoticeStrip()) {
+ return false;
+ }
+ // Detecting sliding up finger to show {@link MoreSuggestionsView}.
+ if (!mMoreSuggestionsView.isShowingInParent()) {
+ mLastX = (int)me.getX();
+ mLastY = (int)me.getY();
+ return mMoreSuggestionsSlidingDetector.onTouchEvent(me);
+ }
+ if (mMoreSuggestionsView.isInModalMode()) {
+ return false;
+ }
+
+ final int action = me.getAction();
+ final int index = me.getActionIndex();
+ final int x = (int)me.getX(index);
+ final int y = (int)me.getY(index);
+ if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance
+ || mOriginY - y >= mMoreSuggestionsModalTolerance) {
+ // Decided to be in the sliding suggestion mode only when the touch point has been moved
+ // upward. Further {@link MotionEvent}s will be delivered to
+ // {@link #onTouchEvent(MotionEvent)}.
+ mNeedsToTransformTouchEventToHoverEvent =
+ AccessibilityUtils.getInstance().isTouchExplorationEnabled();
+ mIsDispatchingHoverEventToMoreSuggestions = false;
+ return true;
+ }
+
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
+ // Decided to be in the modal input mode.
+ mMoreSuggestionsView.setModalMode();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) {
+ // Don't populate accessibility event with suggested words and voice key.
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(final MotionEvent me) {
+ if (!mMoreSuggestionsView.isShowingInParent()) {
+ // Ignore any touch event while more suggestions panel hasn't been shown.
+ // Detecting sliding up is done at {@link #onInterceptTouchEvent}.
+ return true;
+ }
+ // In the sliding input mode. {@link MotionEvent} should be forwarded to
+ // {@link MoreSuggestionsView}.
+ final int index = me.getActionIndex();
+ final int x = mMoreSuggestionsView.translateX((int)me.getX(index));
+ final int y = mMoreSuggestionsView.translateY((int)me.getY(index));
+ me.setLocation(x, y);
+ if (!mNeedsToTransformTouchEventToHoverEvent) {
+ mMoreSuggestionsView.onTouchEvent(me);
+ return true;
+ }
+ // In sliding suggestion mode with accessibility mode on, a touch event should be
+ // transformed to a hover event.
+ final int width = mMoreSuggestionsView.getWidth();
+ final int height = mMoreSuggestionsView.getHeight();
+ final boolean onMoreSuggestions = (x >= 0 && x < width && y >= 0 && y < height);
+ if (!onMoreSuggestions && !mIsDispatchingHoverEventToMoreSuggestions) {
+ // Just drop this touch event because dispatching hover event isn't started yet and
+ // the touch event isn't on {@link MoreSuggestionsView}.
+ return true;
+ }
+ final int hoverAction;
+ if (onMoreSuggestions && !mIsDispatchingHoverEventToMoreSuggestions) {
+ // Transform this touch event to a hover enter event and start dispatching a hover
+ // event to {@link MoreSuggestionsView}.
+ mIsDispatchingHoverEventToMoreSuggestions = true;
+ hoverAction = MotionEvent.ACTION_HOVER_ENTER;
+ } else if (me.getActionMasked() == MotionEvent.ACTION_UP) {
+ // Transform this touch event to a hover exit event and stop dispatching a hover event
+ // after this.
+ mIsDispatchingHoverEventToMoreSuggestions = false;
+ mNeedsToTransformTouchEventToHoverEvent = false;
+ hoverAction = MotionEvent.ACTION_HOVER_EXIT;
+ } else {
+ // Transform this touch event to a hover move event.
+ hoverAction = MotionEvent.ACTION_HOVER_MOVE;
+ }
+ me.setAction(hoverAction);
+ mMoreSuggestionsView.onHoverEvent(me);
+ return true;
+ }
+
+ @Override
+ public void onClick(final View view) {
+ AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback(
+ Constants.CODE_UNSPECIFIED, this);
+ if (view == mImportantNoticeStrip) {
+ mListener.showImportantNoticeContents();
+ return;
+ }
+ if (view == mVoiceKey) {
+ mListener.onCodeInput(Constants.CODE_SHORTCUT,
+ Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
+ false /* isKeyRepeat */);
+ return;
+ }
+
+ final Object tag = view.getTag();
+ // {@link Integer} tag is set at
+ // {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and
+ // {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup}
+ if (tag instanceof Integer) {
+ final int index = (Integer) tag;
+ if (index >= mSuggestedWords.size()) {
+ return;
+ }
+ final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
+ mListener.pickSuggestionManually(wordInfo);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ dismissMoreSuggestionsPanel();
+ }
+
+ @Override
+ protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) {
+ // Called by the framework when the size is known. Show the important notice if applicable.
+ // This may be overriden by showing suggestions later, if applicable.
+ if (oldw <= 0 && w > 0) {
+ maybeShowImportantNoticeTitle();
+ }
+ }
+}
diff --git a/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java b/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
new file mode 100644
index 000000000..5af9611cf
--- /dev/null
+++ b/java/src/org/kelar/inputmethod/latin/suggestions/SuggestionStripViewAccessor.java
@@ -0,0 +1,27 @@
+/*
+ * 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 org.kelar.inputmethod.latin.suggestions;
+
+import org.kelar.inputmethod.latin.SuggestedWords;
+
+/**
+ * An object that gives basic control of a suggestion strip and some info on it.
+ */
+public interface SuggestionStripViewAccessor {
+ public void setNeutralSuggestionStrip();
+ public void showSuggestionStrip(final SuggestedWords suggestedWords);
+}