diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
17 files changed, 779 insertions, 314 deletions
diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java index 307b81d43..460930f16 100644 --- a/java/src/com/android/inputmethod/latin/AutoDictionary.java +++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Google Inc. + * Copyright (C) 2010 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 @@ -41,13 +41,8 @@ public class AutoDictionary extends ExpandableDictionary { static final int FREQUENCY_FOR_PICKED = 3; // Weight added to a user typing a new word that doesn't get corrected (or is reverted) static final int FREQUENCY_FOR_TYPED = 1; - // A word that is frequently typed and gets promoted to the user dictionary, uses this - // frequency. - static final int FREQUENCY_FOR_AUTO_ADD = 250; // If the user touches a typed word 2 times or more, it will become valid. private static final int VALIDITY_THRESHOLD = 2 * FREQUENCY_FOR_PICKED; - // If the user touches a typed word 4 times or more, it will be added to the user dict. - private static final int PROMOTION_THRESHOLD = 4 * FREQUENCY_FOR_PICKED; private LatinIME mIme; // Locale for which this auto dictionary is storing words @@ -151,11 +146,6 @@ public class AutoDictionary extends ExpandableDictionary { freq = freq < 0 ? addFrequency : freq + addFrequency; super.addWord(word, freq); - if (freq >= PROMOTION_THRESHOLD) { - mIme.promoteToUserDictionary(word, FREQUENCY_FOR_AUTO_ADD); - freq = 0; - } - synchronized (mPendingWritesLock) { // Write a null frequency if it is to be deleted from the db mPendingWrites.put(word, freq == 0 ? null : new Integer(freq)); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index d95fb9638..9748d6006 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -196,8 +196,9 @@ public class BinaryDictionary extends Dictionary { Arrays.fill(outputChars, (char) 0); Arrays.fill(scores, 0); + final int proximityInfo = keyboard == null ? 0 : keyboard.getProximityInfo(); return getSuggestionsNative( - mNativeDict, keyboard.getProximityInfo(), + mNativeDict, proximityInfo, codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, mFlags, outputChars, scores); } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 562580d41..7ce92920d 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -78,16 +78,20 @@ class BinaryDictionaryGetter { } else { try { // If that was no-go, try to find a publicly exported dictionary. - return BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context); + List<AssetFileAddress> listFromContentProvider = + BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context); + if (null != listFromContentProvider) { + return listFromContentProvider; + } + // If the list is null, fall through and return the fallback } catch (FileNotFoundException e) { Log.e(TAG, "Unable to create dictionary file from provider for locale " + locale.toString() + ": falling back to internal dictionary"); - return Arrays.asList(loadFallbackResource(context, fallbackResId)); } catch (IOException e) { Log.e(TAG, "Unable to read source data for locale " + locale.toString() + ": falling back to internal dictionary"); - return Arrays.asList(loadFallbackResource(context, fallbackResId)); } + return Arrays.asList(loadFallbackResource(context, fallbackResId)); } } } diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index abdf30e6b..09fd3b473 100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java @@ -16,10 +16,9 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Typeface; import android.os.Handler; @@ -45,28 +44,53 @@ 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.latin.SuggestedWords.SuggestedWordInfo; + import java.util.ArrayList; import java.util.List; public class CandidateView extends LinearLayout implements OnClickListener, OnLongClickListener { + public interface Listener { + public boolean addWordToDictionary(String word); + public void pickSuggestionManually(int index, CharSequence word); + } + private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); - private static final int MAX_SUGGESTIONS = 16; + // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. + private static final int MAX_SUGGESTIONS = 18; + private static final int UNSPECIFIED_MEASURESPEC = MeasureSpec.makeMeasureSpec( + 0, MeasureSpec.UNSPECIFIED); private static final boolean DBG = LatinImeLogger.sDBG; - private final ArrayList<View> mWords = new ArrayList<View>(); - private final boolean mConfigCandidateHighlightFontColorEnabled; + private static final int NUM_CANDIDATES_IN_STRIP = 3; + private final ImageView mExpandCandidatesPane; + private final ImageView mCloseCandidatesPane; + private ViewGroup mCandidatesPane; + private ViewGroup mCandidatesPaneContainer; + private View mKeyboardView; + 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 int mCandidatePadding; + private final int mCandidateStripHeight; private final CharacterStyle mInvertedForegroundColorSpan; private final CharacterStyle mInvertedBackgroundColorSpan; - private final int mColorNormal; - private final int mColorRecommended; - private final int mColorOther; + private final int mAutoCorrectHighlight; + 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 final int mColorTypedWord; + private final int mColorAutoCorrect; + private final int mColorSuggestedCandidate; private final PopupWindow mPreviewPopup; private final TextView mPreviewText; - private LatinIME mService; + private Listener mListener; private SuggestedWords mSuggestions = SuggestedWords.EMPTY; private boolean mShowingAutoCorrectionInverted; private boolean mShowingAddToDictionary; @@ -123,46 +147,113 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo * @param attrs */ public CandidateView(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.candidateViewStyle); + } + + public CandidateView(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.candidateViewStyle) { + throw new IllegalArgumentException( + "can't accept defStyle other than R.attr.candidayeViewStyle: defStyle=" + + defStyle); + } + setBackgroundDrawable(LinearLayoutCompatUtils.getBackgroundDrawable( + context, attrs, defStyle, R.style.CandidateViewStyle)); Resources res = context.getResources(); - mPreviewPopup = new PopupWindow(context); LayoutInflater inflater = LayoutInflater.from(context); + inflater.inflate(R.layout.candidates_strip, this); + + mPreviewPopup = new PopupWindow(context); mPreviewText = (TextView) inflater.inflate(R.layout.candidate_preview, null); mPreviewPopup.setWindowLayoutMode(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); mPreviewPopup.setContentView(mPreviewText); mPreviewPopup.setBackgroundDrawable(null); - mConfigCandidateHighlightFontColorEnabled = - res.getBoolean(R.bool.config_candidate_highlight_font_color_enabled); - mColorNormal = res.getColor(R.color.candidate_normal); - mColorRecommended = res.getColor(R.color.candidate_recommended); - mColorOther = res.getColor(R.color.candidate_other); - mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorNormal ^ 0x00ffffff); - mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorNormal); + mCandidatePadding = res.getDimensionPixelOffset(R.dimen.candidate_padding); + mCandidateStripHeight = res.getDimensionPixelOffset(R.dimen.candidate_strip_height); for (int i = 0; i < MAX_SUGGESTIONS; i++) { - View v = inflater.inflate(R.layout.candidate, null); - TextView tv = (TextView)v.findViewById(R.id.candidate_word); - tv.setTag(i); - tv.setOnClickListener(this); + final TextView word, info; + switch (i) { + case 0: + word = (TextView)findViewById(R.id.word_left); + word.setPadding(mCandidatePadding, 0, 0, 0); + info = (TextView)findViewById(R.id.info_left); + break; + case 1: + word = (TextView)findViewById(R.id.word_center); + info = (TextView)findViewById(R.id.info_center); + break; + case 2: + word = (TextView)findViewById(R.id.word_right); + info = (TextView)findViewById(R.id.info_right); + break; + default: + word = (TextView)inflater.inflate(R.layout.candidate_word, null); + info = (TextView)inflater.inflate(R.layout.candidate_info, null); + break; + } + word.setTag(i); + word.setOnClickListener(this); if (i == 0) - tv.setOnLongClickListener(this); - ImageView divider = (ImageView)v.findViewById(R.id.candidate_divider); - // Do not display divider of first candidate. - divider.setVisibility(i == 0 ? INVISIBLE : VISIBLE); - mWords.add(v); + word.setOnLongClickListener(this); + mWords.add(word); + mInfos.add(info); + if (i > 0) { + final View divider = inflater.inflate(R.layout.candidate_divider, null); + divider.measure(UNSPECIFIED_MEASURESPEC, UNSPECIFIED_MEASURESPEC); + mDividers.add(divider); + } } - scrollTo(0, getScrollY()); + final TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle); + mAutoCorrectHighlight = a.getInt(R.styleable.CandidateView_autoCorrectHighlight, 0); + mColorTypedWord = a.getColor(R.styleable.CandidateView_colorTypedWord, 0); + mColorAutoCorrect = a.getColor(R.styleable.CandidateView_colorAutoCorrect, 0); + mColorSuggestedCandidate = a.getColor(R.styleable.CandidateView_colorSuggested, 0); + mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff); + mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord); + + mExpandCandidatesPane = (ImageView)findViewById(R.id.expand_candidates_pane); + mExpandCandidatesPane.setImageDrawable( + a.getDrawable(R.styleable.CandidateView_iconExpandPane)); + mExpandCandidatesPane.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + expandCandidatesPane(); + } + }); + mCloseCandidatesPane = (ImageView)findViewById(R.id.close_candidates_pane); + mCloseCandidatesPane.setImageDrawable( + a.getDrawable(R.styleable.CandidateView_iconClosePane)); + mCloseCandidatesPane.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + closeCandidatesPane(); + } + }); + + a.recycle(); } /** - * A connection back to the service to communicate with the text field + * A connection back to the input method. * @param listener */ - public void setService(LatinIME listener) { - mService = listener; + public void setListener(Listener listener, View inputView) { + mListener = listener; + mKeyboardView = inputView.findViewById(R.id.keyboard_view); + mCandidatesPane = FrameLayoutCompatUtils.getPlacer( + (ViewGroup)inputView.findViewById(R.id.candidates_pane)); + mCandidatesPane.setOnClickListener(this); + mCandidatesPaneContainer = (ViewGroup)inputView.findViewById( + R.id.candidates_pane_container); } public void setSuggestions(SuggestedWords suggestions) { @@ -176,80 +267,170 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo } } + private CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect) { + if (!isAutoCorrect) + return word; + final Spannable spannedWord = new SpannableString(word); + if ((mAutoCorrectHighlight & AUTO_CORRECT_BOLD) != 0) + spannedWord.setSpan(BOLD_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + if ((mAutoCorrectHighlight & AUTO_CORRECT_UNDERLINE) != 0) + spannedWord.setSpan(UNDERLINE_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + return spannedWord; + } + + private int getCandidateTextColor(boolean isAutoCorrect, boolean isSuggestedCandidate, + SuggestedWordInfo info) { + final int color; + if (isAutoCorrect) { + color = mColorAutoCorrect; + } else if (isSuggestedCandidate) { + color = mColorSuggestedCandidate; + } else { + color = mColorTypedWord; + } + if (info != null && info.isPreviousSuggestedWord()) { + final int newAlpha = (int)(Color.alpha(color) * 0.5f); + return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); + } else { + return color; + } + } + private void updateSuggestions() { final SuggestedWords suggestions = mSuggestions; + final List<SuggestedWordInfo> suggestedWordInfoList = suggestions.mSuggestedWordInfoList; + clear(); + final int paneWidth = getWidth(); + final int dividerWidth = mDividers.get(0).getMeasuredWidth(); + final int dividerHeight = mDividers.get(0).getMeasuredHeight(); + int x = 0; + int y = 0; + int fromIndex = NUM_CANDIDATES_IN_STRIP; final int count = Math.min(mWords.size(), suggestions.size()); + closeCandidatesPane(); + mExpandCandidatesPane.setEnabled(count >= NUM_CANDIDATES_IN_STRIP); for (int i = 0; i < count; i++) { - CharSequence word = suggestions.getWord(i); - if (word == null) continue; - final int wordLength = word.length(); - final List<SuggestedWordInfo> suggestedWordInfoList = - suggestions.mSuggestedWordInfoList; - - final View v = mWords.get(i); - final TextView tv = (TextView)v.findViewById(R.id.candidate_word); - final TextView dv = (TextView)v.findViewById(R.id.candidate_debug_info); - tv.setTextColor(mColorNormal); - // TODO: Needs safety net? - if (suggestions.mHasMinimalSuggestion + final CharSequence suggestion = suggestions.getWord(i); + if (suggestion == null) continue; + + final SuggestedWordInfo suggestionInfo = (suggestedWordInfoList != null) + ? suggestedWordInfoList.get(i) : null; + final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion && ((i == 1 && !suggestions.mTypedWordValid) - || (i == 0 && suggestions.mTypedWordValid))) { - final CharacterStyle style; - if (mConfigCandidateHighlightFontColorEnabled) { - style = BOLD_SPAN; - tv.setTextColor(mColorRecommended); - } else { - style = UNDERLINE_SPAN; - } - final Spannable spannedWord = new SpannableString(word); - spannedWord.setSpan(style, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - word = spannedWord; - } else if (i != 0 || (wordLength == 1 && count > 1)) { - // HACK: even if i == 0, we use mColorOther when this - // suggestion's length is 1 - // and there are multiple suggestions, such as the default - // punctuation list. - if (mConfigCandidateHighlightFontColorEnabled) - tv.setTextColor(mColorOther); + || (i == 0 && suggestions.mTypedWordValid)); + // HACK: even if i == 0, we use mColorOther when this suggestion's length is 1 + // and there are multiple suggestions, such as the default punctuation list. + // TODO: Need to revisit this logic with bigram suggestions + final boolean isSuggestedCandidate = (i != 0); + final boolean isPunctuationSuggestions = (suggestion.length() == 1 && count > 1); + + final TextView word = mWords.get(i); + // TODO: Reorder candidates in strip as appropriate. The center candidate should hold + // the word when space is typed (valid typed word or auto corrected word). + word.setTextColor(getCandidateTextColor(isAutoCorrect, + isSuggestedCandidate || isPunctuationSuggestions, suggestionInfo)); + word.setText(getStyledCandidateWord(suggestion, isAutoCorrect)); + // TODO: call TextView.setTextScaleX() to fit the candidate in single line. + word.measure(UNSPECIFIED_MEASURESPEC, UNSPECIFIED_MEASURESPEC); + final int width = word.getMeasuredWidth(); + final int height = word.getMeasuredHeight(); + + final TextView info; + if (DBG && suggestionInfo != null + && !TextUtils.isEmpty(suggestionInfo.getDebugString())) { + info = mInfos.get(i); + info.setText(suggestionInfo.getDebugString()); + info.setVisibility(View.VISIBLE); + info.measure(UNSPECIFIED_MEASURESPEC, UNSPECIFIED_MEASURESPEC); + } else { + info = null; } - tv.setText(word); - tv.setClickable(true); - - if (suggestedWordInfoList != null && suggestedWordInfoList.get(i) != null) { - final SuggestedWordInfo info = suggestedWordInfoList.get(i); - if (info.isPreviousSuggestedWord()) { - int color = tv.getCurrentTextColor(); - tv.setTextColor(Color.argb((int)(Color.alpha(color) * 0.5f), Color.red(color), - Color.green(color), Color.blue(color))); - } - final String debugString = info.getDebugString(); - if (DBG) { - if (TextUtils.isEmpty(debugString)) { - dv.setVisibility(GONE); - } else { - dv.setText(debugString); - dv.setVisibility(VISIBLE); - } - } else { - dv.setVisibility(GONE); + + if (i < NUM_CANDIDATES_IN_STRIP) { + if (info != null) { + final int infoWidth = info.getMeasuredWidth(); + FrameLayoutCompatUtils.placeViewAt( + info, x + width - infoWidth, y, infoWidth, info.getMeasuredHeight()); } } else { - dv.setVisibility(GONE); + // TODO: Handle overflow case. + if (dividerWidth + x + width >= paneWidth) { + centeringCandidates(fromIndex, i - 1, x, paneWidth); + x = 0; + y += mCandidateStripHeight; + fromIndex = i; + } + if (x != 0) { + final View divider = mDividers.get(i - NUM_CANDIDATES_IN_STRIP); + mCandidatesPane.addView(divider); + FrameLayoutCompatUtils.placeViewAt( + divider, x, y + (mCandidateStripHeight - dividerHeight) / 2, + dividerWidth, dividerHeight); + x += dividerWidth; + } + mCandidatesPane.addView(word); + FrameLayoutCompatUtils.placeViewAt( + word, x, y + (mCandidateStripHeight - height) / 2, width, height); + if (info != null) { + mCandidatesPane.addView(info); + final int infoWidth = info.getMeasuredWidth(); + FrameLayoutCompatUtils.placeViewAt( + info, x + width - infoWidth, y, infoWidth, info.getMeasuredHeight()); + } + x += width; } - addView(v); } + if (x != 0) { + // Centering last candidates row. + centeringCandidates(fromIndex, count - 1, x, paneWidth); + } + } + + private void centeringCandidates(int from, int to, int width, int paneWidth) { + final ViewGroup pane = mCandidatesPane; + final int fromIndex = pane.indexOfChild(mWords.get(from)); + final int toIndex; + if (mInfos.get(to).getParent() != null) { + toIndex = pane.indexOfChild(mInfos.get(to)); + } else { + toIndex = pane.indexOfChild(mWords.get(to)); + } + final int offset = (paneWidth - width) / 2; + for (int index = fromIndex; index <= toIndex; index++) { + offsetMargin(pane.getChildAt(index), offset, 0); + } + } + + private static void offsetMargin(View v, int dx, int dy) { + if (v == null) + return; + final ViewGroup.LayoutParams lp = v.getLayoutParams(); + if (lp instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams)lp; + mlp.setMargins(mlp.leftMargin + dx, mlp.topMargin + dy, 0, 0); + } + } + + private void expandCandidatesPane() { + mExpandCandidatesPane.setVisibility(View.GONE); + mCloseCandidatesPane.setVisibility(View.VISIBLE); + mCandidatesPaneContainer.setMinimumHeight(mKeyboardView.getMeasuredHeight()); + mCandidatesPaneContainer.setVisibility(View.VISIBLE); + mKeyboardView.setVisibility(View.GONE); + } - scrollTo(0, getScrollY()); - requestLayout(); + private void closeCandidatesPane() { + mExpandCandidatesPane.setVisibility(View.VISIBLE); + mCloseCandidatesPane.setVisibility(View.GONE); + mCandidatesPaneContainer.setVisibility(View.GONE); + mKeyboardView.setVisibility(View.VISIBLE); } public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) { - // Displaying auto corrected word as inverted is enabled only when highlighting candidate - // with color is disabled. - if (mConfigCandidateHighlightFontColorEnabled) + if ((mAutoCorrectHighlight & AUTO_CORRECT_INVERT) == 0) return; - final TextView tv = (TextView)mWords.get(1).findViewById(R.id.candidate_word); + final TextView tv = mWords.get(1); final Spannable word = new SpannableString(autoCorrectedWord); final int wordLength = word.length(); word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength, @@ -260,10 +441,6 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo mShowingAutoCorrectionInverted = true; } - public boolean isConfigCandidateHighlightFontColorEnabled() { - return mConfigCandidateHighlightFontColorEnabled; - } - public boolean isShowingAddToDictionaryHint() { return mShowingAddToDictionary; } @@ -275,7 +452,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo setSuggestions(builder.build()); mShowingAddToDictionary = true; // Disable R.string.hint_add_to_dictionary button - TextView tv = (TextView)getChildAt(1).findViewById(R.id.candidate_word); + TextView tv = mWords.get(1); tv.setClickable(false); } @@ -292,7 +469,11 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo public void clear() { mShowingAddToDictionary = false; mShowingAutoCorrectionInverted = false; - removeAllViews(); + for (int i = 0; i < NUM_CANDIDATES_IN_STRIP; i++) { + mWords.get(i).setText(null); + mInfos.get(i).setVisibility(View.GONE); + } + mCandidatesPane.removeAllViews(); } private void hidePreview() { @@ -304,11 +485,11 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo return; final TextView previewText = mPreviewText; - previewText.setTextColor(mColorNormal); + previewText.setTextColor(mColorTypedWord); previewText.setText(word); previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - View v = getChildAt(index); + View v = mWords.get(index); final int[] offsetInWindow = new int[2]; v.getLocationInWindow(offsetInWindow); final int posX = offsetInWindow[0]; @@ -324,15 +505,21 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo } private void addToDictionary(CharSequence word) { - if (mService.addWordToDictionary(word.toString())) { + if (mListener.addWordToDictionary(word.toString())) { showPreview(0, getContext().getString(R.string.added_word, word)); } } @Override public boolean onLongClick(View view) { - int index = (Integer) view.getTag(); - CharSequence word = mSuggestions.getWord(index); + final Object tag = view.getTag(); + if (!(tag instanceof Integer)) + return true; + final int index = (Integer) tag; + if (index >= mSuggestions.size()) + return true; + + final CharSequence word = mSuggestions.getWord(index); if (word.length() < 2) return false; addToDictionary(word); @@ -341,13 +528,22 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo @Override public void onClick(View view) { - int index = (Integer) view.getTag(); - CharSequence word = mSuggestions.getWord(index); + 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); if (mShowingAddToDictionary && index == 0) { addToDictionary(word); } else { - mService.pickSuggestionManually(index, word); + mListener.pickSuggestionManually(index, word); } + // Because some punctuation letters are not treated as word separator depending on locale, + // {@link #setSuggestions} might not be called and candidates pane left opened. + closeCandidatesPane(); } @Override diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java index b057cf4e3..66a041508 100644 --- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java @@ -40,7 +40,7 @@ public class ContactsDictionary extends ExpandableDictionary { /** * Frequency for contacts information into the dictionary */ - private static final int FREQUENCY_FOR_CONTACTS = 128; + private static final int FREQUENCY_FOR_CONTACTS = 40; private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90; private static final int INDEX_NAME = 1; @@ -114,7 +114,7 @@ public class ContactsDictionary extends ExpandableDictionary { while (!cursor.isAfterLast()) { String name = cursor.getString(INDEX_NAME); - if (name != null) { + if (name != null && -1 == name.indexOf('@')) { int len = name.length(); String prevWord = null; @@ -143,8 +143,6 @@ public class ContactsDictionary extends ExpandableDictionary { if (wordLen < maxWordLength && wordLen > 1) { super.addWord(word, FREQUENCY_FOR_CONTACTS); if (!TextUtils.isEmpty(prevWord)) { - // TODO Do not add email address - // Not so critical super.setBigram(prevWord, word, FREQUENCY_FOR_CONTACTS_BIGRAM); } diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index 3fcb6ed55..5e7de3e6b 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -54,8 +54,8 @@ public class DictionaryCollection extends Dictionary { @Override public boolean isValidWord(CharSequence word) { - for (final Dictionary dict : mDictionaries) - if (dict.isValidWord(word)) return true; + for (int i = mDictionaries.size() - 1; i >= 0; --i) + if (mDictionaries.get(i).isValidWord(word)) return true; return false; } diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java index 39e7e402f..e56aa695d 100644 --- a/java/src/com/android/inputmethod/latin/EditingUtils.java +++ b/java/src/com/android/inputmethod/latin/EditingUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 Google Inc. + * Copyright (C) 2009 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 diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 26391fe46..97a4a1816 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -229,6 +229,7 @@ public class ExpandableDictionary extends Dictionary { * Returns the word's frequency or -1 if not found */ protected int getWordFrequency(CharSequence word) { + // Case-sensitive search Node node = searchNode(mRoots, word, 0, word.length()); return (node == null) ? -1 : node.mFrequency; } @@ -366,12 +367,16 @@ public class ExpandableDictionary extends Dictionary { /** * Adds bigrams to the in-memory trie structure that is being used to retrieve any word - * @param frequency frequency for this bigrams - * @param addFrequency if true, it adds to current frequency + * @param frequency frequency for this bigram + * @param addFrequency if true, it adds to current frequency, else it overwrites the old value * @return returns the final frequency */ private int addOrSetBigram(String word1, String word2, int frequency, boolean addFrequency) { - Node firstWord = searchWord(mRoots, word1, 0, null); + // We don't want results to be different according to case of the looked up left hand side + // word. We do want however to return the correct case for the right hand side. + // So we want to squash the case of the left hand side, and preserve that of the right + // hand side word. + Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null); Node secondWord = searchWord(mRoots, word2, 0, null); LinkedList<NextWord> bigram = firstWord.mNGrams; if (bigram == null || bigram.size() == 0) { @@ -437,8 +442,12 @@ public class ExpandableDictionary extends Dictionary { } } - private void runReverseLookUp(final CharSequence previousWord, final WordCallback callback) { - Node prevWord = searchNode(mRoots, previousWord, 0, previousWord.length()); + private void runBigramReverseLookUp(final CharSequence previousWord, + final WordCallback callback) { + // Search for the lowercase version of the word only, because that's where bigrams + // store their sons. + Node prevWord = searchNode(mRoots, previousWord.toString().toLowerCase(), 0, + previousWord.length()); if (prevWord != null && prevWord.mNGrams != null) { reverseLookUp(prevWord.mNGrams, callback); } @@ -448,7 +457,7 @@ public class ExpandableDictionary extends Dictionary { public void getBigrams(final WordComposer codes, final CharSequence previousWord, final WordCallback callback) { if (!reloadDictionaryIfRequired()) { - runReverseLookUp(previousWord, callback); + runBigramReverseLookUp(previousWord, callback); } } @@ -494,14 +503,20 @@ public class ExpandableDictionary extends Dictionary { } /** - * Search for the terminal node of the word + * Recursively search for the terminal node of the word. + * + * One iteration takes the full word to search for and the current index of the recursion. + * + * @param children the node of the trie to search under. + * @param word the word to search for. Only read [offset..length] so there may be trailing chars + * @param offset the index in {@code word} this recursion should operate on. + * @param length the length of the input word. * @return Returns the terminal node of the word if the word exists */ private Node searchNode(final NodeArray children, final CharSequence word, final int offset, final int length) { - // TODO Consider combining with addWordRec final int count = children.mLength; - char currentChar = word.charAt(offset); + final char currentChar = word.charAt(offset); for (int j = 0; j < count; j++) { final Node node = children.mData[j]; if (node.mCode == currentChar) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 2c39eba55..9c6465dd2 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -16,22 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.compat.CompatUtils; -import com.android.inputmethod.compat.EditorInfoCompatUtils; -import com.android.inputmethod.compat.InputConnectionCompatUtils; -import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; -import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; -import com.android.inputmethod.compat.InputTypeCompatUtils; -import com.android.inputmethod.deprecated.LanguageSwitcherProxy; -import com.android.inputmethod.deprecated.VoiceProxy; -import com.android.inputmethod.deprecated.recorrection.Recorrection; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.LatinKeyboard; -import com.android.inputmethod.keyboard.LatinKeyboardView; - import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; @@ -59,7 +43,6 @@ import android.util.PrintWriterPrinter; import android.util.Printer; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -69,7 +52,24 @@ import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.InputConnection; -import android.widget.LinearLayout; + +import com.android.inputmethod.accessibility.AccessibilityUtils; +import com.android.inputmethod.compat.CompatUtils; +import com.android.inputmethod.compat.EditorInfoCompatUtils; +import com.android.inputmethod.compat.InputConnectionCompatUtils; +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; +import com.android.inputmethod.compat.InputTypeCompatUtils; +import com.android.inputmethod.compat.SuggestionSpanUtils; +import com.android.inputmethod.deprecated.LanguageSwitcherProxy; +import com.android.inputmethod.deprecated.VoiceProxy; +import com.android.inputmethod.deprecated.recorrection.Recorrection; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.LatinKeyboard; +import com.android.inputmethod.keyboard.LatinKeyboardView; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -78,11 +78,12 @@ import java.util.Locale; /** * Input method implementation for Qwerty'ish keyboard. */ -public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener { +public class LatinIME extends InputMethodServiceCompatWrapper implements KeyboardActionListener, + CandidateView.Listener { private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean PERF_DEBUG = false; private static final boolean TRACE = false; - private static boolean DEBUG = LatinImeLogger.sDBG; + private static boolean DEBUG; /** * The private IME option used to indicate that no microphone should be @@ -155,7 +156,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private UserDictionary mUserDictionary; private UserBigramDictionary mUserBigramDictionary; - private ContactsDictionary mContactsDictionary; private AutoDictionary mAutoDictionary; // TODO: Create an inner class to group options and pseudo-options to improve readability. @@ -173,6 +173,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // Magic space: a space that should disappear on space/apostrophe insertion, move after the // punctuation on punctuation insertion, and become a real space on alpha char insertion. private boolean mJustAddedMagicSpace; // This indicates whether the last char is a magic space. + // This indicates whether the last keypress resulted in processing of double space replacement + // with period-space. + private boolean mJustReplacedDoubleSpace; private int mCorrectionMode; private int mCommittedLength; @@ -181,8 +184,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private int mLastSelectionStart; private int mLastSelectionEnd; - // Indicates whether the suggestion strip is to be on in landscape - private boolean mJustAccepted; + // Whether we are expecting an onUpdateSelection event to fire. If it does when we don't + // "expect" it, it means the user actually moved the cursor. + private boolean mExpectingUpdateSelection; private int mDeleteCount; private long mLastKeyTime; @@ -217,15 +221,15 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void handleMessage(Message msg) { final KeyboardSwitcher switcher = mKeyboardSwitcher; - final LatinKeyboardView inputView = switcher.getInputView(); + final LatinKeyboardView inputView = switcher.getKeyboardView(); switch (msg.what) { case MSG_UPDATE_SUGGESTIONS: updateSuggestions(); break; case MSG_UPDATE_OLD_SUGGESTIONS: - mRecorrection.setRecorrectionSuggestions(mVoiceProxy, mCandidateView, mSuggest, - mKeyboardSwitcher, mWord, mHasUncommittedTypedChars, mLastSelectionStart, - mLastSelectionEnd, mSettingsValues.mWordSeparators); + mRecorrection.fetchAndDisplayRecorrectionSuggestions(mVoiceProxy, mCandidateView, + mSuggest, mKeyboardSwitcher, mWord, mHasUncommittedTypedChars, + mLastSelectionStart, mLastSelectionEnd, mSettingsValues.mWordSeparators); break; case MSG_UPDATE_SHIFT_STATE: switcher.updateShiftState(); @@ -307,7 +311,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar public void startDisplayLanguageOnSpacebar(boolean localeChanged) { removeMessages(MSG_FADEOUT_LANGUAGE_ON_SPACEBAR); removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR); - final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); + final LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView != null) { final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard(); // The language is always displayed when the delay is negative. @@ -350,6 +354,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar SubtypeSwitcher.init(this, prefs); KeyboardSwitcher.init(this, prefs); Recorrection.init(this, prefs); + AccessibilityUtils.init(this, prefs); super.onCreate(); @@ -358,6 +363,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mKeyboardSwitcher = KeyboardSwitcher.getInstance(); mRecorrection = Recorrection.getInstance(); + DEBUG = LatinImeLogger.sDBG; loadSettings(); @@ -402,11 +408,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this); if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr()); + resetContactsDictionary(); } private void initSuggest() { final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); - final Locale keyboardLocale = new Locale(localeStr); + final Locale keyboardLocale = Utils.constructLocaleFromString(localeStr); final Resources res = mResources; final Locale savedLocale = Utils.setSystemLocale(res, keyboardLocale); @@ -424,8 +431,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mUserDictionary = new UserDictionary(this, localeStr); mSuggest.setUserDictionary(mUserDictionary); - mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS); - mSuggest.setContactsDictionary(mContactsDictionary); + resetContactsDictionary(); mAutoDictionary = new AutoDictionary(this, this, localeStr, Suggest.DIC_AUTO); mSuggest.setAutoDictionary(mAutoDictionary); @@ -438,9 +444,16 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar Utils.setSystemLocale(res, savedLocale); } + private void resetContactsDictionary() { + if (null == mSuggest) return; + ContactsDictionary contactsDictionary = mSettingsValues.mUseContactsDict + ? new ContactsDictionary(this, Suggest.DIC_CONTACTS) : null; + mSuggest.setContactsDictionary(contactsDictionary); + } + /* package private */ void resetSuggestMainDict() { final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); - final Locale keyboardLocale = new Locale(localeStr); + final Locale keyboardLocale = Utils.constructLocaleFromString(localeStr); int mainDicResId = Utils.getMainDictionaryResourceId(mResources); mSuggest.resetMainDict(this, mainDicResId, keyboardLocale); } @@ -487,24 +500,29 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } @Override - public View onCreateCandidatesView() { - LayoutInflater inflater = getLayoutInflater(); - LinearLayout container = (LinearLayout)inflater.inflate(R.layout.candidates, null); - mCandidateViewContainer = container; + public void setInputView(View view) { + super.setInputView(view); + mCandidateViewContainer = view.findViewById(R.id.candidates_container); + mCandidateView = (CandidateView) view.findViewById(R.id.candidates); + mCandidateView.setListener(this, view); mCandidateStripHeight = (int)mResources.getDimension(R.dimen.candidate_strip_height); - mCandidateView = (CandidateView) container.findViewById(R.id.candidates); - mCandidateView.setService(this); - setCandidatesViewShown(true); - return container; + } + + @Override + public void setCandidatesView(View view) { + // To ensure that CandidatesView will never be set. + return; } @Override public void onStartInputView(EditorInfo attribute, boolean restarting) { final KeyboardSwitcher switcher = mKeyboardSwitcher; - LatinKeyboardView inputView = switcher.getInputView(); + LatinKeyboardView inputView = switcher.getKeyboardView(); if (DEBUG) { - Log.d(TAG, "onStartInputView: " + inputView); + Log.d(TAG, "onStartInputView: attribute:" + ((attribute == null) ? "none" + : String.format("inputType=0x%08x imeOptions=0x%08x", + attribute.inputType, attribute.imeOptions))); } // In landscape mode, this method gets called without the input view being created. if (inputView == null) { @@ -530,6 +548,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mHasUncommittedTypedChars = false; mDeleteCount = 0; mJustAddedMagicSpace = false; + mJustReplacedDoubleSpace = false; loadSettings(); updateCorrectionMode(); @@ -550,7 +569,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar switcher.updateShiftState(); } - setCandidatesViewShownInternal(isCandidateStripVisible(), false /* needsInputViewShown */ ); + setSuggestionStripShownInternal(isCandidateStripVisible(), /* needsInputViewShown */ false); // Delay updating suggestions because keyboard input view may not be shown at this point. mHandler.postUpdateSuggestions(); @@ -624,6 +643,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } @Override + public void onWindowHidden() { + super.onWindowHidden(); + KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView != null) inputView.closing(); + } + + @Override public void onFinishInput() { super.onFinishInput(); @@ -632,7 +658,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging); - KeyboardView inputView = mKeyboardSwitcher.getInputView(); + KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView != null) inputView.closing(); if (mAutoDictionary != null) mAutoDictionary.flushPendingWrites(); if (mUserBigramDictionary != null) mUserBigramDictionary.flushPendingWrites(); @@ -641,7 +667,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void onFinishInputView(boolean finishingInput) { super.onFinishInputView(finishingInput); - KeyboardView inputView = mKeyboardSwitcher.getInputView(); + KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView != null) inputView.setForeground(false); // Remove pending messages related to update suggestions mHandler.cancelUpdateSuggestions(); @@ -702,14 +728,17 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar ic.finishComposingText(); } mVoiceProxy.setVoiceInputHighlighted(false); - } else if (!mHasUncommittedTypedChars && !mJustAccepted) { + } else if (!mHasUncommittedTypedChars && !mExpectingUpdateSelection) { if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) { if (TextEntryState.isAcceptedDefault()) TextEntryState.reset(); - mJustAddedMagicSpace = false; // The user moved the cursor. } } - mJustAccepted = false; + if (!mExpectingUpdateSelection) { + mJustAddedMagicSpace = false; // The user moved the cursor. + mJustReplacedDoubleSpace = false; + } + mExpectingUpdateSelection = false; mHandler.postUpdateShiftKeyState(); // Make a note of the cursor position @@ -797,11 +826,11 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // When in fullscreen mode, show completions generated by the application setSuggestions(builder.build()); mBestWord = null; - setCandidatesViewShown(true); + setSuggestionStripShown(true); } } - private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) { + private void setSuggestionStripShownInternal(boolean shown, boolean needsInputViewShown) { // TODO: Modify this if we support candidates with hard keyboard if (onEvaluateInputViewShown()) { final boolean shouldShowCandidates = shown @@ -809,27 +838,26 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (isExtractViewShown()) { // No need to have extra space to show the key preview. mCandidateViewContainer.setMinimumHeight(0); - super.setCandidatesViewShown(shown); + mCandidateViewContainer.setVisibility( + shouldShowCandidates ? View.VISIBLE : View.GONE); } else { // We must control the visibility of the suggestion strip in order to avoid clipped // key previews, even when we don't show the suggestion strip. mCandidateViewContainer.setVisibility( shouldShowCandidates ? View.VISIBLE : View.INVISIBLE); - super.setCandidatesViewShown(true); } } } - @Override - public void setCandidatesViewShown(boolean shown) { - setCandidatesViewShownInternal(shown, true /* needsInputViewShown */ ); + private void setSuggestionStripShown(boolean shown) { + setSuggestionStripShownInternal(shown, /* needsInputViewShown */true); } @Override public void onComputeInsets(InputMethodService.Insets outInsets) { super.onComputeInsets(outInsets); - final KeyboardView inputView = mKeyboardSwitcher.getInputView(); - if (inputView == null) + final KeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView == null || mCandidateViewContainer == null) return; final int containerHeight = mCandidateViewContainer.getHeight(); int touchY = containerHeight; @@ -870,8 +898,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: - if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) { - if (mKeyboardSwitcher.getInputView().handleBack()) { + if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getKeyboardView() != null) { + if (mKeyboardSwitcher.getKeyboardView().handleBack()) { return true; } } @@ -960,6 +988,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar ic.commitText(". ", 1); ic.endBatchEdit(); mKeyboardSwitcher.updateShiftState(); + mJustReplacedDoubleSpace = true; } else { mHandler.startDoubleSpacesTimer(); } @@ -990,6 +1019,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } } + @Override public boolean addWordToDictionary(String word) { mUserDictionary.addWord(word, 128); // Suggestion strip should be updated after the operation of adding word to the @@ -1007,14 +1037,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } private void onSettingsKeyPressed() { - if (!isShowingOptionDialog()) { - if (!mSettingsValues.mEnableShowSubtypeSettings) { - showSubtypeSelectorAndSettings(); - } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) { - showOptionsMenu(); - } else { - launchSettings(); - } + if (isShowingOptionDialog()) + return; + if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) { + showSubtypeSelectorAndSettings(); + } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) { + showOptionsMenu(); + } else { + launchSettings(); } } @@ -1042,10 +1072,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mLastKeyTime = when; KeyboardSwitcher switcher = mKeyboardSwitcher; final boolean distinctMultiTouch = switcher.hasDistinctMultitouch(); + final boolean lastStateOfJustReplacedDoubleSpace = mJustReplacedDoubleSpace; + mJustReplacedDoubleSpace = false; switch (primaryCode) { case Keyboard.CODE_DELETE: - handleBackspace(); + handleBackspace(lastStateOfJustReplacedDoubleSpace); mDeleteCount++; + mExpectingUpdateSelection = true; LatinImeLogger.logOnDelete(); break; case Keyboard.CODE_SHIFT: @@ -1083,6 +1116,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar break; case Keyboard.CODE_TAB: handleTab(); + // There are two cases for tab. Either we send a "next" event, that may change the + // focus but will never move the cursor. Or, we send a real tab keycode, which some + // applications may accept or ignore, and we don't know whether this will move the + // cursor or not. So actually, we don't really know. + // So to go with the safer option, we'd rather behave as if the user moved the + // cursor when they didn't than the opposite. We also expect that most applications + // will actually use tab only for focus movement. + // To sum it up: do not update mExpectingUpdateSelection here. break; default: if (mSettingsValues.isWordSeparator(primaryCode)) { @@ -1090,6 +1131,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } else { handleCharacter(primaryCode, keyCodes, x, y); } + mExpectingUpdateSelection = true; + break; } switcher.onKey(primaryCode); // Reset after any single keystroke @@ -1119,7 +1162,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mKeyboardSwitcher.onCancelInput(); } - private void handleBackspace() { + private void handleBackspace(boolean justReplacedDoubleSpace) { if (mVoiceProxy.logAndRevertVoiceInput()) return; final InputConnection ic = getCurrentInputConnection(); @@ -1160,6 +1203,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar ic.endBatchEdit(); return; } + if (justReplacedDoubleSpace) { + if (revertDoubleSpace()) { + ic.endBatchEdit(); + return; + } + } if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) { ic.deleteSurroundingText(mEnteredText.length(), 0); @@ -1200,7 +1249,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (EditorInfoCompatUtils.hasFlagNavigateNext(imeOptions) && !isManualTemporaryUpperCase) { EditorInfoCompatUtils.performEditorActionNext(ic); - ic.performEditorAction(EditorInfo.IME_ACTION_NEXT); } else if (EditorInfoCompatUtils.hasFlagNavigatePrevious(imeOptions) && isManualTemporaryUpperCase) { EditorInfoCompatUtils.performEditorActionPrevious(ic); @@ -1228,7 +1276,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar clearSuggestions(); } } - KeyboardSwitcher switcher = mKeyboardSwitcher; + final KeyboardSwitcher switcher = mKeyboardSwitcher; if (switcher.isShiftedOrShiftLocked()) { if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT || keyCodes[0] > Character.MAX_CODE_POINT) { @@ -1236,13 +1284,15 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } code = keyCodes[0]; if (switcher.isAlphabetMode() && Character.isLowerCase(code)) { - int upperCaseCode = Character.toUpperCase(code); - if (upperCaseCode != code) { - code = upperCaseCode; + // In some locales, such as Turkish, Character.toUpperCase() may return a wrong + // character because it doesn't take care of locale. + final String upperCaseString = new String(new int[] {code}, 0, 1) + .toUpperCase(mSubtypeSwitcher.getInputLocale()); + if (upperCaseString.codePointCount(0, upperCaseString.length()) == 1) { + code = upperCaseString.codePointAt(0); } else { // Some keys, such as [eszett], have upper case as multi-characters. - String upperCase = new String(new int[] {code}, 0, 1).toUpperCase(); - onTextInput(upperCase); + onTextInput(upperCaseString); return; } } @@ -1358,7 +1408,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar commitTyped(getCurrentInputConnection()); mVoiceProxy.handleClose(); requestHideSelf(0); - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); + LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView != null) inputView.closing(); } @@ -1394,7 +1444,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (DEBUG) { Log.d(TAG, "Switch to keyboard view."); } - View v = mKeyboardSwitcher.getInputView(); + View v = mKeyboardSwitcher.getKeyboardView(); if (v != null) { // Confirms that the keyboard view doesn't have parent view. ViewParent p = v.getParent(); @@ -1403,7 +1453,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } setInputView(v); } - setCandidatesViewShown(isCandidateStripVisible()); + setSuggestionStripShown(isCandidateStripVisible()); updateInputViewShown(); mHandler.postUpdateSuggestions(); } @@ -1413,16 +1463,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } public void setSuggestions(SuggestedWords words) { - if (mVoiceProxy.getAndResetIsShowingHint()) { - setCandidatesView(mCandidateViewContainer); - } - if (mCandidateView != null) { mCandidateView.setSuggestions(words); - if (mCandidateView.isConfigCandidateHighlightFontColorEnabled()) { - mKeyboardSwitcher.onAutoCorrectionStateChanged( - words.hasWordAboveAutoCorrectionScoreThreshold()); - } + mKeyboardSwitcher.onAutoCorrectionStateChanged( + words.hasWordAboveAutoCorrectionScoreThreshold()); } } @@ -1445,7 +1489,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(), mSettingsValues.mWordSeparators); SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( - mKeyboardSwitcher.getInputView(), word, prevWord); + mKeyboardSwitcher.getKeyboardView(), word, prevWord); boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection(); final CharSequence typedWord = word.getTypedWord(); @@ -1494,7 +1538,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } else { mBestWord = null; } - setCandidatesViewShown(isCandidateStripVisible()); + setSuggestionStripShown(isCandidateStripVisible()); } private boolean pickDefaultSuggestion(int separatorCode) { @@ -1505,8 +1549,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } if (mBestWord != null && mBestWord.length() > 0) { TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord, separatorCode); - mJustAccepted = true; - pickSuggestion(mBestWord); + mExpectingUpdateSelection = true; + commitBestWord(mBestWord); // Add the word to the auto dictionary if it's not a known word addToAutoAndUserBigramDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED); return true; @@ -1514,6 +1558,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar return false; } + @Override public void pickSuggestionManually(int index, CharSequence suggestion) { SuggestedWords suggestions = mCandidateView.getSuggestions(); mVoiceProxy.flushAndLogAllTextModificationCounters(index, suggestion, @@ -1553,7 +1598,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // a magic space even if it was a normal space. This is meant to help in case the user // pressed space on purpose of displaying the suggestion strip punctuation. final char primaryCode = suggestion.charAt(0); - final CharSequence beforeText = ic.getTextBeforeCursor(1, 0); + final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : ""; final int toLeft = (ic == null || TextUtils.isEmpty(beforeText)) ? 0 : beforeText.charAt(0); final boolean oldMagicSpace = mJustAddedMagicSpace; @@ -1572,8 +1617,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // context - no user input. We should reset the word composer. mWord.reset(); } - mJustAccepted = true; - pickSuggestion(suggestion); + mExpectingUpdateSelection = true; + commitBestWord(suggestion); // Add the word to the auto dictionary if it's not a known word if (index == 0) { addToAutoAndUserBigramDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED); @@ -1613,8 +1658,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (!showingAddToDictionaryHint) { // If we're not showing the "Touch again to save", then show corrections again. // In case the cursor position doesn't change, make sure we show the suggestions again. - clearSuggestions(); - mHandler.postUpdateOldSuggestions(); + updateBigramPredictions(); + // Updating the predictions right away may be slow and feel unresponsive on slower + // terminals. On the other hand if we just postUpdateBigramPredictions() it will + // take a noticeable delay to update them which may feel uneasy. } if (showingAddToDictionaryHint) { mCandidateView.showAddToDictionaryHint(suggestion); @@ -1627,25 +1674,25 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar /** * Commits the chosen word to the text field and saves it for later * retrieval. - * @param suggestion the suggestion picked by the user to be committed to - * the text field */ - private void pickSuggestion(CharSequence suggestion) { + private void commitBestWord(CharSequence bestWord) { KeyboardSwitcher switcher = mKeyboardSwitcher; if (!switcher.isKeyboardAvailable()) return; InputConnection ic = getCurrentInputConnection(); if (ic != null) { - mVoiceProxy.rememberReplacedWord(suggestion, mSettingsValues.mWordSeparators); - ic.commitText(suggestion, 1); + mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators); + SuggestedWords suggestedWords = mCandidateView.getSuggestions(); + ic.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( + this, bestWord, suggestedWords), 1); } - mRecorrection.saveRecorrectionSuggestion(mWord, suggestion); + mRecorrection.saveRecorrectionSuggestion(mWord, bestWord); mHasUncommittedTypedChars = false; - mCommittedLength = suggestion.length(); + mCommittedLength = bestWord.length(); } private static final WordComposer sEmptyWordComposer = new WordComposer(); - private void updateBigramPredictions() { + public void updateBigramPredictions() { if (mSuggest == null || !isSuggestionsRequested()) return; @@ -1657,7 +1704,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(), mSettingsValues.mWordSeparators); SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( - mKeyboardSwitcher.getInputView(), sEmptyWordComposer, prevWord); + mKeyboardSwitcher.getKeyboardView(), sEmptyWordComposer, prevWord); if (builder.size() > 0) { // Explicitly supply an empty typed word (the no-second-arg version of @@ -1670,7 +1717,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar public void setPunctuationSuggestions() { setSuggestions(mSettingsValues.mSuggestPuncList); - setCandidatesViewShown(isCandidateStripVisible()); + setSuggestionStripShown(isCandidateStripVisible()); } private void addToAutoAndUserBigramDictionaries(CharSequence suggestion, int frequencyDelta) { @@ -1780,6 +1827,21 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } } + public boolean revertDoubleSpace() { + mHandler.cancelDoubleSpacesTimer(); + final InputConnection ic = getCurrentInputConnection(); + // Here we test whether we indeed have a period and a space before us. This should not + // be needed, but it's there just in case something went wrong. + final CharSequence textBeforeCursor = ic.getTextBeforeCursor(2, 0); + if (!". ".equals(textBeforeCursor)) + return false; + ic.beginBatchEdit(); + ic.deleteSurroundingText(2, 0); + ic.commitText(" ", 1); + ic.endBatchEdit(); + return true; + } + public boolean isWordSeparator(int code) { return mSettingsValues.isWordSeparator(code); } @@ -1813,7 +1875,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } // The following is necessary because on API levels < 10, we don't get notified when // subtype changes. - onRefreshKeyboard(); + if (!CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) + onRefreshKeyboard(); } @Override @@ -1879,7 +1942,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // if mAudioManager is null, we don't have the ringer state yet // mAudioManager will be set by updateRingerMode if (mAudioManager == null) { - if (mKeyboardSwitcher.getInputView() != null) { + if (mKeyboardSwitcher.getKeyboardView() != null) { updateRingerMode(); } } @@ -1906,7 +1969,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (!mSettingsValues.mVibrateOn) { return; } - LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); + LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); if (inputView != null) { inputView.performHapticFeedback( HapticFeedbackConstants.KEYBOARD_TAP, @@ -1914,11 +1977,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } } - public void promoteToUserDictionary(String word, int frequency) { - if (mUserDictionary.isValidWord(word)) return; - mUserDictionary.addWord(word, frequency); - } - public WordComposer getCurrentWord() { return mWord; } @@ -2030,7 +2088,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private void showOptionsMenuInternal(CharSequence title, CharSequence[] items, DialogInterface.OnClickListener listener) { - final IBinder windowToken = mKeyboardSwitcher.getInputView().getWindowToken(); + final IBinder windowToken = mKeyboardSwitcher.getKeyboardView().getWindowToken(); if (windowToken == null) return; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setCancelable(true); diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 7c323c155..3ad2a5965 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -79,6 +79,8 @@ public class Settings extends PreferenceActivity public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY = "pref_key_preview_popup_dismiss_delay"; + public static final String PREF_KEY_USE_CONTACTS_DICT = + "pref_key_use_contacts_dict"; public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; @@ -87,7 +89,6 @@ public class Settings extends PreferenceActivity public static class Values { // From resources: - public final boolean mEnableShowSubtypeSettings; public final boolean mSwipeDownDismissKeyboardEnabled; public final int mDelayBeforeFadeoutLanguageOnSpacebar; public final int mDelayUpdateSuggestions; @@ -115,21 +116,20 @@ public class Settings extends PreferenceActivity public final boolean mBigramSuggestionEnabled; // Prediction: use bigrams to predict the next word when there is no input for it yet public final boolean mBigramPredictionEnabled; + public final boolean mUseContactsDict; public Values(final SharedPreferences prefs, final Context context, final String localeStr) { final Resources res = context.getResources(); final Locale savedLocale; if (null != localeStr) { - final Locale keyboardLocale = new Locale(localeStr); + final Locale keyboardLocale = Utils.constructLocaleFromString(localeStr); savedLocale = Utils.setSystemLocale(res, keyboardLocale); } else { savedLocale = null; } // Get the resources - mEnableShowSubtypeSettings = res.getBoolean( - R.bool.config_enable_show_subtype_settings); mSwipeDownDismissKeyboardEnabled = res.getBoolean( R.bool.config_swipe_down_dismiss_keyboard_enabled); mDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger( @@ -178,6 +178,8 @@ public class Settings extends PreferenceActivity mAutoCorrectionThreshold = getAutoCorrectionThreshold(prefs, res); + mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); + Utils.setSystemLocale(res, savedLocale); } @@ -354,8 +356,6 @@ public class Settings extends PreferenceActivity (PreferenceGroup) findPreference(PREF_GENERAL_SETTINGS_KEY); final PreferenceGroup textCorrectionGroup = (PreferenceGroup) findPreference(PREF_CORRECTION_SETTINGS_KEY); - final PreferenceGroup bigramGroup = - (PreferenceGroup) findPreference(PREF_NGRAM_SETTINGS_KEY); final boolean showSettingsKeyOption = res.getBoolean( R.bool.config_enable_show_settings_key_option); @@ -373,10 +373,7 @@ public class Settings extends PreferenceActivity generalSettings.removePreference(findPreference(PREF_VIBRATE_ON)); } - final boolean showSubtypeSettings = res.getBoolean( - R.bool.config_enable_show_subtype_settings); - if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED - && !showSubtypeSettings) { + if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) { generalSettings.removePreference(findPreference(PREF_SUBTYPES)); } @@ -499,7 +496,6 @@ public class Settings extends PreferenceActivity } private void updateSettingsKeySummary() { - final ListPreference lp = mSettingsKeyPreference; mSettingsKeyPreference.setSummary( getResources().getStringArray(R.array.settings_key_modes) [mSettingsKeyPreference.findIndexOfValue(mSettingsKeyPreference.getValue())]); diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index d8012087b..6ca12c0c5 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -92,10 +92,9 @@ public class SubtypeSwitcher { } public static void init(LatinIME service, SharedPreferences prefs) { + SubtypeLocale.init(service); sInstance.initialize(service, prefs); sInstance.updateAllParameters(); - - SubtypeLocale.init(service); } private SubtypeSwitcher() { @@ -281,14 +280,8 @@ public class SubtypeSwitcher { // "en_US" --> language: en & country: US // "en" --> language: en // "" --> the system locale - mLocaleSplitter.setString(inputLocaleStr); - if (mLocaleSplitter.hasNext()) { - String language = mLocaleSplitter.next(); - if (mLocaleSplitter.hasNext()) { - mInputLocale = new Locale(language, mLocaleSplitter.next()); - } else { - mInputLocale = new Locale(language); - } + if (!TextUtils.isEmpty(inputLocaleStr)) { + mInputLocale = Utils.constructLocaleFromString(inputLocaleStr); mInputLocaleStr = inputLocaleStr; } else { mInputLocale = mSystemLocale; @@ -420,7 +413,7 @@ public class SubtypeSwitcher { final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); final LatinKeyboard keyboard = switcher.getLatinKeyboard(); if (keyboard != null) { - keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getInputView()); + keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getKeyboardView()); } } @@ -510,7 +503,7 @@ public class SubtypeSwitcher { private void triggerVoiceIME() { if (!mService.isInputViewShown()) return; VoiceProxy.getInstance().startListening(false, - KeyboardSwitcher.getInstance().getInputView().getWindowToken()); + KeyboardSwitcher.getInstance().getKeyboardView().getWindowToken()); } ////////////////////////////////////// @@ -549,12 +542,12 @@ public class SubtypeSwitcher { || mEnabledKeyboardSubtypesOfCurrentInputMethod.size() == 0) return; mCurrentKeyboardSubtypeIndex = getCurrentIndex(); mNextKeyboardSubtype = getNextKeyboardSubtypeInternal(mCurrentKeyboardSubtypeIndex); - Locale locale = new Locale(mNextKeyboardSubtype.getLocale()); - mNextLanguage = getDisplayLanguage(locale); + Locale locale = Utils.constructLocaleFromString(mNextKeyboardSubtype.getLocale()); + mNextLanguage = getFullDisplayName(locale, true); mPreviousKeyboardSubtype = getPreviousKeyboardSubtypeInternal( mCurrentKeyboardSubtypeIndex); - locale = new Locale(mPreviousKeyboardSubtype.getLocale()); - mPreviousLanguage = getDisplayLanguage(locale); + locale = Utils.constructLocaleFromString(mPreviousKeyboardSubtype.getLocale()); + mPreviousLanguage = getFullDisplayName(locale, true); } private int normalize(int index) { @@ -584,29 +577,30 @@ public class SubtypeSwitcher { public static String getFullDisplayName(Locale locale, boolean returnsNameInThisLocale) { if (returnsNameInThisLocale) { - return toTitleCase(SubtypeLocale.getFullDisplayName(locale)); + return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale); } else { - return toTitleCase(locale.getDisplayName()); + return toTitleCase(locale.getDisplayName(), locale); } } public static String getDisplayLanguage(Locale locale) { - return toTitleCase(locale.getDisplayLanguage(locale)); + return toTitleCase(SubtypeLocale.getFullDisplayName(locale), locale); } public static String getMiddleDisplayLanguage(Locale locale) { - return toTitleCase((new Locale(locale.getLanguage()).getDisplayLanguage(locale))); + return toTitleCase((Utils.constructLocaleFromString( + locale.getLanguage()).getDisplayLanguage(locale)), locale); } public static String getShortDisplayLanguage(Locale locale) { - return toTitleCase(locale.getLanguage()); + return toTitleCase(locale.getLanguage(), locale); } - private static String toTitleCase(String s) { + private static String toTitleCase(String s, Locale locale) { if (s.length() == 0) { return s; } - return Character.toUpperCase(s.charAt(0)) + s.substring(1); + return s.toUpperCase(locale).charAt(0) + s.substring(1); } public String getInputLanguageName() { diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index ca75866c0..eb5ed5a65 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -84,7 +84,7 @@ public class Suggest implements Dictionary.WordCallback { private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>(); private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>(); - private int mPrefMaxSuggestions = 12; + private int mPrefMaxSuggestions = 18; private static final int PREF_MAX_BIGRAMS = 60; @@ -117,30 +117,31 @@ public class Suggest implements Dictionary.WordCallback { } private void init(Context context, Dictionary mainDict) { - if (mainDict != null) { - mMainDict = mainDict; - mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict); - mBigramDictionaries.put(DICT_KEY_MAIN, mainDict); - } + mMainDict = mainDict; + addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict); + addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict); mWhiteListDictionary = WhitelistDictionary.init(context); - if (mWhiteListDictionary != null) { - mUnigramDictionaries.put(DICT_KEY_WHITELIST, mWhiteListDictionary); - } + addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary); mAutoCorrection = new AutoCorrection(); initPool(); } + private void addOrReplaceDictionary(Map<String, Dictionary> dictionaries, String key, + Dictionary dict) { + final Dictionary oldDict = (dict == null) + ? dictionaries.remove(key) + : dictionaries.put(key, dict); + if (oldDict != null && dict != oldDict) { + oldDict.close(); + } + } + public void resetMainDict(Context context, int dictionaryResId, Locale locale) { final Dictionary newMainDict = DictionaryFactory.createDictionaryFromManager( context, locale, dictionaryResId); mMainDict = newMainDict; - if (null == newMainDict) { - mUnigramDictionaries.remove(DICT_KEY_MAIN); - mBigramDictionaries.remove(DICT_KEY_MAIN); - } else { - mUnigramDictionaries.put(DICT_KEY_MAIN, newMainDict); - mBigramDictionaries.put(DICT_KEY_MAIN, newMainDict); - } + addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict); + addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict); } private void initPool() { @@ -179,28 +180,25 @@ public class Suggest implements Dictionary.WordCallback { * before the main dictionary, if set. */ public void setUserDictionary(Dictionary userDictionary) { - if (userDictionary != null) - mUnigramDictionaries.put(DICT_KEY_USER, userDictionary); + addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER, userDictionary); } /** - * Sets an optional contacts dictionary resource to be loaded. + * Sets an optional contacts dictionary resource to be loaded. It is also possible to remove + * the contacts dictionary by passing null to this method. In this case no contacts dictionary + * won't be used. */ public void setContactsDictionary(Dictionary contactsDictionary) { - if (contactsDictionary != null) { - mUnigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary); - mBigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary); - } + addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary); + addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary); } public void setAutoDictionary(Dictionary autoDictionary) { - if (autoDictionary != null) - mUnigramDictionaries.put(DICT_KEY_AUTO, autoDictionary); + addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_AUTO, autoDictionary); } public void setUserBigramDictionary(Dictionary userBigramDictionary) { - if (userBigramDictionary != null) - mBigramDictionaries.put(DICT_KEY_USER_BIGRAM, userBigramDictionary); + addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_BIGRAM, userBigramDictionary); } public void setAutoCorrectionThreshold(double threshold) { @@ -252,6 +250,7 @@ public class Suggest implements Dictionary.WordCallback { poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) : new StringBuilder(getApproxMaxWordLength()); sb.setLength(0); + // TODO: Must pay attention to locale when changing case. if (all) { sb.append(word.toString().toUpperCase()); } else if (first) { @@ -317,6 +316,7 @@ public class Suggest implements Dictionary.WordCallback { } else { // Word entered: return only bigrams that match the first char of the typed word final char currentChar = typedWord.charAt(0); + // TODO: Must pay attention to locale when changing case. final char currentCharUpper = Character.toUpperCase(currentChar); int count = 0; final int bigramSuggestionSize = mBigramSuggestions.size(); @@ -520,6 +520,7 @@ public class Suggest implements Dictionary.WordCallback { StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) : new StringBuilder(getApproxMaxWordLength()); sb.setLength(0); + // TODO: Must pay attention to locale when changing case. if (mIsAllUpperCase) { sb.append(new String(word, offset, length).toUpperCase()); } else if (mIsFirstCharCapitalized) { diff --git a/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java new file mode 100644 index 000000000..4a3f42d5d --- /dev/null +++ b/java/src/com/android/inputmethod/latin/SuggestionSpanPickedNotificationReceiver.java @@ -0,0 +1,43 @@ +/* + * 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 com.android.inputmethod.compat.SuggestionSpanUtils; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +public class SuggestionSpanPickedNotificationReceiver extends BroadcastReceiver { + private static final boolean DBG = LatinImeLogger.sDBG; + private static final String TAG = + SuggestionSpanPickedNotificationReceiver.class.getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + if (SuggestionSpanUtils.ACTION_SUGGESTION_PICKED.equals(intent.getAction())) { + if (DBG) { + final String before = intent.getStringExtra( + SuggestionSpanUtils.SUGGESTION_SPAN_PICKED_BEFORE); + final String after = intent.getStringExtra( + SuggestionSpanUtils.SUGGESTION_SPAN_PICKED_AFTER); + Log.d(TAG, "Received notification picked: " + before + "," + after); + } + } + } +} diff --git a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java index a32a6461a..5b615ca29 100644 --- a/java/src/com/android/inputmethod/latin/UserBigramDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBigramDictionary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Google Inc. + * Copyright (C) 2010 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 @@ -158,7 +158,7 @@ public class UserBigramDictionary extends ExpandableDictionary { * Pair will be added to the userbigram database. */ public int addBigrams(String word1, String word2) { - // remove caps + // remove caps if second word is autocapitalized if (mIme != null && mIme.getCurrentWord().isAutoCapitalized()) { word2 = Character.toLowerCase(word2.charAt(0)) + word2.substring(1); } diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index 47890e643..245fc20bc 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.compat.InputMethodInfoCompatWrapper; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.compat.InputMethodSubtypeCompatWrapper; import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; @@ -43,7 +44,10 @@ import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; +import java.util.List; import java.util.Locale; public class Utils { @@ -108,7 +112,34 @@ public class Utils { } public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManagerCompatWrapper imm) { - return imm.getEnabledInputMethodList().size() > 1 + final List<InputMethodInfoCompatWrapper> enabledImis = imm.getEnabledInputMethodList(); + + // Filters out IMEs that have auxiliary subtypes only (including either implicitly or + // explicitly enabled ones). + final ArrayList<InputMethodInfoCompatWrapper> filteredImis = + new ArrayList<InputMethodInfoCompatWrapper>(); + + outerloop: + for (InputMethodInfoCompatWrapper imi : enabledImis) { + // We can return true immediately after we find two or more filtered IMEs. + if (filteredImis.size() > 1) return true; + final List<InputMethodSubtypeCompatWrapper> subtypes = + imm.getEnabledInputMethodSubtypeList(imi, true); + // IMEs that have no subtypes should be included. + if (subtypes.isEmpty()) { + filteredImis.add(imi); + continue; + } + // IMEs that have one or more non-auxiliary subtypes should be included. + for (InputMethodSubtypeCompatWrapper subtype : subtypes) { + if (!subtype.isAuxiliary()) { + filteredImis.add(imi); + continue outerloop; + } + } + } + + return filteredImis.size() > 1 // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled // input method subtype (The current IME should be LatinIME.) || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; @@ -537,8 +568,6 @@ public class Utils { return KeyboardId.MODE_IM; } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) { return KeyboardId.MODE_TEXT; - } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) { - return KeyboardId.MODE_WEB; } else { return KeyboardId.MODE_TEXT; } @@ -662,4 +691,28 @@ public class Utils { res.updateConfiguration(conf, res.getDisplayMetrics()); return saveLocale; } + + private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>(); + + public static Locale constructLocaleFromString(String localeStr) { + if (localeStr == null) + return null; + synchronized (sLocaleCache) { + if (sLocaleCache.containsKey(localeStr)) + return sLocaleCache.get(localeStr); + Locale retval = null; + String[] localeParams = localeStr.split("_", 3); + if (localeParams.length == 1) { + retval = new Locale(localeParams[0]); + } else if (localeParams.length == 2) { + retval = new Locale(localeParams[0], localeParams[1]); + } else if (localeParams.length == 3) { + retval = new Locale(localeParams[0], localeParams[1], localeParams[2]); + } + if (retval != null) { + sLocaleCache.put(localeStr, retval); + } + return retval; + } + } } diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java index 2389d4e3c..4377373d2 100644 --- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java +++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java @@ -39,6 +39,7 @@ public class WhitelistDictionary extends Dictionary { public static WhitelistDictionary init(Context context) { synchronized (sInstance) { if (context != null) { + // Wordlist is initialized by the proper language in Suggestion.java#init sInstance.initWordlist( context.getResources().getStringArray(R.array.wordlist_whitelist)); } else { diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java new file mode 100644 index 000000000..63c6d69d7 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java @@ -0,0 +1,115 @@ +/* + * 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.spellcheck; + +import android.content.Context; +import android.content.res.Resources; + +import com.android.inputmethod.compat.ArraysCompatUtils; +import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.Dictionary.DataType; +import com.android.inputmethod.latin.Dictionary.WordCallback; +import com.android.inputmethod.latin.DictionaryFactory; +import com.android.inputmethod.latin.Utils; +import com.android.inputmethod.latin.WordComposer; + +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +/** + * Implements spell checking methods. + */ +public class SpellChecker { + + public final Dictionary mDictionary; + + public SpellChecker(final Context context, final Locale locale) { + final Resources resources = context.getResources(); + final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources); + mDictionary = DictionaryFactory.createDictionaryFromManager(context, locale, + fallbackResourceId); + } + + // Note : this must be reentrant + /** + * Finds out whether a word is in the dictionary or not. + * + * @param text the sequence containing the word to check for. + * @param start the index of the first character of the word in text. + * @param end the index of the next-to-last character in text. + * @return true if the word is in the dictionary, false otherwise. + */ + public boolean isCorrect(final CharSequence text, final int start, final int end) { + return mDictionary.isValidWord(text.subSequence(start, end)); + } + + private static class SuggestionsGatherer implements WordCallback { + private final int DEFAULT_SUGGESTION_LENGTH = 16; + private final List<String> mSuggestions = new LinkedList<String>(); + private int[] mScores = new int[DEFAULT_SUGGESTION_LENGTH]; + private int mLength = 0; + + @Override + synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score, + int dicTypeId, DataType dataType) { + if (mLength >= mScores.length) { + final int newLength = mScores.length * 2; + mScores = new int[newLength]; + } + final int positionIndex = ArraysCompatUtils.binarySearch(mScores, 0, mLength, score); + // binarySearch returns the index if the element exists, and -<insertion index> - 1 + // if it doesn't. See documentation for binarySearch. + final int insertionIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1; + System.arraycopy(mScores, insertionIndex, mScores, insertionIndex + 1, + mLength - insertionIndex); + mLength += 1; + mScores[insertionIndex] = score; + mSuggestions.add(insertionIndex, new String(word, wordOffset, wordLength)); + return true; + } + + public List<String> getGatheredSuggestions() { + return mSuggestions; + } + } + + // Note : this must be reentrant + /** + * Gets a list of suggestions for a specific string. + * + * This returns a list of possible corrections for the text passed as an + * arguments. It may split or group words, and even perform grammatical + * analysis. + * + * @param text the sequence containing the word to check for. + * @param start the index of the first character of the word in text. + * @param end the index of the next-to-last character in text. + * @return a list of possible suggestions to replace the text. + */ + public List<String> getSuggestions(final CharSequence text, final int start, final int end) { + final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(); + final WordComposer composer = new WordComposer(); + for (int i = start; i < end; ++i) { + int character = text.charAt(i); + composer.add(character, new int[] { character }, + WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); + } + mDictionary.getWords(composer, suggestionsGatherer); + return suggestionsGatherer.getGatheredSuggestions(); + } +} |