Newer
Older
* 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 com.android.inputmethod.latin;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Message;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
import com.android.inputmethod.compat.FrameLayoutCompatUtils;
import com.android.inputmethod.compat.LinearLayoutCompatUtils;
import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.MoreKeysPanel;
import com.android.inputmethod.keyboard.PointerTracker;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.ArrayList;
public class SuggestionsView extends LinearLayout implements OnClickListener, OnLongClickListener {
public interface Listener {
public boolean addWordToDictionary(String word);
public void pickSuggestionManually(int index, CharSequence word);
}
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
public static final int MAX_SUGGESTIONS = 18;
private static final boolean DBG = LatinImeLogger.sDBG;
private final ViewGroup mSuggestionsPlacer;
private final ViewGroup mSuggestionsStrip;
private KeyboardView mKeyboardView;
private final View mMoreSuggestionsContainer;
private final MoreSuggestionsView mMoreSuggestionsView;
private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
private final PopupWindow mMoreSuggestionsWindow;
private final ArrayList<TextView> mWords = new ArrayList<TextView>();
private final ArrayList<TextView> mInfos = new ArrayList<TextView>();
private final ArrayList<View> mDividers = new ArrayList<View>();
private final PopupWindow mPreviewPopup;
private final TextView mPreviewText;
private SuggestedWords mSuggestions = SuggestedWords.EMPTY;
private boolean mShowingAutoCorrectionInverted;
Amith Yamasani
committed
private final SuggestionsViewParams mParams;
private static final float MIN_TEXT_XSCALE = 0.70f;
private final UiHandler mHandler = new UiHandler(this);
private static class UiHandler extends StaticInnerHandlerWrapper<SuggestionsView> {
private static final int MSG_HIDE_PREVIEW = 0;
private static final int MSG_UPDATE_SUGGESTION = 1;
private static final long DELAY_HIDE_PREVIEW = 1300;
private static final long DELAY_UPDATE_SUGGESTION = 300;
public UiHandler(SuggestionsView outerInstance) {
super(outerInstance);
}
@Override
public void dispatchMessage(Message msg) {
final SuggestionsView suggestionsView = getOuterInstance();
switch (msg.what) {
case MSG_HIDE_PREVIEW:
suggestionsView.hidePreview();
suggestionsView.updateSuggestions();
public void postHidePreview() {
cancelHidePreview();
sendMessageDelayed(obtainMessage(MSG_HIDE_PREVIEW), DELAY_HIDE_PREVIEW);
}
public void cancelHidePreview() {
removeMessages(MSG_HIDE_PREVIEW);
}
public void postUpdateSuggestions() {
sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION),
DELAY_UPDATE_SUGGESTION);
}
public void cancelUpdateSuggestions() {
removeMessages(MSG_UPDATE_SUGGESTION);
}
public void cancelAllMessages() {
cancelHidePreview();
cancelUpdateSuggestions();
}
private static class SuggestionsViewParams {
private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
private static final int DEFAULT_CENTER_SUGGESTION_PERCENTILE = 40;
private static final int PUNCTUATIONS_IN_STRIP = 6;
public final int mPadding;
public final int mDividerWidth;
public final int mSuggestionsStripHeight;
public final int mSuggestionsCountInStrip;
private final List<TextView> mWords;
private final List<View> mDividers;
private final List<TextView> mInfos;
private final int mColorTypedWord;
private final int mColorAutoCorrect;
private final int mColorSuggested;
private final float mCenterSuggestionWeight;
private final int mCenterSuggestionIndex;
private final Drawable mMoreSuggestionsHint;
private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
private final CharacterStyle mInvertedForegroundColorSpan;
private final CharacterStyle mInvertedBackgroundColorSpan;
private static final int AUTO_CORRECT_BOLD = 0x01;
private static final int AUTO_CORRECT_UNDERLINE = 0x02;
private static final int AUTO_CORRECT_INVERT = 0x04;
private static final int VALID_TYPED_WORD_BOLD = 0x08;
private final int mSuggestionStripOption;
private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
public boolean mMoreSuggestionsAvailable;
public final TextView mWordToSaveView;
private final TextView mHintToSaveView;
private final CharSequence mHintToSaveText;
public SuggestionsViewParams(Context context, AttributeSet attrs, int defStyle,
List<TextView> words, List<View> dividers, List<TextView> infos) {
mWords = words;
mDividers = dividers;
mInfos = infos;
final TextView word = words.get(0);
final View divider = dividers.get(0);
mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight();
divider.measure(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
mDividerWidth = divider.getMeasuredWidth();
final Resources res = word.getResources();
mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.SuggestionsView, defStyle, R.style.SuggestionsViewStyle);
mSuggestionStripOption = a.getInt(R.styleable.SuggestionsView_suggestionStripOption, 0);
mColorTypedWord = a.getColor(R.styleable.SuggestionsView_colorTypedWord, 0);
mColorAutoCorrect = a.getColor(R.styleable.SuggestionsView_colorAutoCorrect, 0);
mColorSuggested = a.getColor(R.styleable.SuggestionsView_colorSuggested, 0);
mSuggestionsCountInStrip = a.getInt(
R.styleable.SuggestionsView_suggestionsCountInStrip,
DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
mCenterSuggestionWeight = a.getInt(
R.styleable.SuggestionsView_centerSuggestionPercentile,
DEFAULT_CENTER_SUGGESTION_PERCENTILE) / 100.0f;
mCenterSuggestionIndex = mSuggestionsCountInStrip / 2;
mMoreSuggestionsHint = res.getDrawable(R.drawable.more_suggestions_hint);
mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff);
mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
final LayoutInflater inflater = LayoutInflater.from(context);
mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
mHintToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
mHintToSaveText = context.getText(R.string.hint_add_to_dictionary);
private CharSequence getStyledSuggestionWord(SuggestedWords suggestions, int pos) {
final CharSequence word = suggestions.getWord(pos);
final boolean isAutoCorrect = pos == 1 && willAutoCorrect(suggestions);
final boolean isTypedWordValid = pos == 0 && suggestions.mTypedWordValid;
if (!isAutoCorrect && !isTypedWordValid)
final int len = word.length();
final Spannable spannedWord = new SpannableString(word);
final int option = mSuggestionStripOption;
if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0)
|| (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) {
spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
}
if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) {
spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
private static boolean willAutoCorrect(SuggestedWords suggestions) {
return !suggestions.mTypedWordValid && suggestions.mHasMinimalSuggestion;
}
private int getWordPosition(int index, SuggestedWords suggestions) {
// TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
// suggestions.
final int centerPos = willAutoCorrect(suggestions) ? 1 : 0;
if (index == mCenterSuggestionIndex) {
return centerPos;
} else if (index == centerPos) {
return mCenterSuggestionIndex;
private int getSuggestionTextColor(int index, SuggestedWords suggestions, int pos) {
// TODO: Need to revisit this logic with bigram suggestions
final boolean isSuggested = (pos != 0);
if (index == mCenterSuggestionIndex && willAutoCorrect(suggestions)) {
} else if (isSuggested) {
color = mColorSuggested;
} else {
color = mColorTypedWord;
}
final SuggestedWordInfo info = (pos < suggestions.size())
? suggestions.getInfo(pos) : null;
if (info != null && info.isObsoleteSuggestedWord()) {
return applyAlpha(color, 0.5f);
} else {
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));
}
public CharSequence getInvertedText(CharSequence text) {
if ((mSuggestionStripOption & AUTO_CORRECT_INVERT) == 0)
return null;
final int len = text.length();
final Spannable word = new SpannableString(text);
word.setSpan(mInvertedBackgroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
word.setSpan(mInvertedForegroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
return word;
}
public void layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup placer,
int stripWidth) {
if (suggestions.isPunctuationSuggestions()) {
layoutPunctuationSuggestions(suggestions, stripView);
return;
final int countInStrip = mSuggestionsCountInStrip;
setupTexts(suggestions, countInStrip);
mMoreSuggestionsAvailable = (suggestions.size() > countInStrip);
int x = 0;
for (int index = 0; index < countInStrip; index++) {
final int pos = getWordPosition(index, suggestions);
if (index != 0) {
final View divider = mDividers.get(pos);
// Add divider if this isn't the left most suggestion in suggestions strip.
x += divider.getMeasuredWidth();
final CharSequence styled = mTexts.get(pos);
final TextView word = mWords.get(pos);
if (index == mCenterSuggestionIndex && mMoreSuggestionsAvailable) {
// TODO: This "more suggestions hint" should have nicely designed icon.
word.setCompoundDrawablesWithIntrinsicBounds(
null, null, null, mMoreSuggestionsHint);
// HACK: To align with other TextView that has no compound drawables.
word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
// Disable this suggestion if the suggestion is null or empty.
word.setEnabled(!TextUtils.isEmpty(styled));
word.setTextColor(getSuggestionTextColor(index, suggestions, pos));
final int width = getSuggestionWidth(index, stripWidth);
final CharSequence text = getEllipsizedText(styled, width, word.getPaint());
final float scaleX = word.getTextScaleX();
word.setText(text); // TextView.setText() resets text scale x to 1.0.
word.setTextScaleX(scaleX);
stripView.addView(word);
word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT);
x += word.getMeasuredWidth();
final CharSequence debugInfo = getDebugInfo(suggestions, pos);
if (debugInfo != null) {
final TextView info = mInfos.get(pos);
info.setText(debugInfo);
placer.addView(info);
info.measure(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
final int infoWidth = info.getMeasuredWidth();
final int y = info.getMeasuredHeight();
FrameLayoutCompatUtils.placeViewAt(
info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
private int getSuggestionWidth(int index, int maxWidth) {
final int paddings = mPadding * mSuggestionsCountInStrip;
final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
final int availableWidth = maxWidth - paddings - dividers;
return (int)(availableWidth * getSuggestionWeight(index));
private float getSuggestionWeight(int index) {
if (index == mCenterSuggestionIndex) {
return mCenterSuggestionWeight;
} else {
// TODO: Revisit this for cases of 5 or more suggestions
return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1);
private void setupTexts(SuggestedWords suggestions, int countInStrip) {
final int count = Math.min(suggestions.size(), countInStrip);
for (int pos = 0; pos < count; pos++) {
final CharSequence styled = getStyledSuggestionWord(suggestions, pos);
for (int pos = count; pos < countInStrip; pos++) {
// Make this inactive for touches in layout().
mTexts.add(null);
}
private void layoutPunctuationSuggestions(SuggestedWords suggestions, ViewGroup stripView) {
final int countInStrip = Math.min(suggestions.size(), PUNCTUATIONS_IN_STRIP);
for (int index = 0; index < countInStrip; index++) {
if (index != 0) {
// Add divider if this isn't the left most suggestion in suggestions strip.
stripView.addView(mDividers.get(index));
}
final TextView word = mWords.get(index);
word.setEnabled(true);
word.setTextColor(mColorTypedWord);
final CharSequence text = suggestions.getWord(index);
word.setText(text);
word.setTextScaleX(1.0f);
word.setCompoundDrawables(null, null, null, null);
setLayoutWeight(word, 1.0f, mSuggestionsStripHeight);
mMoreSuggestionsAvailable = false;
public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView,
int stripWidth) {
final int width = stripWidth - mDividerWidth - mPadding * 2;
final TextView wordView = mWordToSaveView;
wordView.setTextColor(mColorTypedWord);
final int wordWidth = (int)(width * mCenterSuggestionWeight);
final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint());
final float wordScaleX = wordView.getTextScaleX();
wordView.setTag(word);
wordView.setText(text);
wordView.setTextScaleX(wordScaleX);
stripView.addView(wordView);
setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
stripView.addView(mDividers.get(0));
final TextView hintView = mHintToSaveView;
hintView.setTextColor(mColorAutoCorrect);
final int hintWidth = width - wordWidth;
final float hintScaleX = getTextScaleX(mHintToSaveText, hintWidth, hintView.getPaint());
hintView.setText(mHintToSaveText);
hintView.setTextScaleX(hintScaleX);
stripView.addView(hintView);
hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
* Construct a {@link SuggestionsView} for showing suggested words for completion.
* @param context
* @param attrs
*/
public SuggestionsView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.suggestionsViewStyle);
public SuggestionsView(Context context, AttributeSet attrs, int defStyle) {
// Note: Up to version 10 (Gingerbread) of the API, LinearLayout doesn't have 3-argument
// constructor.
// TODO: Call 3-argument constructor, super(context, attrs, defStyle), when we abandon
// backward compatibility with the version 10 or earlier of the API.
super(context, attrs);
if (defStyle != R.attr.suggestionsViewStyle) {
throw new IllegalArgumentException(
"can't accept defStyle other than R.attr.suggestionsViewStyle: defStyle="
+ defStyle);
}
setBackgroundDrawable(LinearLayoutCompatUtils.getBackgroundDrawable(
context, attrs, defStyle, R.style.SuggestionsViewStyle));
final LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.suggestions_strip, this);
mPreviewPopup = new PopupWindow(context);
mPreviewText = (TextView) inflater.inflate(R.layout.suggestion_preview, null);
mPreviewPopup.setWindowLayoutMode(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mPreviewPopup.setContentView(mPreviewText);
mPreviewPopup.setBackgroundDrawable(null);
mSuggestionsPlacer = (ViewGroup)findViewById(R.id.suggestions_placer);
mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) {
final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null);
word.setOnClickListener(this);
word.setOnLongClickListener(this);
final View divider = inflater.inflate(R.layout.suggestion_divider, null);
divider.setOnClickListener(this);
mDividers.add(divider);
mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null));
mParams = new SuggestionsViewParams(context, attrs, defStyle, mWords, mDividers, mInfos);
mParams.mWordToSaveView.setOnClickListener(this);
mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
.findViewById(R.id.more_suggestions_view);
mMoreSuggestionsBuilder = new MoreSuggestions.Builder(mMoreSuggestionsView);
mMoreSuggestionsWindow = new PopupWindow(context);
mMoreSuggestionsWindow.setWindowLayoutMode(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
mMoreSuggestionsWindow.setBackgroundDrawable(null);
* A connection back to the input method.
* @param listener
*/
public void setListener(Listener listener, View inputView) {
mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view);
public void setSuggestions(SuggestedWords suggestions) {
if (suggestions == null)
return;
mSuggestions = suggestions;
if (mShowingAutoCorrectionInverted) {
mHandler.postUpdateSuggestions();
updateSuggestions();
private void updateSuggestions() {
if (mSuggestions.size() == 0)
mParams.layout(mSuggestions, mSuggestionsStrip, mSuggestionsPlacer, getWidth());
private static CharSequence getDebugInfo(SuggestedWords suggestions, int pos) {
if (DBG && pos < suggestions.size()) {
final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
if (wordInfo != null) {
final CharSequence debugInfo = wordInfo.getDebugString();
if (!TextUtils.isEmpty(debugInfo)) {
return debugInfo;
private static void setLayoutWeight(View v, float weight, 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;
private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) {
paint.setTextScaleX(1.0f);
final int width = getTextWidth(text, paint);
if (width <= maxWidth) {
return 1.0f;
}
return maxWidth / (float)width;
}
private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
TextPaint paint) {
paint.setTextScaleX(1.0f);
final int width = getTextWidth(text, paint);
if (width <= maxWidth) {
return text;
}
final float scaleX = maxWidth / (float)width;
if (scaleX >= MIN_TEXT_XSCALE) {
paint.setTextScaleX(scaleX);
return text;
// Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To get
// squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
final CharSequence ellipsized = TextUtils.ellipsize(
text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
paint.setTextScaleX(MIN_TEXT_XSCALE);
return ellipsized;
private static int getTextWidth(CharSequence text, TextPaint paint) {
if (TextUtils.isEmpty(text)) return 0;
final Typeface savedTypeface = paint.getTypeface();
paint.setTypeface(getTextTypeface(text));
final int len = text.length();
final float[] widths = new float[len];
final int count = paint.getTextWidths(text, 0, len, widths);
for (int i = 0; i < count; i++) {
width += Math.round(widths[i] + 0.5f);
paint.setTypeface(savedTypeface);
return width;
private static Typeface getTextTypeface(CharSequence text) {
if (!(text instanceof SpannableString))
return Typeface.DEFAULT;
final SpannableString ss = (SpannableString)text;
final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
if (styles.length == 0)
return Typeface.DEFAULT;
switch (styles[0].getStyle()) {
case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
// TODO: BOLD_ITALIC, ITALIC case?
default: return Typeface.DEFAULT;
public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) {
final CharSequence inverted = mParams.getInvertedText(autoCorrectedWord);
final TextView tv = mWords.get(1);
mShowingAutoCorrectionInverted = true;
}
Amith Yamasani
committed
public boolean isShowingAddToDictionaryHint() {
return mSuggestionsStrip.getChildCount() > 0
&& mSuggestionsStrip.getChildAt(0) == mParams.mWordToSaveView;
Amith Yamasani
committed
}
public void showAddToDictionaryHint(CharSequence word) {
mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth());
}
public boolean dismissAddToDictionaryHint() {
if (isShowingAddToDictionaryHint()) {
clear();
return true;
}
return false;
public SuggestedWords getSuggestions() {
return mSuggestions;
}
public void clear() {
mShowingAutoCorrectionInverted = false;
mSuggestionsPlacer.removeAllViews();
mSuggestionsPlacer.addView(mSuggestionsStrip);
mSuggestionsStrip.removeAllViews();
dismissMoreSuggestions();

Amith Yamasani
committed
private void hidePreview() {
mPreviewPopup.dismiss();

Amith Yamasani
committed
}
private void showPreview(View view, CharSequence word) {
if (TextUtils.isEmpty(word))
return;
final TextView previewText = mPreviewText;
previewText.setTextColor(mParams.mColorTypedWord);
previewText.setText(word);
previewText.measure(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
final int[] offsetInWindow = new int[2];
view.getLocationInWindow(offsetInWindow);
final int posX = offsetInWindow[0];
final int posY = offsetInWindow[1] - previewText.getMeasuredHeight();
final PopupWindow previewPopup = mPreviewPopup;
if (previewPopup.isShowing()) {
previewPopup.update(posX, posY, previewPopup.getWidth(), previewPopup.getHeight());
} else {
previewPopup.showAtLocation(this, Gravity.NO_GRAVITY, posX, posY);
previewText.setVisibility(VISIBLE);
private void addToDictionary(CharSequence word) {
if (mListener.addWordToDictionary(word.toString())) {
final CharSequence message = getContext().getString(R.string.added_word, word);
showPreview(mParams.mWordToSaveView, message);
private final KeyboardActionListener mMoreSuggestionsListener =
new KeyboardActionListener.Adapter() {
@Override
public boolean onCustomRequest(int requestCode) {
final int index = requestCode;
final CharSequence word = mSuggestions.getWord(index);
mListener.pickSuggestionManually(index, word);
dismissMoreSuggestions();
return true;
}
@Override
public void onCancelInput() {
dismissMoreSuggestions();
}
};
private final MoreKeysPanel.Controller mMoreSuggestionsController =
new MoreKeysPanel.Controller() {
@Override
public boolean dismissMoreKeysPanel() {
return dismissMoreSuggestions();
private boolean dismissMoreSuggestions() {
if (mMoreSuggestionsWindow.isShowing()) {
mMoreSuggestionsWindow.dismiss();
mKeyboardView.dimEntireKeyboard(false);
return true;
}
return false;
}
@Override
public boolean onLongClick(View view) {
final SuggestionsViewParams params = mParams;
if (params.mMoreSuggestionsAvailable) {
final int stripWidth = getWidth();
final View container = mMoreSuggestionsContainer;
final int maxWidth = stripWidth - container.getPaddingLeft()
- container.getPaddingRight();
final DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
// TODO: Revise how we determine the height
final int maxHeight = dm.heightPixels - mKeyboardView.getHeight() - getHeight() * 3;
final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
builder.layout(mSuggestions, params.mSuggestionsCountInStrip, maxWidth, maxHeight);
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 = 0;
moreKeysPanel.showMoreKeysPanel(
this, mMoreSuggestionsController, pointX, pointY,
mMoreSuggestionsWindow, mMoreSuggestionsListener);
// TODO: Should figure out how to select the pointer tracker correctly.
final PointerTracker tracker = PointerTracker.getPointerTracker(0, moreKeysPanel);
final int translatedX = moreKeysPanel.translateX(tracker.getLastX());
final int translatedY = moreKeysPanel.translateY(tracker.getLastY());
tracker.onShowMoreKeysPanel(
translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel);
view.setPressed(false);
mKeyboardView.dimEntireKeyboard(true);
return true;
}
return false;
}
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
public boolean dispatchTouchEvent(MotionEvent me) {
if (!mMoreSuggestionsWindow.isShowing()) {
return super.dispatchTouchEvent(me);
}
final int action = me.getAction();
final long eventTime = me.getEventTime();
final int index = me.getActionIndex();
final int id = me.getPointerId(index);
final PointerTracker tracker = PointerTracker.getPointerTracker(id, mMoreSuggestionsView);
final int x = mMoreSuggestionsView.translateX((int)me.getX(index));
final int y = mMoreSuggestionsView.translateY((int)me.getY(index));
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
tracker.onDownEvent(x, y, eventTime, mMoreSuggestionsView);
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
tracker.onUpEvent(x, y, eventTime);
break;
case MotionEvent.ACTION_MOVE:
tracker.onMoveEvent(x, y, eventTime);
break;
case MotionEvent.ACTION_CANCEL:
tracker.onCancelEvent(x, y, eventTime);
break;
}
return true;
}
@Override
public void onClick(View view) {
if (view == mParams.mWordToSaveView) {
addToDictionary((CharSequence)view.getTag());
clear();
return;
}
final Object tag = view.getTag();
if (!(tag instanceof Integer))
return;
final int index = (Integer) tag;
if (index >= mSuggestions.size())
return;
final CharSequence word = mSuggestions.getWord(index);
mListener.pickSuggestionManually(index, word);

Amith Yamasani
committed
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();

Amith Yamasani
committed
hidePreview();
}