aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java9
-rw-r--r--java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java4
-rw-r--r--java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java5
-rw-r--r--java/src/com/android/inputmethod/deprecated/recorrection/RecorrectionSuggestionEntries.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/Key.java168
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyDetector.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/Keyboard.java326
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java6
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardId.java50
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java398
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardView.java97
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboard.java82
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java11
-rw-r--r--java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java70
-rw-r--r--java/src/com/android/inputmethod/keyboard/MiniKeyboard.java26
-rw-r--r--java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/PointerTracker.java24
-rw-r--r--java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/ProximityInfo.java15
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java10
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java (renamed from java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java)270
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java55
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java95
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java2
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java120
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java52
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/Row.java33
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionary.java15
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java122
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java191
-rw-r--r--java/src/com/android/inputmethod/latin/CandidateView.java742
-rw-r--r--java/src/com/android/inputmethod/latin/ContactsDictionary.java16
-rw-r--r--java/src/com/android/inputmethod/latin/Dictionary.java18
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryCollection.java7
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java4
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java279
-rw-r--r--java/src/com/android/inputmethod/latin/Settings.java51
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java2
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java26
-rw-r--r--java/src/com/android/inputmethod/latin/SuggestedWords.java6
-rw-r--r--java/src/com/android/inputmethod/latin/UserDictionary.java7
-rw-r--r--java/src/com/android/inputmethod/latin/Utils.java60
-rw-r--r--java/src/com/android/inputmethod/latin/WhitelistDictionary.java5
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java174
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java (renamed from java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java)21
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java78
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java115
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java94
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java43
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java41
50 files changed, 2401 insertions, 1665 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 1619451f0..3dca9aae6 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -149,15 +149,6 @@ public class AccessibleKeyboardViewProxy {
return onHoverEventInternal(event, tracker);
}
- public boolean dispatchTouchEvent(MotionEvent event) {
- // Since touch exploration translates hover double-tap to a regular
- // single-tap, we're going to drop non-touch exploration events.
- if (!AccessibilityUtils.getInstance().isTouchExplorationEvent(event))
- return true;
-
- return false;
- }
-
/**
* Handles touch exploration events when Accessibility is turned on.
*
diff --git a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
index cf6cd0f5e..e75559e62 100644
--- a/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/deprecated/languageswitcher/InputLanguageSelection.java
@@ -16,7 +16,7 @@
package com.android.inputmethod.deprecated.languageswitcher;
-import com.android.inputmethod.keyboard.internal.KeyboardParser;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
import com.android.inputmethod.latin.DictionaryFactory;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.Settings;
@@ -162,7 +162,7 @@ public class InputLanguageSelection extends PreferenceActivity {
try {
final String localeStr = locale.toString();
- final String[] layoutCountryCodes = KeyboardParser.parseKeyboardLocale(
+ final String[] layoutCountryCodes = KeyboardBuilder.parseKeyboardLocale(
this, R.xml.kbd_qwerty).split(",", -1);
if (!TextUtils.isEmpty(localeStr) && layoutCountryCodes.length > 0) {
for (String s : layoutCountryCodes) {
diff --git a/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java b/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java
index d40728d25..bf2512d7b 100644
--- a/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java
+++ b/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java
@@ -271,9 +271,10 @@ public class Recorrection implements SharedPreferences.OnSharedPreferenceChangeL
// but always use the default setting defined in the resources.
if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
- res.getBoolean(R.bool.config_default_recorrection_enabled));
+ res.getBoolean(R.bool.config_default_compat_recorrection_enabled));
} else {
- mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
+ mRecorrectionEnabled =
+ res.getBoolean(R.bool.config_default_compat_recorrection_enabled);
}
}
diff --git a/java/src/com/android/inputmethod/deprecated/recorrection/RecorrectionSuggestionEntries.java b/java/src/com/android/inputmethod/deprecated/recorrection/RecorrectionSuggestionEntries.java
index 5e6c87044..f33a46277 100644
--- a/java/src/com/android/inputmethod/deprecated/recorrection/RecorrectionSuggestionEntries.java
+++ b/java/src/com/android/inputmethod/deprecated/recorrection/RecorrectionSuggestionEntries.java
@@ -57,6 +57,7 @@ public class RecorrectionSuggestionEntries {
private static SuggestedWords.Builder getTypedSuggestions(
Suggest suggest, KeyboardSwitcher keyboardSwitcher, WordComposer word) {
- return suggest.getSuggestedWordBuilder(keyboardSwitcher.getKeyboardView(), word, null);
+ return suggest.getSuggestedWordBuilder(keyboardSwitcher.getKeyboardView(), word, null,
+ keyboardSwitcher.getLatinKeyboard().getProximityInfo());
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 0ee8d7174..397b7b16b 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -27,13 +27,13 @@ import android.util.Xml;
import com.android.inputmethod.keyboard.internal.KeyStyles;
import com.android.inputmethod.keyboard.internal.KeyStyles.KeyStyle;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.keyboard.internal.KeyboardParser;
-import com.android.inputmethod.keyboard.internal.KeyboardParser.ParseException;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder.ParseException;
import com.android.inputmethod.keyboard.internal.PopupCharactersParser;
import com.android.inputmethod.keyboard.internal.Row;
import com.android.inputmethod.latin.R;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@@ -51,10 +51,10 @@ public class Key {
/** Hint label to display on the key in conjunction with the label */
public final CharSequence mHintLabel;
/** Option of the label */
- public final int mLabelOption;
- public static final int LABEL_OPTION_ALIGN_LEFT = 0x01;
- public static final int LABEL_OPTION_ALIGN_RIGHT = 0x02;
- public static final int LABEL_OPTION_ALIGN_LEFT_OF_CENTER = 0x08;
+ private final int mLabelOption;
+ private static final int LABEL_OPTION_ALIGN_LEFT = 0x01;
+ private static final int LABEL_OPTION_ALIGN_RIGHT = 0x02;
+ private static final int LABEL_OPTION_ALIGN_LEFT_OF_CENTER = 0x08;
private static final int LABEL_OPTION_LARGE_LETTER = 0x10;
private static final int LABEL_OPTION_FONT_NORMAL = 0x20;
private static final int LABEL_OPTION_FONT_MONO_SPACE = 0x40;
@@ -63,6 +63,8 @@ public class Key {
private static final int LABEL_OPTION_HAS_POPUP_HINT = 0x200;
private static final int LABEL_OPTION_HAS_UPPERCASE_LETTER = 0x400;
private static final int LABEL_OPTION_HAS_HINT_LABEL = 0x800;
+ private static final int LABEL_OPTION_WITH_ICON_LEFT = 0x1000;
+ private static final int LABEL_OPTION_WITH_ICON_RIGHT = 0x2000;
/** Icon to display instead of a label. Icon takes precedence over a label */
private Drawable mIcon;
@@ -74,7 +76,9 @@ public class Key {
/** Height of the key, not including the gap */
public final int mHeight;
/** The horizontal gap around this key */
- public final int mGap;
+ public final int mHorizontalGap;
+ /** The vertical gap below this key */
+ public final int mVerticalGap;
/** The visual insets */
public final int mVisualInsetsLeft;
public final int mVisualInsetsRight;
@@ -103,15 +107,14 @@ public class Key {
/** Whether this key repeats itself when held down */
public final boolean mRepeatable;
- /** The Keyboard that this key belongs to */
- private final Keyboard mKeyboard;
-
/** The current pressed state of this key */
private boolean mPressed;
/** If this is a sticky key, is its highlight on? */
private boolean mHighlightOn;
/** Key is enabled and responds on press */
private boolean mEnabled = true;
+ /** Whether this key needs to show the "..." popup hint for special purposes */
+ private boolean mNeedsSpecialPopupHint;
// keyWidth constants
private static final int KEYWIDTH_FILL_RIGHT = 0;
@@ -192,13 +195,13 @@ public class Key {
/**
* This constructor is being used only for key in popup mini keyboard.
*/
- public Key(Resources res, Keyboard keyboard, CharSequence popupCharacter, int x, int y,
+ public Key(Resources res, KeyboardParams params, CharSequence popupCharacter, int x, int y,
int width, int height, int edgeFlags) {
- mKeyboard = keyboard;
- mHeight = height - keyboard.getVerticalGap();
- mGap = keyboard.getHorizontalGap();
+ mHeight = height - params.mVerticalGap;
+ mHorizontalGap = params.mHorizontalGap;
+ mVerticalGap = params.mVerticalGap;
mVisualInsetsLeft = mVisualInsetsRight = 0;
- mWidth = width - mGap;
+ mWidth = width - mHorizontalGap;
mEdgeFlags = edgeFlags;
mHintLabel = null;
mLabelOption = 0;
@@ -211,10 +214,10 @@ public class Key {
mLabel = PopupCharactersParser.getLabel(popupSpecification);
mOutputText = PopupCharactersParser.getOutputText(popupSpecification);
final int code = PopupCharactersParser.getCode(res, popupSpecification);
- mCode = keyboard.isRtlKeyboard() ? getRtlParenthesisCode(code) : code;
- mIcon = keyboard.mIconsSet.getIcon(PopupCharactersParser.getIconId(popupSpecification));
+ mCode = params.mIsRtlKeyboard ? getRtlParenthesisCode(code) : code;
+ mIcon = params.mIconsSet.getIcon(PopupCharactersParser.getIconId(popupSpecification));
// Horizontal gap is divided equally to both sides of the key.
- mX = x + mGap / 2;
+ mX = x + mHorizontalGap / 2;
mY = y;
}
@@ -222,30 +225,30 @@ public class Key {
* Create a key with the given top-left coordinate and extract its attributes from the XML
* parser.
* @param res resources associated with the caller's context
- * @param row the row that this key belongs to. The row must already be attached to
- * a {@link Keyboard}.
+ * @param params the keyboard building parameters.
+ * @param row the row that this key belongs to.
* @param x the x coordinate of the top-left
* @param y the y coordinate of the top-left
* @param parser the XML parser containing the attributes for this key
* @param keyStyles active key styles set
*/
- public Key(Resources res, Row row, int x, int y, XmlResourceParser parser,
- KeyStyles keyStyles) {
- mKeyboard = row.getKeyboard();
+ public Key(Resources res, KeyboardParams params, Row row, int x, int y,
+ XmlResourceParser parser, KeyStyles keyStyles) {
final TypedArray keyboardAttr = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard);
int keyWidth;
try {
- mHeight = KeyboardParser.getDimensionOrFraction(keyboardAttr,
+ mHeight = KeyboardBuilder.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_rowHeight,
- mKeyboard.getKeyboardHeight(), row.mDefaultHeight) - row.mVerticalGap;
- mGap = KeyboardParser.getDimensionOrFraction(keyboardAttr,
+ params.mHeight, row.mRowHeight) - params.mVerticalGap;
+ mHorizontalGap = KeyboardBuilder.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_horizontalGap,
- mKeyboard.getDisplayWidth(), row.mDefaultHorizontalGap);
- keyWidth = KeyboardParser.getDimensionOrFraction(keyboardAttr,
+ params.mWidth, params.mHorizontalGap);
+ mVerticalGap = params.mVerticalGap;
+ keyWidth = KeyboardBuilder.getDimensionOrFraction(keyboardAttr,
R.styleable.Keyboard_keyWidth,
- mKeyboard.getDisplayWidth(), row.mDefaultWidth);
+ params.mWidth, row.mDefaultKeyWidth);
} finally {
keyboardAttr.recycle();
}
@@ -263,8 +266,8 @@ public class Key {
style = keyStyles.getEmptyKeyStyle();
}
- final int keyboardWidth = mKeyboard.getDisplayWidth();
- int keyXPos = KeyboardParser.getDimensionOrFraction(keyAttr,
+ final int keyboardWidth = params.mOccupiedWidth;
+ int keyXPos = KeyboardBuilder.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_keyXPos, keyboardWidth, x);
if (keyXPos < 0) {
// If keyXPos is negative, the actual x-coordinate will be k + keyXPos.
@@ -288,23 +291,28 @@ public class Key {
}
// Horizontal gap is divided equally to both sides of the key.
- mX = keyXPos + mGap / 2;
+ mX = keyXPos + mHorizontalGap / 2;
mY = y;
- mWidth = keyWidth - mGap;
+ mWidth = keyWidth - mHorizontalGap;
- final CharSequence[] popupCharacters = style.getTextArray(keyAttr,
- R.styleable.Keyboard_Key_popupCharacters);
+ CharSequence[] popupCharacters = style.getTextArray(
+ keyAttr, R.styleable.Keyboard_Key_popupCharacters);
+ if (params.mId.mPasswordInput) {
+ popupCharacters = PopupCharactersParser.filterOut(
+ res, popupCharacters, PopupCharactersParser.NON_ASCII_FILTER);
+ }
// In Arabic symbol layouts, we'd like to keep digits in popup characters regardless of
// config_digit_popup_characters_enabled.
- if (mKeyboard.mId.isAlphabetKeyboard() && !res.getBoolean(
+ if (params.mId.isAlphabetKeyboard() && !res.getBoolean(
R.bool.config_digit_popup_characters_enabled)) {
- mPopupCharacters = filterOutDigitPopupCharacters(popupCharacters);
+ mPopupCharacters = PopupCharactersParser.filterOut(
+ res, popupCharacters, PopupCharactersParser.DIGIT_FILTER);
} else {
mPopupCharacters = popupCharacters;
}
mMaxPopupColumn = style.getInt(keyboardAttr,
R.styleable.Keyboard_Key_maxPopupKeyboardColumn,
- mKeyboard.getMaxPopupKeyboardColumn());
+ params.mMaxPopupColumn);
mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
mFunctional = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isFunctional, false);
@@ -312,19 +320,23 @@ public class Key {
mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
mEdgeFlags = 0;
- final KeyboardIconsSet iconsSet = mKeyboard.mIconsSet;
- mVisualInsetsLeft = KeyboardParser.getDimensionOrFraction(keyAttr,
+ final KeyboardIconsSet iconsSet = params.mIconsSet;
+ mVisualInsetsLeft = KeyboardBuilder.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_visualInsetsLeft, keyboardWidth, 0);
- mVisualInsetsRight = KeyboardParser.getDimensionOrFraction(keyAttr,
+ mVisualInsetsRight = KeyboardBuilder.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_visualInsetsRight, keyboardWidth, 0);
mPreviewIcon = iconsSet.getIcon(style.getInt(
keyAttr, R.styleable.Keyboard_Key_keyIconPreview,
KeyboardIconsSet.ICON_UNDEFINED));
- Keyboard.setDefaultBounds(mPreviewIcon);
mIcon = iconsSet.getIcon(style.getInt(
keyAttr, R.styleable.Keyboard_Key_keyIcon,
KeyboardIconsSet.ICON_UNDEFINED));
- Keyboard.setDefaultBounds(mIcon);
+ final int shiftedIconId = style.getInt(keyAttr, R.styleable.Keyboard_Key_keyIconShifted,
+ KeyboardIconsSet.ICON_UNDEFINED);
+ if (shiftedIconId != KeyboardIconsSet.ICON_UNDEFINED) {
+ final Drawable shiftedIcon = iconsSet.getIcon(shiftedIconId);
+ params.addShiftedIcon(this, shiftedIcon);
+ }
mHintLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyHintLabel);
mLabel = style.getText(keyAttr, R.styleable.Keyboard_Key_keyLabel);
@@ -336,18 +348,12 @@ public class Key {
Keyboard.CODE_UNSPECIFIED);
if (code == Keyboard.CODE_UNSPECIFIED && !TextUtils.isEmpty(mLabel)) {
final int firstChar = mLabel.charAt(0);
- mCode = mKeyboard.isRtlKeyboard() ? getRtlParenthesisCode(firstChar) : firstChar;
+ mCode = params.mIsRtlKeyboard ? getRtlParenthesisCode(firstChar) : firstChar;
} else if (code != Keyboard.CODE_UNSPECIFIED) {
mCode = code;
} else {
mCode = Keyboard.CODE_DUMMY;
}
-
- final Drawable shiftedIcon = iconsSet.getIcon(style.getInt(
- keyAttr, R.styleable.Keyboard_Key_keyIconShifted,
- KeyboardIconsSet.ICON_UNDEFINED));
- if (shiftedIcon != null)
- mKeyboard.getShiftedIcons().put(this, shiftedIcon);
} finally {
keyAttr.recycle();
}
@@ -357,10 +363,6 @@ public class Key {
mEdgeFlags |= flags;
}
- public CharSequence getCaseAdjustedLabel() {
- return mKeyboard.adjustLabelCase(mLabel);
- }
-
public Typeface selectTypeface(Typeface defaultTypeface) {
// TODO: Handle "bold" here too?
if ((mLabelOption & LABEL_OPTION_FONT_NORMAL) != 0) {
@@ -386,10 +388,30 @@ public class Key {
}
}
+ public boolean isAlignLeft() {
+ return (mLabelOption & LABEL_OPTION_ALIGN_LEFT) != 0;
+ }
+
+ public boolean isAlignRight() {
+ return (mLabelOption & LABEL_OPTION_ALIGN_RIGHT) != 0;
+ }
+
+ public boolean isAlignLeftOfCenter() {
+ return (mLabelOption & LABEL_OPTION_ALIGN_LEFT_OF_CENTER) != 0;
+ }
+
public boolean hasPopupHint() {
return (mLabelOption & LABEL_OPTION_HAS_POPUP_HINT) != 0;
}
+ public void setNeedsSpecialPopupHint(boolean needsSpecialPopupHint) {
+ mNeedsSpecialPopupHint = needsSpecialPopupHint;
+ }
+
+ public boolean needsSpecialPopupHint() {
+ return mNeedsSpecialPopupHint;
+ }
+
public boolean hasUppercaseLetter() {
return (mLabelOption & LABEL_OPTION_HAS_UPPERCASE_LETTER) != 0;
}
@@ -398,34 +420,12 @@ public class Key {
return (mLabelOption & LABEL_OPTION_HAS_HINT_LABEL) != 0;
}
- private static boolean isDigitPopupCharacter(CharSequence label) {
- return label != null && label.length() == 1 && Character.isDigit(label.charAt(0));
+ public boolean hasLabelWithIconLeft() {
+ return (mLabelOption & LABEL_OPTION_WITH_ICON_LEFT) != 0;
}
- private static CharSequence[] filterOutDigitPopupCharacters(CharSequence[] popupCharacters) {
- if (popupCharacters == null || popupCharacters.length < 1)
- return null;
- if (popupCharacters.length == 1 && isDigitPopupCharacter(
- PopupCharactersParser.getLabel(popupCharacters[0].toString())))
- return null;
- ArrayList<CharSequence> filtered = null;
- for (int i = 0; i < popupCharacters.length; i++) {
- final CharSequence popupSpec = popupCharacters[i];
- if (isDigitPopupCharacter(PopupCharactersParser.getLabel(popupSpec.toString()))) {
- if (filtered == null) {
- filtered = new ArrayList<CharSequence>();
- for (int j = 0; j < i; j++)
- filtered.add(popupCharacters[j]);
- }
- } else if (filtered != null) {
- filtered.add(popupSpec);
- }
- }
- if (filtered == null)
- return popupCharacters;
- if (filtered.size() == 0)
- return null;
- return filtered.toArray(new CharSequence[filtered.size()]);
+ public boolean hasLabelWithIconRight() {
+ return (mLabelOption & LABEL_OPTION_WITH_ICON_RIGHT) != 0;
}
public Drawable getIcon() {
@@ -482,10 +482,10 @@ public class Key {
* assume that all points between the key and the edge are considered to be on the key.
*/
public boolean isOnKey(int x, int y) {
- final int left = mX - mGap / 2;
- final int right = left + mWidth + mGap;
+ final int left = mX - mHorizontalGap / 2;
+ final int right = left + mWidth + mHorizontalGap;
final int top = mY;
- final int bottom = top + mHeight + mKeyboard.getVerticalGap();
+ final int bottom = top + mHeight + mVerticalGap;
final int flags = mEdgeFlags;
if (flags == 0) {
return x >= left && x <= right && y >= top && y <= bottom;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index 6d25025c5..0a3acb48b 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -57,7 +57,7 @@ public class KeyDetector {
mCorrectionX = (int)correctionX;
mCorrectionY = (int)correctionY;
mKeyboard = keyboard;
- final int threshold = keyboard.getMostCommonKeyWidth();
+ final int threshold = keyboard.mMostCommonKeyWidth;
mProximityThresholdSquare = threshold * threshold;
}
@@ -153,7 +153,7 @@ public class KeyDetector {
}
private void getNearbyKeyCodes(final int[] allCodes) {
- final List<Key> keys = getKeyboard().getKeys();
+ final List<Key> keys = getKeyboard().mKeys;
final int[] indices = mIndices;
// allCodes[0] should always have the key code even if it is a non-letter key.
@@ -187,7 +187,7 @@ public class KeyDetector {
* @return The nearest key index
*/
public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
- final List<Key> keys = getKeyboard().getKeys();
+ final List<Key> keys = getKeyboard().mKeys;
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 19847c5ec..f8e08b06a 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -16,25 +16,17 @@
package com.android.inputmethod.keyboard;
-import android.content.Context;
-import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
-import android.util.Log;
import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
-import com.android.inputmethod.keyboard.internal.KeyboardParser;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.keyboard.internal.KeyboardShiftState;
-import com.android.inputmethod.latin.R;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
@@ -55,8 +47,6 @@ import java.util.Map;
* </pre>
*/
public class Keyboard {
- private static final String TAG = Keyboard.class.getSimpleName();
-
public static final int EDGE_LEFT = 0x01;
public static final int EDGE_RIGHT = 0x02;
public static final int EDGE_TOP = 0x04;
@@ -77,6 +67,8 @@ public class Keyboard {
public static final int CODE_CLOSING_SQUARE_BRACKET = ']';
public static final int CODE_CLOSING_CURLY_BRACKET = '}';
public static final int CODE_CLOSING_ANGLE_BRACKET = '>';
+ public static final int CODE_DIGIT0 = '0';
+ public static final int CODE_PLUS = '+';
/** Special keys code. These should be aligned with values/keycodes.xml */
@@ -87,227 +79,94 @@ public class Keyboard {
public static final int CODE_CANCEL = -4;
public static final int CODE_DELETE = -5;
public static final int CODE_SETTINGS = -6;
- public static final int CODE_SETTINGS_LONGPRESS = -7;
- public static final int CODE_SHORTCUT = -8;
+ public static final int CODE_SHORTCUT = -7;
// Code value representing the code is not specified.
public static final int CODE_UNSPECIFIED = -99;
- /** Horizontal gap default for all rows */
- private int mDefaultHorizontalGap;
+ public final KeyboardId mId;
+
+ /** Total height of the keyboard, including the padding and keys */
+ public final int mOccupiedHeight;
+ /** Total width of the keyboard, including the padding and keys */
+ public final int mOccupiedWidth;
- /** Default key width */
- private int mDefaultWidth;
+ public final int mHeight;
+ public final int mWidth;
- /** Default key height */
- private int mDefaultHeight;
+ /** Default row height */
+ public final int mDefaultRowHeight;
/** Default gap between rows */
- private int mDefaultVerticalGap;
+ public final int mVerticalGap;
+
+ public final int mMostCommonKeyWidth;
/** Popup keyboard template */
- private int mPopupKeyboardResId;
+ public final int mPopupKeyboardResId;
/** Maximum column for popup keyboard */
- private int mMaxPopupColumn;
+ public final int mMaxPopupColumn;
/** True if Right-To-Left keyboard */
- private boolean mIsRtlKeyboard;
+ public final boolean mIsRtlKeyboard;
- /** List of shift keys in this keyboard and its icons and state */
- private final List<Key> mShiftKeys = new ArrayList<Key>();
- private final HashMap<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>();
- private final HashMap<Key, Drawable> mNormalShiftIcons = new HashMap<Key, Drawable>();
- private final HashSet<Key> mShiftLockEnabled = new HashSet<Key>();
- private final KeyboardShiftState mShiftState = new KeyboardShiftState();
-
- /** Total height of the keyboard, including the padding and keys */
- private int mTotalHeight;
-
- /**
- * Total width (minimum width) of the keyboard, including left side gaps and keys, but not any
- * gaps on the right side.
- */
- private int mMinWidth;
+ /** List of keys and icons in this keyboard */
+ public final List<Key> mKeys;
+ public final List<Key> mShiftKeys;
+ public final Set<Key> mShiftLockKeys;
+ public final Map<Key, Drawable> mShiftedIcons;
+ public final Map<Key, Drawable> mUnshiftedIcons;
+ public final KeyboardIconsSet mIconsSet;
- /** List of keys in this keyboard */
- private final List<Key> mKeys = new ArrayList<Key>();
-
- /** Width of the screen available to fit the keyboard */
- private final int mDisplayWidth;
-
- /** Height of the screen */
- private final int mDisplayHeight;
-
- /** Height of keyboard */
- private int mKeyboardHeight;
-
- private int mMostCommonKeyWidth = 0;
-
- public final KeyboardId mId;
-
- public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
-
- // Variables for pre-computing nearest keys.
-
- // TODO: Change GRID_WIDTH and GRID_HEIGHT to private.
- public final int GRID_WIDTH;
- public final int GRID_HEIGHT;
+ private final KeyboardShiftState mShiftState = new KeyboardShiftState();
private final ProximityInfo mProximityInfo;
- /**
- * Creates a keyboard from the given xml key layout file.
- * @param context the application or service context
- * @param xmlLayoutResId the resource file that contains the keyboard layout and keys.
- * @param id keyboard identifier
- * @param width keyboard width
- */
+ public Keyboard(KeyboardParams params) {
+ mId = params.mId;
+ mOccupiedHeight = params.mOccupiedHeight;
+ mOccupiedWidth = params.mOccupiedWidth;
+ mHeight = params.mHeight;
+ mWidth = params.mWidth;
+ mMostCommonKeyWidth = params.mMostCommonKeyWidth;
+ mIsRtlKeyboard = params.mIsRtlKeyboard;
+ mPopupKeyboardResId = params.mPopupKeyboardResId;
+ mMaxPopupColumn = params.mMaxPopupColumn;
+
+ mDefaultRowHeight = params.mDefaultRowHeight;
+ mVerticalGap = params.mVerticalGap;
+
+ mKeys = Collections.unmodifiableList(params.mKeys);
+ mShiftKeys = Collections.unmodifiableList(params.mShiftKeys);
+ mShiftLockKeys = Collections.unmodifiableSet(params.mShiftLockKeys);
+ mShiftedIcons = Collections.unmodifiableMap(params.mShiftedIcons);
+ mUnshiftedIcons = Collections.unmodifiableMap(params.mUnshiftedIcons);
+ mIconsSet = params.mIconsSet;
- public Keyboard(Context context, int xmlLayoutResId, KeyboardId id, int width) {
- final Resources res = context.getResources();
- GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
- GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
-
- final int horizontalEdgesPadding = (int)res.getDimension(
- R.dimen.keyboard_horizontal_edges_padding);
- mDisplayWidth = width - horizontalEdgesPadding * 2;
- // TODO: Adjust the height by referring to the height of area available for drawing as well.
- mDisplayHeight = res.getDisplayMetrics().heightPixels;
-
- mDefaultHorizontalGap = 0;
- setKeyWidth(mDisplayWidth / 10);
- mDefaultVerticalGap = 0;
- mDefaultHeight = mDefaultWidth;
- mId = id;
- loadKeyboard(context, xmlLayoutResId);
mProximityInfo = new ProximityInfo(
- GRID_WIDTH, GRID_HEIGHT, getMinWidth(), getHeight(), getKeyWidth(), mKeys);
- }
-
- public int getProximityInfo() {
- return mProximityInfo.getNativeProximityInfo();
- }
-
- public List<Key> getKeys() {
- return mKeys;
- }
-
- public int getHorizontalGap() {
- return mDefaultHorizontalGap;
- }
-
- public void setHorizontalGap(int gap) {
- mDefaultHorizontalGap = gap;
+ params.GRID_WIDTH, params.GRID_HEIGHT, mOccupiedWidth, mOccupiedHeight,
+ mMostCommonKeyWidth, mKeys);
}
- public int getVerticalGap() {
- return mDefaultVerticalGap;
- }
-
- public void setVerticalGap(int gap) {
- mDefaultVerticalGap = gap;
- }
-
- public int getRowHeight() {
- return mDefaultHeight;
- }
-
- public void setRowHeight(int height) {
- mDefaultHeight = height;
- }
-
- public int getKeyWidth() {
- return mDefaultWidth;
- }
-
- public void setKeyWidth(int width) {
- mDefaultWidth = width;
- }
-
- /**
- * Returns the total height of the keyboard
- * @return the total height of the keyboard
- */
- public int getHeight() {
- return mTotalHeight;
+ public ProximityInfo getProximityInfo() {
+ return mProximityInfo;
}
- public void setHeight(int height) {
- mTotalHeight = height;
- }
-
- public int getMinWidth() {
- return mMinWidth;
- }
-
- public void setMinWidth(int minWidth) {
- mMinWidth = minWidth;
- }
-
- public int getDisplayHeight() {
- return mDisplayHeight;
- }
-
- public int getDisplayWidth() {
- return mDisplayWidth;
- }
-
- public int getKeyboardHeight() {
- return mKeyboardHeight;
- }
-
- public void setKeyboardHeight(int height) {
- mKeyboardHeight = height;
- }
-
- public boolean isRtlKeyboard() {
- return mIsRtlKeyboard;
- }
-
- public void setRtlKeyboard(boolean isRtl) {
- mIsRtlKeyboard = isRtl;
- }
-
- public int getPopupKeyboardResId() {
- return mPopupKeyboardResId;
- }
-
- public void setPopupKeyboardResId(int resId) {
- mPopupKeyboardResId = resId;
- }
-
- public int getMaxPopupKeyboardColumn() {
- return mMaxPopupColumn;
- }
-
- public void setMaxPopupKeyboardColumn(int column) {
- mMaxPopupColumn = column;
- }
-
- public List<Key> getShiftKeys() {
- return mShiftKeys;
- }
-
- public Map<Key, Drawable> getShiftedIcons() {
- return mShiftedIcons;
- }
-
- public void enableShiftLock() {
- for (final Key key : getShiftKeys()) {
- mShiftLockEnabled.add(key);
- mNormalShiftIcons.put(key, key.getIcon());
- }
- }
-
- public boolean isShiftLockEnabled(Key key) {
- return mShiftLockEnabled.contains(key);
+ public boolean hasShiftLockKey() {
+ return !mShiftLockKeys.isEmpty();
}
public boolean setShiftLocked(boolean newShiftLockState) {
- final Map<Key, Drawable> shiftedIcons = getShiftedIcons();
- for (final Key key : getShiftKeys()) {
+ for (final Key key : mShiftLockKeys) {
+ // To represent "shift locked" state. The highlight is handled by background image that
+ // might be a StateListDrawable.
key.setHighlightOn(newShiftLockState);
- key.setIcon(newShiftLockState ? shiftedIcons.get(key) : mNormalShiftIcons.get(key));
+ // To represent "shifted" state. The key might have a shifted icon.
+ if (newShiftLockState && mShiftedIcons.containsKey(key)) {
+ key.setIcon(mShiftedIcons.get(key));
+ } else {
+ key.setIcon(mUnshiftedIcons.get(key));
+ }
}
mShiftState.setShiftLocked(newShiftLockState);
return true;
@@ -318,12 +177,11 @@ public class Keyboard {
}
public boolean setShifted(boolean newShiftState) {
- final Map<Key, Drawable> shiftedIcons = getShiftedIcons();
- for (final Key key : getShiftKeys()) {
+ for (final Key key : mShiftKeys) {
if (!newShiftState && !mShiftState.isShiftLocked()) {
- key.setIcon(mNormalShiftIcons.get(key));
+ key.setIcon(mUnshiftedIcons.get(key));
} else if (newShiftState && !mShiftState.isShiftedOrShiftLocked()) {
- key.setIcon(shiftedIcons.get(key));
+ key.setIcon(mShiftedIcons.get(key));
}
}
return mShiftState.setShifted(newShiftState);
@@ -384,52 +242,4 @@ public class Keyboard {
public int[] getNearestKeys(int x, int y) {
return mProximityInfo.getNearestKeys(x, y);
}
-
- /**
- * Compute the most common key width in order to use it as proximity key detection threshold.
- *
- * @return The most common key width in the keyboard
- */
- public int getMostCommonKeyWidth() {
- if (mMostCommonKeyWidth == 0) {
- final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
- int maxCount = 0;
- int mostCommonWidth = 0;
- for (final Key key : mKeys) {
- final Integer width = key.mWidth + key.mGap;
- Integer count = histogram.get(width);
- if (count == null)
- count = 0;
- histogram.put(width, ++count);
- if (count > maxCount) {
- maxCount = count;
- mostCommonWidth = width;
- }
- }
- mMostCommonKeyWidth = mostCommonWidth;
- }
- return mMostCommonKeyWidth;
- }
-
- private void loadKeyboard(Context context, int xmlLayoutResId) {
- try {
- KeyboardParser parser = new KeyboardParser(this, context);
- parser.parseKeyboard(xmlLayoutResId);
- // mMinWidth is the width of this keyboard which is maximum width of row.
- mMinWidth = parser.getMaxRowWidth();
- mTotalHeight = parser.getTotalHeight();
- } catch (XmlPullParserException e) {
- Log.w(TAG, "keyboard XML parse error: " + e);
- throw new IllegalArgumentException(e);
- } catch (IOException e) {
- Log.w(TAG, "keyboard XML parse error: " + e);
- throw new RuntimeException(e);
- }
- }
-
- public static void setDefaultBounds(Drawable drawable) {
- if (drawable != null)
- drawable.setBounds(0, 0, drawable.getIntrinsicWidth(),
- drawable.getIntrinsicHeight());
- }
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 905f779c0..864091289 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -70,4 +70,10 @@ public interface KeyboardActionListener {
* Called when user released a finger outside any key.
*/
public void onCancelInput();
+
+ /**
+ * Send a non-"code input" custom request to the listener.
+ * @return true if the request has been consumed, false otherwise.
+ */
+ public boolean onCustomRequest(int requestCode);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 3f30165aa..d0a2f864c 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -42,8 +42,6 @@ public class KeyboardId {
public static final int F2KEY_MODE_SHORTCUT_IME = 2;
public static final int F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS = 3;
- private static final int MINI_KEYBOARD_ID_MARKER = -1;
-
public final Locale mLocale;
public final int mOrientation;
public final int mWidth;
@@ -55,10 +53,9 @@ public class KeyboardId {
public final boolean mHasSettingsKey;
public final int mF2KeyMode;
public final boolean mClobberSettingsKey;
- public final boolean mVoiceKeyEnabled;
- public final boolean mHasVoiceKey;
+ public final boolean mShortcutKeyEnabled;
+ public final boolean mHasShortcutKey;
public final int mImeAction;
- public final boolean mEnableShiftLock;
public final String mXmlName;
public final EditorInfo mAttribute;
@@ -67,8 +64,7 @@ public class KeyboardId {
public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int width,
int mode, EditorInfo attribute, boolean hasSettingsKey, int f2KeyMode,
- boolean clobberSettingsKey, boolean voiceKeyEnabled, boolean hasVoiceKey,
- boolean enableShiftLock) {
+ boolean clobberSettingsKey, boolean shortcutKeyEnabled, boolean hasShortcutKey) {
final int inputType = (attribute != null) ? attribute.inputType : 0;
final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
this.mLocale = locale;
@@ -85,13 +81,12 @@ public class KeyboardId {
this.mHasSettingsKey = hasSettingsKey;
this.mF2KeyMode = f2KeyMode;
this.mClobberSettingsKey = clobberSettingsKey;
- this.mVoiceKeyEnabled = voiceKeyEnabled;
- this.mHasVoiceKey = hasVoiceKey;
+ this.mShortcutKeyEnabled = shortcutKeyEnabled;
+ this.mHasShortcutKey = hasShortcutKey;
// We are interested only in {@link EditorInfo#IME_MASK_ACTION} enum value and
// {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION}.
this.mImeAction = imeOptions & (
EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
- this.mEnableShiftLock = enableShiftLock;
this.mXmlName = xmlName;
this.mAttribute = attribute;
@@ -107,34 +102,29 @@ public class KeyboardId {
hasSettingsKey,
f2KeyMode,
clobberSettingsKey,
- voiceKeyEnabled,
- hasVoiceKey,
+ shortcutKeyEnabled,
+ hasShortcutKey,
mImeAction,
- enableShiftLock,
});
}
- public KeyboardId cloneAsMiniKeyboard() {
- return new KeyboardId("mini popup keyboard", MINI_KEYBOARD_ID_MARKER, mLocale, mOrientation,
- mWidth, mMode, mAttribute, false, F2KEY_MODE_NONE, false, false, false, false);
+ public KeyboardId cloneWithNewXml(String xmlName, int xmlId) {
+ return new KeyboardId(xmlName, xmlId, mLocale, mOrientation, mWidth, mMode, mAttribute,
+ false, F2KEY_MODE_NONE, false, false, false);
}
public KeyboardId cloneWithNewGeometry(int orientation, int width) {
if (mWidth == width)
return this;
return new KeyboardId(mXmlName, mXmlId, mLocale, orientation, width, mMode, mAttribute,
- mHasSettingsKey, mF2KeyMode, mClobberSettingsKey, mVoiceKeyEnabled, mHasVoiceKey,
- mEnableShiftLock);
+ mHasSettingsKey, mF2KeyMode, mClobberSettingsKey, mShortcutKeyEnabled,
+ mHasShortcutKey);
}
public int getXmlId() {
return mXmlId;
}
- public boolean isMiniKeyboard() {
- return mXmlId == MINI_KEYBOARD_ID_MARKER;
- }
-
public boolean isAlphabetKeyboard() {
return mXmlId == R.xml.kbd_qwerty;
}
@@ -160,7 +150,7 @@ public class KeyboardId {
return other instanceof KeyboardId && equals((KeyboardId) other);
}
- boolean equals(KeyboardId other) {
+ private boolean equals(KeyboardId other) {
return other.mLocale.equals(this.mLocale)
&& other.mOrientation == this.mOrientation
&& other.mWidth == this.mWidth
@@ -171,10 +161,9 @@ public class KeyboardId {
&& other.mHasSettingsKey == this.mHasSettingsKey
&& other.mF2KeyMode == this.mF2KeyMode
&& other.mClobberSettingsKey == this.mClobberSettingsKey
- && other.mVoiceKeyEnabled == this.mVoiceKeyEnabled
- && other.mHasVoiceKey == this.mHasVoiceKey
- && other.mImeAction == this.mImeAction
- && other.mEnableShiftLock == this.mEnableShiftLock;
+ && other.mShortcutKeyEnabled == this.mShortcutKeyEnabled
+ && other.mHasShortcutKey == this.mHasShortcutKey
+ && other.mImeAction == this.mImeAction;
}
@Override
@@ -184,7 +173,7 @@ public class KeyboardId {
@Override
public String toString() {
- return String.format("[%s.xml %s %s%d %s %s %s%s%s%s%s%s%s%s]",
+ return String.format("[%s.xml %s %s%d %s %s %s%s%s%s%s%s%s]",
mXmlName,
mLocale,
(mOrientation == 1 ? "port" : "land"), mWidth,
@@ -195,9 +184,8 @@ public class KeyboardId {
(mNavigateAction ? " navigateAction" : ""),
(mPasswordInput ? " passwordInput" : ""),
(mHasSettingsKey ? " hasSettingsKey" : ""),
- (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""),
- (mHasVoiceKey ? " hasVoiceKey" : ""),
- (mEnableShiftLock ? " enableShiftLock" : "")
+ (mShortcutKeyEnabled ? " shortcutKeyEnabled" : ""),
+ (mHasShortcutKey ? " hasShortcutKey" : "")
);
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 552a3cd30..21477a992 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -18,7 +18,9 @@ package com.android.inputmethod.keyboard;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.res.Configuration;
import android.content.res.Resources;
+import android.inputmethodservice.InputMethodService;
import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.InflateException;
@@ -27,7 +29,6 @@ import android.view.View;
import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
-import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
import com.android.inputmethod.keyboard.internal.ModifierKeyState;
import com.android.inputmethod.keyboard.internal.ShiftKeyState;
import com.android.inputmethod.latin.LatinIME;
@@ -38,6 +39,7 @@ import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.Utils;
import java.lang.ref.SoftReference;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
@@ -62,6 +64,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
private View mCurrentInputView;
private LatinKeyboardView mKeyboardView;
private LatinIME mInputMethodService;
+ private String mPackageName;
+ private Resources mResources;
// TODO: Combine these key state objects with auto mode switch state.
private ShiftKeyState mShiftKeyState = new ShiftKeyState("Shift");
@@ -74,6 +78,11 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
private KeyboardId mCurrentId;
private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache =
new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
+ // TODO: Remove this cache object when {@link DisplayMetrics} has actual window width excluding
+ // system navigation bar.
+ private WindowWidthCache mWindowWidthCache;
+
+ private KeyboardLayoutState mSavedKeyboardState = new KeyboardLayoutState();
/** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
* what user actually typed. */
@@ -91,20 +100,135 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
private static final int SWITCH_STATE_CHORDING_SYMBOL = 6;
private int mSwitchState = SWITCH_STATE_ALPHA;
- private static final int SETTINGS_KEY_MODE_AUTO = R.string.settings_key_mode_auto;
- private static final int SETTINGS_KEY_MODE_ALWAYS_SHOW =
- R.string.settings_key_mode_always_show;
- // NOTE: No need to have SETTINGS_KEY_MODE_ALWAYS_HIDE here because it's not being referred to
- // in the source code now.
- // Default is SETTINGS_KEY_MODE_AUTO.
- private static final int DEFAULT_SETTINGS_KEY_MODE = SETTINGS_KEY_MODE_AUTO;
-
private int mThemeIndex = -1;
private Context mThemeContext;
- private int mWindowWidth;
private static final KeyboardSwitcher sInstance = new KeyboardSwitcher();
+ private static class WindowWidthCache {
+ private final InputMethodService mService;
+ private final Resources mResources;
+ private final boolean mIsRegistered[] = new boolean[Configuration.ORIENTATION_SQUARE + 1];
+ private final int mWidth[] = new int[Configuration.ORIENTATION_SQUARE + 1];
+
+ public WindowWidthCache(InputMethodService service) {
+ mService = service;
+ mResources = service.getResources();
+
+ Arrays.fill(mIsRegistered, false);
+ Arrays.fill(mWidth, 0);
+ }
+
+ private int getCurrentWindowWidth() {
+ return mService.getWindow().getWindow().getDecorView().getWidth();
+ }
+
+ public int getWidth(Configuration conf) {
+ final int orientation = conf.orientation;
+ try {
+ final int width = mWidth[orientation];
+ if (mIsRegistered[orientation] || width > 0) {
+ // Return registered or cached window width for this orientation.
+ return width;
+ }
+ // Fall through
+ } catch (IndexOutOfBoundsException e) {
+ Log.w(TAG, "unknwon orientation value " + orientation);
+ // Fall through
+ }
+
+ // Return screen width as default window width.
+ return mResources.getDisplayMetrics().widthPixels;
+ }
+
+ public int getWidthOnSizeChanged(Configuration conf) {
+ final int orientation = conf.orientation;
+ try {
+ if (mIsRegistered[orientation]) {
+ // Return registered window width for this orientation.
+ return mWidth[orientation];
+ }
+
+ // Cache the current window width without registering.
+ final int width = getCurrentWindowWidth();
+ mWidth[orientation] = width;
+ return width;
+ } catch (IndexOutOfBoundsException e) {
+ Log.w(TAG, "unknwon orientation value " + orientation);
+ return 0;
+ }
+ }
+
+ public void registerWidth() {
+ final int orientation = mResources.getConfiguration().orientation;
+ try {
+ if (!mIsRegistered[orientation]) {
+ final int width = getCurrentWindowWidth();
+ if (width > 0) {
+ // Register current window width.
+ mWidth[orientation] = width;
+ mIsRegistered[orientation] = true;
+ }
+ }
+ } catch (IndexOutOfBoundsException e) {
+ Log.w(TAG, "unknwon orientation value " + orientation);
+ }
+ }
+ }
+
+ public class KeyboardLayoutState {
+ private boolean mIsValid;
+ private boolean mIsAlphabetMode;
+ private boolean mIsShiftLocked;
+ private boolean mIsShifted;
+
+ public boolean isValid() {
+ return mIsValid;
+ }
+
+ public void save() {
+ if (mCurrentId == null) {
+ return;
+ }
+ mIsAlphabetMode = isAlphabetMode();
+ if (mIsAlphabetMode) {
+ mIsShiftLocked = isShiftLocked();
+ mIsShifted = !mIsShiftLocked && isShiftedOrShiftLocked();
+ } else {
+ mIsShiftLocked = false;
+ mIsShifted = mCurrentId.equals(mSymbolsShiftedKeyboardId);
+ }
+ mIsValid = true;
+ }
+
+ public KeyboardId getKeyboardId() {
+ if (!mIsValid) return mMainKeyboardId;
+
+ if (mIsAlphabetMode) {
+ return mMainKeyboardId;
+ } else {
+ return mIsShifted ? mSymbolsShiftedKeyboardId : mSymbolsKeyboardId;
+ }
+ }
+
+ public void restore() {
+ if (!mIsValid) return;
+ mIsValid = false;
+
+ if (mIsAlphabetMode) {
+ final boolean isAlphabetMode = isAlphabetMode();
+ final boolean isShiftLocked = isAlphabetMode && isShiftLocked();
+ final boolean isShifted = !isShiftLocked && isShiftedOrShiftLocked();
+ if (mIsShiftLocked != isShiftLocked) {
+ toggleCapsLock();
+ } else if (mIsShifted != isShifted) {
+ onPressShift(false);
+ onReleaseShift(false);
+ }
+ }
+ }
+ }
+
public static KeyboardSwitcher getInstance() {
return sInstance;
}
@@ -114,11 +238,18 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
}
public static void init(LatinIME ims, SharedPreferences prefs) {
- sInstance.mInputMethodService = ims;
- sInstance.mPrefs = prefs;
- sInstance.mSubtypeSwitcher = SubtypeSwitcher.getInstance();
- sInstance.setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs));
- prefs.registerOnSharedPreferenceChangeListener(sInstance);
+ sInstance.initInternal(ims, prefs);
+ }
+
+ private void initInternal(LatinIME ims, SharedPreferences prefs) {
+ mInputMethodService = ims;
+ mPackageName = ims.getPackageName();
+ mResources = ims.getResources();
+ mPrefs = prefs;
+ mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+ mWindowWidthCache = new WindowWidthCache(ims);
+ setContextThemeWrapper(ims, getKeyboardThemeIndex(ims, prefs));
+ prefs.registerOnSharedPreferenceChangeListener(this);
}
private static int getKeyboardThemeIndex(Context context, SharedPreferences prefs) {
@@ -143,83 +274,105 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
}
}
- public void loadKeyboard(EditorInfo attribute, Settings.Values settings) {
- mSwitchState = SWITCH_STATE_ALPHA;
+ public void loadKeyboard(EditorInfo editorInfo, Settings.Values settingsValues) {
try {
- final boolean voiceKeyEnabled = settings.isVoiceKeyEnabled(attribute);
- final boolean voiceKeyOnMain = settings.isVoiceKeyOnMain();
- mMainKeyboardId = getKeyboardId(
- attribute, false, false, voiceKeyEnabled, voiceKeyOnMain);
- mSymbolsKeyboardId = getKeyboardId(
- attribute, true, false, voiceKeyEnabled, voiceKeyOnMain);
- mSymbolsShiftedKeyboardId = getKeyboardId(
- attribute, true, true, voiceKeyEnabled, voiceKeyOnMain);
- setKeyboard(getKeyboard(mMainKeyboardId));
+ mMainKeyboardId = getKeyboardId(editorInfo, false, false, settingsValues);
+ mSymbolsKeyboardId = getKeyboardId(editorInfo, true, false, settingsValues);
+ mSymbolsShiftedKeyboardId = getKeyboardId(editorInfo, true, true, settingsValues);
+ setKeyboard(getKeyboard(mSavedKeyboardState.getKeyboardId()));
} catch (RuntimeException e) {
Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e);
LatinImeLogger.logOnException(mMainKeyboardId.toString(), e);
}
}
+ public KeyboardLayoutState getKeyboardState() {
+ return mSavedKeyboardState;
+ }
+
+ public void onFinishInputView() {
+ mIsAutoCorrectionActive = false;
+ }
+
+ public void onHideWindow() {
+ mIsAutoCorrectionActive = false;
+ }
+
+ public void registerWindowWidth() {
+ mWindowWidthCache.registerWidth();
+ }
+
@SuppressWarnings("unused")
public void onSizeChanged(int w, int h, int oldw, int oldh) {
- final int width = mInputMethodService.getWindow().getWindow().getDecorView().getWidth();
+ // TODO: This hack should be removed when display metric returns a proper width.
+ // Until then, the behavior of KeyboardSwitcher is suboptimal on a device that has a
+ // vertical system navigation bar in landscape screen orientation, for instance.
+ final Configuration conf = mResources.getConfiguration();
+ final int width = mWindowWidthCache.getWidthOnSizeChanged(conf);
// If the window width hasn't fixed yet or keyboard doesn't exist, nothing to do with.
if (width == 0 || mCurrentId == null)
return;
- // The window width is fixed.
- mWindowWidth = width;
- // If this is the first time the {@link KeyboardView} has been shown, no need to reload
- // keyboard.
- if (oldw == 0 && oldh == 0)
- return;
// Reload keyboard with new width.
- final int orientation = mInputMethodService.getResources().getConfiguration().orientation;
- final KeyboardId newId = mCurrentId.cloneWithNewGeometry(orientation, width);
- // If the new keyboard is the same as the current one, no need to reload it.
- if (newId.equals(mCurrentId))
- return;
+ final KeyboardId newId = mCurrentId.cloneWithNewGeometry(conf.orientation, width);
+ mInputMethodService.mHandler.postRestoreKeyboardLayout();
setKeyboard(getKeyboard(newId));
}
- private void setKeyboard(final Keyboard newKeyboard) {
+ private void setKeyboard(final Keyboard keyboard) {
final Keyboard oldKeyboard = mKeyboardView.getKeyboard();
- mKeyboardView.setKeyboard(newKeyboard);
- mCurrentId = newKeyboard.mId;
- final Resources res = mInputMethodService.getResources();
+ mKeyboardView.setKeyboard(keyboard);
+ mCurrentId = keyboard.mId;
+ mSwitchState = getSwitchState(mCurrentId);
+ updateShiftLockState(keyboard);
mKeyboardView.setKeyPreviewPopupEnabled(
- Settings.Values.isKeyPreviewPopupEnabled(mPrefs, res),
- Settings.Values.getKeyPreviewPopupDismissDelay(mPrefs, res));
+ Settings.Values.isKeyPreviewPopupEnabled(mPrefs, mResources),
+ Settings.Values.getKeyPreviewPopupDismissDelay(mPrefs, mResources));
final boolean localeChanged = (oldKeyboard == null)
- || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
+ || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
+ updateShiftState();
+ }
+
+ private int getSwitchState(KeyboardId id) {
+ return id.equals(mMainKeyboardId) ? SWITCH_STATE_ALPHA : SWITCH_STATE_SYMBOL_BEGIN;
+ }
+
+ private void updateShiftLockState(Keyboard keyboard) {
+ if (mCurrentId.equals(mSymbolsShiftedKeyboardId)) {
+ // Symbol keyboard may have an ALT key that has a caps lock style indicator (a.k.a.
+ // sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked()
+ // that takes care of the current keyboard having such ALT key or not.
+ keyboard.setShiftLocked(keyboard.hasShiftLockKey());
+ } else if (mCurrentId.equals(mSymbolsKeyboardId)) {
+ // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
+ // indicator, we need to call setShiftLocked(false).
+ keyboard.setShiftLocked(false);
+ }
}
private LatinKeyboard getKeyboard(KeyboardId id) {
final SoftReference<LatinKeyboard> ref = mKeyboardCache.get(id);
LatinKeyboard keyboard = (ref == null) ? null : ref.get();
if (keyboard == null) {
- final Resources res = mInputMethodService.getResources();
- final Locale savedLocale = Utils.setSystemLocale(res,
- mSubtypeSwitcher.getInputLocale());
-
- keyboard = new LatinKeyboard(mThemeContext, id, id.mWidth);
-
- if (id.mEnableShiftLock) {
- keyboard.enableShiftLock();
+ final Locale savedLocale = Utils.setSystemLocale(
+ mResources, mSubtypeSwitcher.getInputLocale());
+ try {
+ keyboard = new LatinKeyboard.Builder(mThemeContext).load(id).build();
+ } finally {
+ Utils.setSystemLocale(mResources, savedLocale);
}
-
mKeyboardCache.put(id, new SoftReference<LatinKeyboard>(keyboard));
- if (DEBUG_CACHE)
+
+ if (DEBUG_CACHE) {
Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": "
+ ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
-
- Utils.setSystemLocale(res, savedLocale);
+ }
} else if (DEBUG_CACHE) {
Log.d(TAG, "keyboard cache size=" + mKeyboardCache.size() + ": HIT id=" + id);
}
keyboard.onAutoCorrectionStateChanged(mIsAutoCorrectionActive);
+ keyboard.setShiftLocked(false);
keyboard.setShifted(false);
// If the cached keyboard had been switched to another keyboard while the language was
// displayed on its spacebar, it might have had arbitrary text fade factor. In such case,
@@ -229,28 +382,16 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
return keyboard;
}
- private static boolean hasSettingsKey(SharedPreferences prefs, Context context,
- EditorInfo attribute) {
- return getSettingsKeyMode(prefs, context)
- && !Utils.inPrivateImeOptions(context.getPackageName(),
- LatinIME.IME_OPTION_NO_SETTINGS_KEY, attribute);
- }
-
- private KeyboardId getKeyboardId(EditorInfo attribute, final boolean isSymbols,
- final boolean isShift, final boolean voiceKeyEnabled, final boolean voiceKeyOnMain) {
- final int mode = Utils.getKeyboardMode(attribute);
- final boolean hasVoiceKey = voiceKeyEnabled && (isSymbols != voiceKeyOnMain);
+ private KeyboardId getKeyboardId(EditorInfo editorInfo, final boolean isSymbols,
+ final boolean isShift, Settings.Values settingsValues) {
+ final int mode = Utils.getKeyboardMode(editorInfo);
final int xmlId;
- final boolean enableShiftLock;
-
switch (mode) {
case KeyboardId.MODE_PHONE:
xmlId = (isSymbols && isShift) ? R.xml.kbd_phone_shift : R.xml.kbd_phone;
- enableShiftLock = true;
break;
case KeyboardId.MODE_NUMBER:
xmlId = R.xml.kbd_number;
- enableShiftLock = false;
break;
default:
if (isSymbols) {
@@ -258,24 +399,28 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
} else {
xmlId = R.xml.kbd_qwerty;
}
- enableShiftLock = true;
break;
}
- final boolean hasSettingsKey = hasSettingsKey(mPrefs, mInputMethodService, attribute);
- final int f2KeyMode = getF2KeyMode(mPrefs, mInputMethodService, attribute);
- final boolean clobberSettingsKey = Utils.inPrivateImeOptions(
- mInputMethodService.getPackageName(), LatinIME.IME_OPTION_NO_SETTINGS_KEY,
- attribute);
- final Resources res = mInputMethodService.getResources();
- final int orientation = res.getConfiguration().orientation;
- if (mWindowWidth == 0)
- mWindowWidth = res.getDisplayMetrics().widthPixels;
- final Locale locale = mSubtypeSwitcher.getInputLocale();
+ final boolean settingsKeyEnabled = settingsValues.isSettingsKeyEnabled(editorInfo);
+ final boolean noMicrophone = Utils.inPrivateImeOptions(
+ mPackageName, LatinIME.IME_OPTION_NO_MICROPHONE, editorInfo)
+ || Utils.inPrivateImeOptions(
+ null, LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, editorInfo);
+ final boolean voiceKeyEnabled = settingsValues.isVoiceKeyEnabled(editorInfo)
+ && !noMicrophone;
+ final boolean voiceKeyOnMain = settingsValues.isVoiceKeyOnMain();
+ final boolean noSettingsKey = Utils.inPrivateImeOptions(
+ mPackageName, LatinIME.IME_OPTION_NO_SETTINGS_KEY, editorInfo);
+ final boolean hasSettingsKey = settingsKeyEnabled && !noSettingsKey;
+ final int f2KeyMode = getF2KeyMode(settingsKeyEnabled, noSettingsKey);
+ final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != voiceKeyOnMain);
+ final Configuration conf = mResources.getConfiguration();
+
return new KeyboardId(
- res.getResourceEntryName(xmlId), xmlId, locale, orientation, mWindowWidth,
- mode, attribute, hasSettingsKey, f2KeyMode, clobberSettingsKey, voiceKeyEnabled,
- hasVoiceKey, enableShiftLock);
+ mResources.getResourceEntryName(xmlId), xmlId, mSubtypeSwitcher.getInputLocale(),
+ conf.orientation, mWindowWidthCache.getWidth(conf), mode, editorInfo,
+ hasSettingsKey, f2KeyMode, noSettingsKey, voiceKeyEnabled, hasShortcutKey);
}
public int getKeyboardMode() {
@@ -398,11 +543,12 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
}
private void setAutomaticTemporaryUpperCase() {
- LatinKeyboard latinKeyboard = getLatinKeyboard();
- if (latinKeyboard != null) {
- latinKeyboard.setAutomaticTemporaryUpperCase();
- mKeyboardView.invalidateAllKeys();
+ if (mKeyboardView == null) return;
+ final Keyboard keyboard = mKeyboardView.getKeyboard();
+ if (keyboard != null) {
+ keyboard.setAutomaticTemporaryUpperCase();
}
+ mKeyboardView.invalidateAllKeys();
}
/**
@@ -414,7 +560,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
Log.d(TAG, "updateShiftState:"
+ " autoCaps=" + mInputMethodService.getCurrentAutoCapsState()
+ " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
- + " shiftKeyState=" + shiftKeyState);
+ + " shiftKeyState=" + shiftKeyState
+ + " isAlphabetMode=" + isAlphabetMode()
+ + " isShiftLocked=" + isShiftLocked());
if (isAlphabetMode()) {
if (!isShiftLocked() && !shiftKeyState.isIgnoring()) {
if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) {
@@ -568,27 +716,12 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
if (mCurrentId.equals(mSymbolsKeyboardId)
|| !mCurrentId.equals(mSymbolsShiftedKeyboardId)) {
keyboard = getKeyboard(mSymbolsShiftedKeyboardId);
- // Symbol keyboard may have an ALT key that has a caps lock style indicator (a.k.a.
- // sticky shift key). To show or dismiss the indicator, we need to call setShiftLocked()
- // that takes care of the current keyboard having such ALT key or not.
- keyboard.setShiftLocked(hasStickyShiftKey(keyboard));
} else {
keyboard = getKeyboard(mSymbolsKeyboardId);
- // Symbol keyboard has an ALT key that has a caps lock style indicator. To disable the
- // indicator, we need to call setShiftLocked(false).
- keyboard.setShiftLocked(false);
}
setKeyboard(keyboard);
}
- private static boolean hasStickyShiftKey(Keyboard keyboard) {
- for (final Key shiftKey : keyboard.getShiftKeys()) {
- if (shiftKey.mSticky)
- return true;
- }
- return false;
- }
-
public boolean isInMomentarySwitchState() {
return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
|| mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
@@ -605,10 +738,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
private void toggleKeyboardMode() {
if (mCurrentId.equals(mMainKeyboardId)) {
setKeyboard(getKeyboard(mSymbolsKeyboardId));
- mSwitchState = SWITCH_STATE_SYMBOL_BEGIN;
} else {
setKeyboard(getKeyboard(mMainKeyboardId));
- mSwitchState = SWITCH_STATE_ALPHA;
}
}
@@ -771,9 +902,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (PREF_KEYBOARD_LAYOUT.equals(key)) {
- final int layoutId = getKeyboardThemeIndex(mInputMethodService, sharedPreferences);
- postSetInputView(createInputView(layoutId, false));
- } else if (Settings.PREF_SETTINGS_KEY.equals(key)) {
+ final int themeIndex = getKeyboardThemeIndex(mInputMethodService, sharedPreferences);
+ postSetInputView(createInputView(themeIndex, false));
+ } else if (Settings.PREF_SHOW_SETTINGS_KEY.equals(key)) {
postSetInputView(createInputView(mThemeIndex, true));
}
}
@@ -791,41 +922,18 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
}
}
- private static boolean getSettingsKeyMode(SharedPreferences prefs, Context context) {
- final Resources res = context.getResources();
- final boolean showSettingsKeyOption = res.getBoolean(
- R.bool.config_enable_show_settings_key_option);
- if (showSettingsKeyOption) {
- final String settingsKeyMode = prefs.getString(Settings.PREF_SETTINGS_KEY,
- res.getString(DEFAULT_SETTINGS_KEY_MODE));
- // We show the settings key when 1) SETTINGS_KEY_MODE_ALWAYS_SHOW or
- // 2) SETTINGS_KEY_MODE_AUTO and there are two or more enabled IMEs on the system
- if (settingsKeyMode.equals(res.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW))
- || (settingsKeyMode.equals(res.getString(SETTINGS_KEY_MODE_AUTO))
- && Utils.hasMultipleEnabledIMEsOrSubtypes(
- (InputMethodManagerCompatWrapper.getInstance())))) {
- return true;
- }
- return false;
- }
- // If the show settings key option is disabled, we always try showing the settings key.
- return true;
- }
-
- private static int getF2KeyMode(SharedPreferences prefs, Context context,
- EditorInfo attribute) {
- final boolean clobberSettingsKey = Utils.inPrivateImeOptions(
- context.getPackageName(), LatinIME.IME_OPTION_NO_SETTINGS_KEY, attribute);
- final Resources res = context.getResources();
- final String settingsKeyMode = prefs.getString(Settings.PREF_SETTINGS_KEY,
- res.getString(DEFAULT_SETTINGS_KEY_MODE));
- if (settingsKeyMode.equals(res.getString(SETTINGS_KEY_MODE_AUTO))) {
- return clobberSettingsKey ? KeyboardId.F2KEY_MODE_SHORTCUT_IME
- : KeyboardId.F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS;
- } else if (settingsKeyMode.equals(res.getString(SETTINGS_KEY_MODE_ALWAYS_SHOW))) {
- return clobberSettingsKey ? KeyboardId.F2KEY_MODE_NONE : KeyboardId.F2KEY_MODE_SETTINGS;
- } else { // SETTINGS_KEY_MODE_ALWAYS_HIDE
+ private static int getF2KeyMode(boolean settingsKeyEnabled, boolean noSettingsKey) {
+ if (noSettingsKey) {
+ // Never shows the Settings key
return KeyboardId.F2KEY_MODE_SHORTCUT_IME;
}
+
+ if (settingsKeyEnabled) {
+ return KeyboardId.F2KEY_MODE_SETTINGS;
+ } else {
+ // It should be alright to fall back to the Settings key on 7-inch layouts
+ // even when the Settings key is not explicitly enabled.
+ return KeyboardId.F2KEY_MODE_SHORTCUT_IME_OR_SETTINGS;
+ }
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index fc47713b8..bc021a690 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -35,6 +35,7 @@ import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import com.android.inputmethod.compat.FrameLayoutCompatUtils;
@@ -82,6 +83,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// HORIZONTAL ELLIPSIS "...", character for popup hint.
private static final String POPUP_HINT_CHAR = "\u2026";
+ // Margin between the label and the icon on a key that has both of them.
+ // Specified by the fraction of the key width.
+ // TODO: Use resource parameter for this value.
+ private static final float LABEL_ICON_MARGIN = 0.05f;
+
// Main keyboard
private Keyboard mKeyboard;
private final KeyDrawParams mKeyDrawParams;
@@ -349,15 +355,18 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
* @param keyboard the keyboard to display in this view
*/
public void setKeyboard(Keyboard keyboard) {
- // Remove any pending messages, except dismissing preview
+ // Remove any pending dismissing preview
mDrawingHandler.cancelAllShowKeyPreviews();
+ if (mKeyboard != null) {
+ PointerTracker.dismissAllKeyPreviews();
+ }
mKeyboard = keyboard;
LatinImeLogger.onSetKeyboard(keyboard);
requestLayout();
mDirtyRect.set(0, 0, getWidth(), getHeight());
mBufferNeedsUpdate = true;
invalidateAllKeys();
- final int keyHeight = keyboard.getRowHeight() - keyboard.getVerticalGap();
+ final int keyHeight = keyboard.mDefaultRowHeight - keyboard.mVerticalGap;
mKeyDrawParams.updateKeyHeight(keyHeight);
mKeyPreviewDrawParams.updateKeyHeight(keyHeight);
}
@@ -396,7 +405,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mKeyboard != null) {
// The main keyboard expands to the display width.
- final int height = mKeyboard.getKeyboardHeight() + getPaddingTop() + getPaddingBottom();
+ final int height = mKeyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(widthMeasureSpec, height);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -443,15 +452,16 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
+ getPaddingLeft();
final int keyDrawY = mInvalidatedKey.mY + getPaddingTop();
canvas.translate(keyDrawX, keyDrawY);
- onBufferDrawKey(mInvalidatedKey, canvas, mPaint, params, isManualTemporaryUpperCase);
+ onBufferDrawKey(mInvalidatedKey, mKeyboard, canvas, mPaint, params,
+ isManualTemporaryUpperCase);
canvas.translate(-keyDrawX, -keyDrawY);
} else {
// Draw all keys.
- for (final Key key : mKeyboard.getKeys()) {
+ for (final Key key : mKeyboard.mKeys) {
final int keyDrawX = key.mX + key.mVisualInsetsLeft + getPaddingLeft();
final int keyDrawY = key.mY + getPaddingTop();
canvas.translate(keyDrawX, keyDrawY);
- onBufferDrawKey(key, canvas, mPaint, params, isManualTemporaryUpperCase);
+ onBufferDrawKey(key, mKeyboard, canvas, mPaint, params, isManualTemporaryUpperCase);
canvas.translate(-keyDrawX, -keyDrawY);
}
}
@@ -470,8 +480,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
return false;
}
- private static void onBufferDrawKey(final Key key, final Canvas canvas, Paint paint,
- KeyDrawParams params, boolean isManualTemporaryUpperCase) {
+ private static void onBufferDrawKey(final Key key, final Keyboard keyboard, final Canvas canvas,
+ Paint paint, KeyDrawParams params, boolean isManualTemporaryUpperCase) {
final boolean debugShowAlign = LatinImeLogger.sVISUALDEBUG;
// Draw key background.
final int bgWidth = key.mWidth - key.mVisualInsetsLeft - key.mVisualInsetsRight
@@ -504,10 +514,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
// Draw key label.
+ final Drawable icon = key.getIcon();
float positionX = centerX;
if (key.mLabel != null) {
// Switch the character to uppercase if shift is pressed
- final CharSequence label = key.getCaseAdjustedLabel();
+ final CharSequence label = keyboard.adjustLabelCase(key.mLabel);
// For characters, use large font. For labels like "Done", use smaller font.
paint.setTypeface(key.selectTypeface(params.mKeyTextStyle));
final int labelSize = key.selectTextSize(params.mKeyLetterSize,
@@ -520,16 +531,27 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
final float baseline = centerY + labelCharHeight / 2;
// Horizontal label text alignment
- if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_LEFT) != 0) {
+ float labelWidth = 0;
+ if (key.isAlignLeft()) {
positionX = (int)params.mKeyLabelHorizontalPadding;
paint.setTextAlign(Align.LEFT);
- } else if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_RIGHT) != 0) {
+ } else if (key.isAlignRight()) {
positionX = keyWidth - (int)params.mKeyLabelHorizontalPadding;
paint.setTextAlign(Align.RIGHT);
- } else if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_LEFT_OF_CENTER) != 0) {
+ } else if (key.isAlignLeftOfCenter()) {
// TODO: Parameterise this?
positionX = centerX - labelCharWidth * 7 / 4;
paint.setTextAlign(Align.LEFT);
+ } else if (key.hasLabelWithIconLeft() && icon != null) {
+ labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ + (int)(LABEL_ICON_MARGIN * keyWidth);
+ positionX = centerX + labelWidth / 2;
+ paint.setTextAlign(Align.RIGHT);
+ } else if (key.hasLabelWithIconRight() && icon != null) {
+ labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth()
+ + (int)(LABEL_ICON_MARGIN * keyWidth);
+ positionX = centerX - labelWidth / 2;
+ paint.setTextAlign(Align.LEFT);
} else {
positionX = centerX;
paint.setTextAlign(Align.CENTER);
@@ -551,6 +573,19 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
// Turn off drop shadow
paint.setShadowLayer(0, 0, 0, 0);
+ if (icon != null) {
+ final int iconWidth = icon.getIntrinsicWidth();
+ final int iconHeight = icon.getIntrinsicHeight();
+ final int iconY = (keyHeight - iconHeight) / 2;
+ if (key.hasLabelWithIconLeft()) {
+ final int iconX = (int)(centerX - labelWidth / 2);
+ drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
+ } else if (key.hasLabelWithIconRight()) {
+ final int iconX = (int)(centerX + labelWidth / 2 - iconWidth);
+ drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight);
+ }
+ }
+
if (debugShowAlign) {
final Paint line = new Paint();
drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line);
@@ -604,16 +639,15 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
// Draw key icon.
- final Drawable icon = key.getIcon();
if (key.mLabel == null && icon != null) {
final int iconWidth = icon.getIntrinsicWidth();
final int iconHeight = icon.getIntrinsicHeight();
final int iconX, alignX;
final int iconY = (keyHeight - iconHeight) / 2;
- if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_LEFT) != 0) {
+ if (key.isAlignLeft()) {
iconX = (int)params.mKeyLabelHorizontalPadding;
alignX = iconX;
- } else if ((key.mLabelOption & Key.LABEL_OPTION_ALIGN_RIGHT) != 0) {
+ } else if (key.isAlignRight()) {
iconX = keyWidth - (int)params.mKeyLabelHorizontalPadding - iconWidth;
alignX = iconX + iconWidth;
} else { // Align center
@@ -630,7 +664,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
}
// Draw popup hint "..." at the bottom right corner of the key.
- if (key.hasPopupHint()) {
+ if ((key.hasPopupHint() && key.mPopupCharacters != null && key.mPopupCharacters.length > 0)
+ || key.needsSpecialPopupHint()) {
paint.setTextSize(params.mKeyHintLetterSize);
paint.setColor(params.mKeyHintLabelColor);
paint.setTextAlign(Align.CENTER);
@@ -693,6 +728,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
return width;
}
+ private static float getLabelWidth(CharSequence label, Paint paint) {
+ paint.getTextBounds(label.toString(), 0, label.length(), sTextBounds);
+ return sTextBounds.width();
+ }
+
private static void drawIcon(Canvas canvas, Drawable icon, int x, int y, int width,
int height) {
canvas.translate(x, y);
@@ -701,7 +741,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
canvas.translate(-x, -y);
}
- private static void drawHorizontalLine(Canvas canvas, float y, float w, int color, Paint paint) {
+ private static void drawHorizontalLine(Canvas canvas, float y, float w, int color,
+ Paint paint) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1.0f);
paint.setColor(color);
@@ -754,22 +795,21 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
@Override
public void dismissKeyPreview(PointerTracker tracker) {
- if (mShowKeyPreviewPopup) {
- mDrawingHandler.cancelShowKeyPreview(tracker);
- mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
- }
+ mDrawingHandler.cancelShowKeyPreview(tracker);
+ mDrawingHandler.dismissKeyPreview(mDelayAfterPreview, tracker);
}
private void addKeyPreview(TextView keyPreview) {
if (mPreviewPlacer == null) {
- mPreviewPlacer = FrameLayoutCompatUtils.getPlacer(
- (ViewGroup)getRootView().findViewById(android.R.id.content));
+ mPreviewPlacer = new RelativeLayout(getContext());
+ final ViewGroup windowContentView =
+ (ViewGroup)getRootView().findViewById(android.R.id.content);
+ windowContentView.addView(mPreviewPlacer);
}
- final ViewGroup placer = mPreviewPlacer;
- placer.addView(keyPreview, FrameLayoutCompatUtils.newLayoutParam(placer, 0, 0));
+ mPreviewPlacer.addView(
+ keyPreview, FrameLayoutCompatUtils.newLayoutParam(mPreviewPlacer, 0, 0));
}
- // TODO: Introduce minimum duration for displaying key previews
private void showKey(final int keyIndex, PointerTracker tracker) {
final TextView previewText = tracker.getKeyPreviewText();
// If the key preview has no parent view yet, add it to the ViewGroup which can place
@@ -800,7 +840,7 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, params.mPreviewTextSize);
previewText.setTypeface(params.mKeyTextStyle);
}
- previewText.setText(key.getCaseAdjustedLabel());
+ previewText.setText(mKeyboard.adjustLabelCase(key.mLabel));
} else {
final Drawable previewIcon = key.getPreviewIcon();
previewText.setCompoundDrawables(null, null, null,
@@ -888,5 +928,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy {
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
closing();
+ if (mPreviewPlacer != null) {
+ mPreviewPlacer.removeAllViews();
+ }
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index 3c27129ec..1b6f57b92 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -31,13 +31,16 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
+import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
+import com.android.inputmethod.keyboard.internal.KeyboardParams;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.Utils;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.List;
import java.util.Locale;
// TODO: We should remove this class
@@ -58,6 +61,7 @@ public class LatinKeyboard extends Keyboard {
private float mSpacebarTextFadeFactor = 0.0f;
private final HashMap<Integer, SoftReference<BitmapDrawable>> mSpaceDrawableCache =
new HashMap<Integer, SoftReference<BitmapDrawable>>();
+ private final boolean mIsSpacebarTriggeringPopupByLongPress;
/* Shortcut key and its icons if available */
private final Key mShortcutKey;
@@ -73,33 +77,20 @@ public class LatinKeyboard extends Keyboard {
private static final String SMALL_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "small";
private static final String MEDIUM_TEXT_SIZE_OF_LANGUAGE_ON_SPACEBAR = "medium";
- public LatinKeyboard(Context context, KeyboardId id, int width) {
- super(context, id.getXmlId(), id, width);
+ private LatinKeyboard(Context context, LatinKeyboardParams params) {
+ super(params);
mRes = context.getResources();
mTheme = context.getTheme();
- final List<Key> keys = getKeys();
- int spaceKeyIndex = -1;
- int shortcutKeyIndex = -1;
- final int keyCount = keys.size();
- for (int index = 0; index < keyCount; index++) {
- // For now, assuming there are up to one space key and one shortcut key respectively.
- switch (keys.get(index).mCode) {
- case CODE_SPACE:
- spaceKeyIndex = index;
- break;
- case CODE_SHORTCUT:
- shortcutKeyIndex = index;
- break;
- }
- }
-
// The index of space key is available only after Keyboard constructor has finished.
- mSpaceKey = (spaceKeyIndex >= 0) ? keys.get(spaceKeyIndex) : null;
+ mSpaceKey = params.mSpaceKey;
mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon() : null;
- mShortcutKey = (shortcutKeyIndex >= 0) ? keys.get(shortcutKeyIndex) : null;
+ mShortcutKey = params.mShortcutKey;
mEnabledShortcutIcon = (mShortcutKey != null) ? mShortcutKey.getIcon() : null;
+ final int longPressSpaceKeyTimeout =
+ mRes.getInteger(R.integer.config_long_press_space_key_timeout);
+ mIsSpacebarTriggeringPopupByLongPress = (longPressSpaceKeyTimeout > 0);
final TypedArray a = context.obtainStyledAttributes(
null, R.styleable.LatinKeyboard, R.attr.latinKeyboardStyle, R.style.LatinKeyboard);
@@ -114,6 +105,42 @@ public class LatinKeyboard extends Keyboard {
a.recycle();
}
+ private static class LatinKeyboardParams extends KeyboardParams {
+ public Key mSpaceKey = null;
+ public Key mShortcutKey = null;
+
+ @Override
+ public void onAddKey(Key key) {
+ super.onAddKey(key);
+
+ switch (key.mCode) {
+ case Keyboard.CODE_SPACE:
+ mSpaceKey = key;
+ break;
+ case Keyboard.CODE_SHORTCUT:
+ mShortcutKey = key;
+ break;
+ }
+ }
+ }
+
+ public static class Builder extends KeyboardBuilder<LatinKeyboardParams> {
+ public Builder(Context context) {
+ super(context, new LatinKeyboardParams());
+ }
+
+ @Override
+ public Builder load(KeyboardId id) {
+ super.load(id);
+ return this;
+ }
+
+ @Override
+ public LatinKeyboard build() {
+ return new LatinKeyboard(mContext, mParams);
+ }
+ }
+
public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboardView view) {
mSpacebarTextFadeFactor = fadeFactor;
updateSpacebarForLocale(false);
@@ -158,8 +185,13 @@ public class LatinKeyboard extends Keyboard {
}
private void updateSpacebarForLocale(boolean isAutoCorrection) {
- if (mSpaceKey == null)
- return;
+ if (mSpaceKey == null) return;
+ final InputMethodManagerCompatWrapper imm = InputMethodManagerCompatWrapper.getInstance();
+ if (imm == null) return;
+ // The "..." popup hint for triggering something by a long-pressing the spacebar
+ final boolean shouldShowInputMethodPicker = mIsSpacebarTriggeringPopupByLongPress
+ && Utils.hasMultipleEnabledIMEsOrSubtypes(imm, true /* include aux subtypes */);
+ mSpaceKey.setNeedsSpecialPopupHint(shouldShowInputMethodPicker);
// If application locales are explicitly selected.
if (mSubtypeSwitcher.needsToDisplayLanguage()) {
mSpaceKey.setIcon(getSpaceDrawable(
@@ -294,8 +326,8 @@ public class LatinKeyboard extends Keyboard {
@Override
public int[] getNearestKeys(int x, int y) {
// Avoid dead pixels at edges of the keyboard
- return super.getNearestKeys(Math.max(0, Math.min(x, getMinWidth() - 1)),
- Math.max(0, Math.min(y, getHeight() - 1)));
+ return super.getNearestKeys(Math.max(0, Math.min(x, mOccupiedWidth - 1)),
+ Math.max(0, Math.min(y, mOccupiedHeight - 1)));
}
public static int getTextSizeFromTheme(Theme theme, int style, int defValue) {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
index cb1a2b782..12aadcb5c 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardBaseView.java
@@ -281,15 +281,12 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke
*/
@Override
public void setKeyboard(Keyboard keyboard) {
- if (getKeyboard() != null) {
- PointerTracker.dismissAllKeyPreviews();
- }
// Remove any pending messages, except dismissing preview
mKeyTimerHandler.cancelKeyTimers();
super.setKeyboard(keyboard);
mKeyDetector.setKeyboard(
keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection);
- mKeyDetector.setProximityThreshold(keyboard.getMostCommonKeyWidth());
+ mKeyDetector.setProximityThreshold(keyboard.mMostCommonKeyWidth);
PointerTracker.setKeyDetector(mKeyDetector);
mPopupPanelCache.clear();
}
@@ -360,7 +357,7 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke
(PopupMiniKeyboardView)container.findViewById(R.id.mini_keyboard_view);
final Keyboard parentKeyboard = getKeyboard();
final Keyboard miniKeyboard = new MiniKeyboardBuilder(
- this, parentKeyboard.getPopupKeyboardResId(), parentKey, parentKeyboard).build();
+ this, parentKeyboard.mPopupKeyboardResId, parentKey, parentKeyboard).build();
miniKeyboardView.setKeyboard(miniKeyboard);
container.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST),
@@ -567,9 +564,9 @@ public class LatinKeyboardBaseView extends KeyboardView implements PointerTracke
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
+ // Drop non-hover touch events when touch exploration is enabled.
if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) {
- return AccessibleKeyboardViewProxy.getInstance().dispatchTouchEvent(event)
- || super.dispatchTouchEvent(event);
+ return false;
}
return super.dispatchTouchEvent(event);
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index c404a5dfb..dad37e728 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -23,6 +23,7 @@ import android.util.Log;
import android.view.MotionEvent;
import com.android.inputmethod.deprecated.VoiceProxy;
+import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.Utils;
@@ -53,55 +54,60 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
@Override
public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) {
- LatinKeyboard latinKeyboard = getLatinKeyboard();
- if (latinKeyboard != null
- && (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard())) {
- // Phone and number keyboard never shows popup preview (except language switch).
- super.setKeyPreviewPopupEnabled(false, delay);
- } else {
- super.setKeyPreviewPopupEnabled(previewEnabled, delay);
+ final Keyboard keyboard = getKeyboard();
+ if (keyboard instanceof LatinKeyboard) {
+ final LatinKeyboard latinKeyboard = (LatinKeyboard)keyboard;
+ if (latinKeyboard.isPhoneKeyboard() || latinKeyboard.isNumberKeyboard()) {
+ // Phone and number keyboard never shows popup preview.
+ super.setKeyPreviewPopupEnabled(false, delay);
+ return;
+ }
}
+ super.setKeyPreviewPopupEnabled(previewEnabled, delay);
}
@Override
public void setKeyboard(Keyboard newKeyboard) {
super.setKeyboard(newKeyboard);
// One-seventh of the keyboard width seems like a reasonable threshold
- mJumpThresholdSquare = newKeyboard.getMinWidth() / 7;
- mJumpThresholdSquare *= mJumpThresholdSquare;
- }
-
- private LatinKeyboard getLatinKeyboard() {
- Keyboard keyboard = getKeyboard();
- if (keyboard instanceof LatinKeyboard) {
- return (LatinKeyboard)keyboard;
- } else {
- return null;
- }
+ final int jumpThreshold = newKeyboard.mOccupiedWidth / 7;
+ mJumpThresholdSquare = jumpThreshold * jumpThreshold;
}
public void setSpacebarTextFadeFactor(float fadeFactor, LatinKeyboard oldKeyboard) {
- final LatinKeyboard currentKeyboard = getLatinKeyboard();
+ final Keyboard keyboard = getKeyboard();
// We should not set text fade factor to the keyboard which does not display the language on
// its spacebar.
- if (currentKeyboard != null && currentKeyboard == oldKeyboard)
- currentKeyboard.setSpacebarTextFadeFactor(fadeFactor, this);
+ if (keyboard instanceof LatinKeyboard && keyboard == oldKeyboard) {
+ ((LatinKeyboard)keyboard).setSpacebarTextFadeFactor(fadeFactor, this);
+ }
}
@Override
protected boolean onLongPress(Key key, PointerTracker tracker) {
- int primaryCode = key.mCode;
+ final int primaryCode = key.mCode;
+ final Keyboard keyboard = getKeyboard();
+ if (keyboard instanceof LatinKeyboard) {
+ final LatinKeyboard latinKeyboard = (LatinKeyboard) keyboard;
+ if (primaryCode == Keyboard.CODE_DIGIT0 && latinKeyboard.isPhoneKeyboard()) {
+ tracker.onLongPressed();
+ // Long pressing on 0 in phone number keypad gives you a '+'.
+ return invokeOnKey(Keyboard.CODE_PLUS);
+ }
+ if (primaryCode == Keyboard.CODE_SHIFT && latinKeyboard.isAlphaKeyboard()) {
+ tracker.onLongPressed();
+ return invokeOnKey(Keyboard.CODE_CAPSLOCK);
+ }
+ }
if (primaryCode == Keyboard.CODE_SETTINGS || primaryCode == Keyboard.CODE_SPACE) {
- tracker.onLongPressed();
// Both long pressing settings key and space key invoke IME switcher dialog.
- return invokeOnKey(Keyboard.CODE_SETTINGS_LONGPRESS);
- } else if (primaryCode == '0' && getLatinKeyboard().isPhoneKeyboard()) {
- tracker.onLongPressed();
- // Long pressing on 0 in phone number keypad gives you a '+'.
- return invokeOnKey('+');
- } else if (primaryCode == Keyboard.CODE_SHIFT) {
- tracker.onLongPressed();
- return invokeOnKey(Keyboard.CODE_CAPSLOCK);
+ if (getKeyboardActionListener().onCustomRequest(
+ LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
+ tracker.onLongPressed();
+ return true;
+ } else {
+ return super.onLongPress(key, tracker);
+ }
} else {
return super.onLongPress(key, tracker);
}
@@ -194,7 +200,7 @@ public class LatinKeyboardView extends LatinKeyboardBaseView {
@Override
public boolean onTouchEvent(MotionEvent me) {
- if (getLatinKeyboard() == null) return true;
+ if (getKeyboard() == null) return true;
// If there was a sudden jump, return without processing the actual motion event.
if (handleSuddenJump(me)) {
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
index 95e32755e..08e7d7e19 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboard.java
@@ -16,30 +16,14 @@
package com.android.inputmethod.keyboard;
-import android.content.Context;
+import com.android.inputmethod.keyboard.internal.MiniKeyboardBuilder.MiniKeyboardParams;
public class MiniKeyboard extends Keyboard {
- private int mDefaultKeyCoordX;
+ private final int mDefaultKeyCoordX;
- public MiniKeyboard(Context context, int xmlLayoutResId, Keyboard parentKeyboard) {
- super(context, xmlLayoutResId, parentKeyboard.mId.cloneAsMiniKeyboard(),
- parentKeyboard.getMinWidth());
- // HACK: Current mini keyboard design totally relies on the 9-patch padding about horizontal
- // and vertical key spacing. To keep the visual of mini keyboard as is, these hacks are
- // needed to keep having the same horizontal and vertical key spacing.
- setHorizontalGap(0);
- setVerticalGap(parentKeyboard.getVerticalGap() / 2);
-
- // TODO: When we have correctly padded key background 9-patch drawables for mini keyboard,
- // revert the above hacks and uncomment the following lines.
- //setHorizontalGap(parentKeyboard.getHorizontalGap());
- //setVerticalGap(parentKeyboard.getVerticalGap());
-
- setRtlKeyboard(parentKeyboard.isRtlKeyboard());
- }
-
- public void setDefaultCoordX(int pos) {
- mDefaultKeyCoordX = pos;
+ public MiniKeyboard(MiniKeyboardParams params) {
+ super(params);
+ mDefaultKeyCoordX = params.getDefaultKeyCoordX() + params.mDefaultKeyWidth / 2;
}
public int getDefaultCoordX() {
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
index 1ec0dda15..84bd44c30 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
@@ -37,7 +37,7 @@ public class MiniKeyboardKeyDetector extends KeyDetector {
@Override
public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
- final List<Key> keys = getKeyboard().getKeys();
+ final List<Key> keys = getKeyboard().mKeys;
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 6b6a4538f..1f8119a0f 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -151,6 +151,8 @@ public class PointerTracker {
public void onTextInput(CharSequence text) {}
@Override
public void onCancelInput() {}
+ @Override
+ public boolean onCustomRequest(int requestCode) { return false; }
};
public static void init(boolean hasDistinctMultitouch, Context context) {
@@ -285,8 +287,8 @@ public class PointerTracker {
public void setKeyDetectorInner(KeyDetector keyDetector) {
mKeyDetector = keyDetector;
mKeyboard = keyDetector.getKeyboard();
- mKeys = mKeyboard.getKeys();
- final int keyQuarterWidth = mKeyboard.getKeyWidth() / 4;
+ mKeys = mKeyboard.mKeys;
+ final int keyQuarterWidth = mKeyboard.mMostCommonKeyWidth / 4;
mKeyQuarterWidthSquared = keyQuarterWidth * keyQuarterWidth;
}
@@ -329,36 +331,28 @@ public class PointerTracker {
return mKeyDetector.getKeyIndexAndNearbyCodes(x, y, null);
}
- public boolean isSpaceKey(int keyIndex) {
- Key key = getKey(keyIndex);
- return key != null && key.mCode == Keyboard.CODE_SPACE;
- }
-
private void setReleasedKeyGraphics(int keyIndex) {
mDrawingProxy.dismissKeyPreview(this);
final Key key = getKey(keyIndex);
- if (key != null) {
+ if (key != null && key.isEnabled()) {
key.onReleased();
mDrawingProxy.invalidateKey(key);
}
}
private void setPressedKeyGraphics(int keyIndex) {
- if (isKeyPreviewRequired(keyIndex)) {
- mDrawingProxy.showKeyPreview(keyIndex, this);
- }
final Key key = getKey(keyIndex);
if (key != null && key.isEnabled()) {
+ if (isKeyPreviewRequired(key)) {
+ mDrawingProxy.showKeyPreview(keyIndex, this);
+ }
key.onPressed();
mDrawingProxy.invalidateKey(key);
}
}
// The modifier key, such as shift key, should not show its key preview.
- private boolean isKeyPreviewRequired(int keyIndex) {
- final Key key = getKey(keyIndex);
- if (key == null || !key.isEnabled())
- return false;
+ private static boolean isKeyPreviewRequired(Key key) {
final int code = key.mCode;
if (isModifierCode(code) || code == Keyboard.CODE_DELETE
|| code == Keyboard.CODE_ENTER || code == Keyboard.CODE_SPACE)
diff --git a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
index 2741ee80b..fb932e3e8 100644
--- a/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/PopupMiniKeyboardView.java
@@ -79,6 +79,8 @@ public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel {
public void onRelease(int primaryCode, boolean withSliding) {
mParentKeyboardView.getKeyboardActionListener().onRelease(primaryCode, withSliding);
}
+ @Override
+ public boolean onCustomRequest(int requestCode) { return false; }
};
public PopupMiniKeyboardView(Context context, AttributeSet attrs) {
@@ -108,8 +110,8 @@ public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final Keyboard keyboard = getKeyboard();
if (keyboard != null) {
- final int width = keyboard.getMinWidth() + getPaddingLeft() + getPaddingRight();
- final int height = keyboard.getKeyboardHeight() + getPaddingTop() + getPaddingBottom();
+ final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
+ final int height = keyboard.mOccupiedHeight + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(width, height);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -170,9 +172,9 @@ public class PopupMiniKeyboardView extends KeyboardView implements PopupPanel {
final int miniKeyboardLeft = pointX - miniKeyboard.getDefaultCoordX()
+ parentKeyboardView.getPaddingLeft();
final int x = Math.max(0, Math.min(miniKeyboardLeft,
- parentKeyboardView.getWidth() - miniKeyboard.getMinWidth()))
+ parentKeyboardView.getWidth() - miniKeyboard.mOccupiedWidth))
- container.getPaddingLeft() + mCoordinates[0];
- final int y = pointY - parentKeyboard.getVerticalGap()
+ final int y = pointY - parentKeyboard.mVerticalGap
- (container.getMeasuredHeight() - container.getPaddingBottom())
+ parentKeyboardView.getPaddingTop() + mCoordinates[1];
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index aadedc69d..7190b051d 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -17,8 +17,10 @@
package com.android.inputmethod.keyboard;
import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
public class ProximityInfo {
@@ -54,6 +56,19 @@ public class ProximityInfo {
computeNearestNeighbors(keyWidth, keys);
}
+ public static ProximityInfo createDummyProximityInfo() {
+ return new ProximityInfo(1, 1, 1, 1, 1, Collections.<Key>emptyList());
+ }
+
+ public static ProximityInfo createSpellCheckerProximityInfo() {
+ final ProximityInfo spellCheckerProximityInfo = createDummyProximityInfo();
+ spellCheckerProximityInfo.mNativeProximityInfo =
+ spellCheckerProximityInfo.setProximityInfoNative(
+ SpellCheckerProximityInfo.ROW_SIZE,
+ 480, 300, 10, 3, SpellCheckerProximityInfo.PROXIMITY);
+ return spellCheckerProximityInfo;
+ }
+
private int mNativeProximityInfo;
static {
Utils.loadNativeLibrary();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
index 30d9692a8..c0dba4173 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyles.java
@@ -20,7 +20,7 @@ import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.util.Log;
-import com.android.inputmethod.keyboard.internal.KeyboardParser.ParseException;
+import com.android.inputmethod.keyboard.internal.KeyboardBuilder.ParseException;
import com.android.inputmethod.latin.R;
import java.util.ArrayList;
@@ -212,19 +212,19 @@ public class KeyStyles {
public void parseKeyStyleAttributes(TypedArray keyStyleAttr, TypedArray keyAttrs,
XmlResourceParser parser) {
- String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
+ final String styleName = keyStyleAttr.getString(R.styleable.Keyboard_KeyStyle_styleName);
if (DEBUG) Log.d(TAG, String.format("<%s styleName=%s />",
- KeyboardParser.TAG_KEY_STYLE, styleName));
+ KeyboardBuilder.TAG_KEY_STYLE, styleName));
if (mStyles.containsKey(styleName))
throw new ParseException("duplicate key style declared: " + styleName, parser);
final DeclaredKeyStyle style = new DeclaredKeyStyle();
if (keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_parentStyle)) {
- String parentStyle = keyStyleAttr.getString(
+ final String parentStyle = keyStyleAttr.getString(
R.styleable.Keyboard_KeyStyle_parentStyle);
final DeclaredKeyStyle parent = mStyles.get(parentStyle);
if (parent == null)
- throw new ParseException("Unknown parentStyle " + parent, parser);
+ throw new ParseException("Unknown parentStyle " + parentStyle, parser);
style.addParent(parent);
}
style.parseKeyStyleAttributes(keyAttrs);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index 8eae2bb42..f599def36 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.util.Xml;
@@ -36,12 +37,11 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.Arrays;
-import java.util.List;
/**
- * Parser for BaseKeyboard.
+ * Keyboard Building helper.
*
- * This class parses Keyboard XML file and fill out keys in Keyboard.
+ * This class parses Keyboard XML file and eventually build a Keyboard.
* The Keyboard XML file looks like:
* <pre>
* &gt;!-- xml/keyboard.xml --&lt;
@@ -107,8 +107,8 @@ import java.util.List;
* </pre>
*/
-public class KeyboardParser {
- private static final String TAG = KeyboardParser.class.getSimpleName();
+public class KeyboardBuilder<KP extends KeyboardParams> {
+ private static final String TAG = KeyboardBuilder.class.getSimpleName();
private static final boolean DEBUG = false;
// Keyboard XML Tags
@@ -123,40 +123,53 @@ public class KeyboardParser {
private static final String TAG_DEFAULT = "default";
public static final String TAG_KEY_STYLE = "key-style";
- private final Keyboard mKeyboard;
- private final Context mContext;
- private final Resources mResources;
+ protected final KP mParams;
+ protected final Context mContext;
+ protected final Resources mResources;
+ private final DisplayMetrics mDisplayMetrics;
- private int mKeyboardTopPadding;
- private int mKeyboardBottomPadding;
- private int mHorizontalEdgesPadding;
private int mCurrentX = 0;
private int mCurrentY = 0;
- private int mMaxRowWidth = 0;
- private int mTotalHeight = 0;
private Row mCurrentRow = null;
private boolean mLeftEdge;
+ private boolean mTopEdge;
private Key mRightEdgeKey = null;
private final KeyStyles mKeyStyles = new KeyStyles();
- public KeyboardParser(Keyboard keyboard, Context context) {
- mKeyboard = keyboard;
+ public KeyboardBuilder(Context context, KP params) {
mContext = context;
final Resources res = context.getResources();
mResources = res;
- mHorizontalEdgesPadding = (int)res.getDimension(R.dimen.keyboard_horizontal_edges_padding);
+ mDisplayMetrics = res.getDisplayMetrics();
+
+ mParams = params;
+ mParams.mHorizontalEdgesPadding = (int)res.getDimension(
+ R.dimen.keyboard_horizontal_edges_padding);
+
+ mParams.GRID_WIDTH = res.getInteger(R.integer.config_keyboard_grid_width);
+ mParams.GRID_HEIGHT = res.getInteger(R.integer.config_keyboard_grid_height);
}
- public int getMaxRowWidth() {
- return mMaxRowWidth;
+ public KeyboardBuilder<KP> load(KeyboardId id) {
+ mParams.mId = id;
+ try {
+ parseKeyboard(id.getXmlId());
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "keyboard XML parse error: " + e);
+ throw new IllegalArgumentException(e);
+ } catch (IOException e) {
+ Log.w(TAG, "keyboard XML parse error: " + e);
+ throw new RuntimeException(e);
+ }
+ return this;
}
- public int getTotalHeight() {
- return mTotalHeight;
+ public Keyboard build() {
+ return new Keyboard(mParams);
}
- public void parseKeyboard(int resId) throws XmlPullParserException, IOException {
- if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mKeyboard.mId));
+ private void parseKeyboard(int resId) throws XmlPullParserException, IOException {
+ if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_KEYBOARD, mParams.mId));
final XmlResourceParser parser = mResources.getXml(resId);
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -165,7 +178,7 @@ public class KeyboardParser {
if (TAG_KEYBOARD.equals(tag)) {
parseKeyboardAttributes(parser);
startKeyboard();
- parseKeyboardContent(parser, mKeyboard.getKeys());
+ parseKeyboardContent(parser, false);
break;
} else {
throw new IllegalStartTag(parser, TAG_KEYBOARD);
@@ -196,15 +209,14 @@ public class KeyboardParser {
}
private void parseKeyboardAttributes(XmlResourceParser parser) {
- final Keyboard keyboard = mKeyboard;
- final int displayWidth = keyboard.getDisplayWidth();
+ final int displayWidth = mDisplayMetrics.widthPixels;
final TypedArray keyboardAttr = mContext.obtainStyledAttributes(
Xml.asAttributeSet(parser), R.styleable.Keyboard, R.attr.keyboardStyle,
R.style.Keyboard);
final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
try {
- final int displayHeight = keyboard.getDisplayHeight();
+ final int displayHeight = mDisplayMetrics.heightPixels;
final int keyboardHeight = (int)keyboardAttr.getDimension(
R.styleable.Keyboard_keyboardHeight, displayHeight / 2);
final int maxKeyboardHeight = getDimensionOrFraction(keyboardAttr,
@@ -219,61 +231,67 @@ public class KeyboardParser {
}
// Keyboard height will not exceed maxKeyboardHeight and will not be less than
// minKeyboardHeight.
- final int height = Math.max(
+ mParams.mOccupiedHeight = Math.max(
Math.min(keyboardHeight, maxKeyboardHeight), minKeyboardHeight);
-
- keyboard.setKeyboardHeight(height);
- keyboard.setRtlKeyboard(keyboardAttr.getBoolean(
- R.styleable.Keyboard_isRtlKeyboard, false));
- keyboard.setKeyWidth(getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_keyWidth, displayWidth, displayWidth / 10));
- keyboard.setRowHeight(getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_rowHeight, height, 50));
- keyboard.setHorizontalGap(getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_horizontalGap, displayWidth, 0));
- keyboard.setVerticalGap(getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_verticalGap, height, 0));
- keyboard.setPopupKeyboardResId(keyboardAttr.getResourceId(
- R.styleable.Keyboard_popupKeyboardTemplate, 0));
- keyboard.setMaxPopupKeyboardColumn(keyAttr.getInt(
- R.styleable.Keyboard_Key_maxPopupKeyboardColumn, 5));
-
- mKeyboard.mIconsSet.loadIcons(keyboardAttr);
- mKeyboardTopPadding = getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_keyboardTopPadding, height, 0);
- mKeyboardBottomPadding = getDimensionOrFraction(keyboardAttr,
- R.styleable.Keyboard_keyboardBottomPadding, height, 0);
+ mParams.mOccupiedWidth = mParams.mId.mWidth;
+ mParams.mTopPadding = getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_keyboardTopPadding, mParams.mOccupiedHeight, 0);
+ mParams.mBottomPadding = getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_keyboardBottomPadding, mParams.mOccupiedHeight, 0);
+
+ final int height = mParams.mOccupiedHeight;
+ final int width = mParams.mOccupiedWidth - mParams.mHorizontalEdgesPadding * 2
+ - mParams.mHorizontalCenterPadding;
+ mParams.mHeight = height;
+ mParams.mWidth = width;
+ mParams.mDefaultKeyWidth = getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_keyWidth, width, width / 10);
+ mParams.mDefaultRowHeight = getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_rowHeight, height, height / 4);
+ mParams.mHorizontalGap = getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_horizontalGap, width, 0);
+ mParams.mVerticalGap = getDimensionOrFraction(keyboardAttr,
+ R.styleable.Keyboard_verticalGap, height, 0);
+
+ mParams.mIsRtlKeyboard = keyboardAttr.getBoolean(
+ R.styleable.Keyboard_isRtlKeyboard, false);
+ mParams.mPopupKeyboardResId = keyboardAttr.getResourceId(
+ R.styleable.Keyboard_popupKeyboardTemplate, 0);
+ mParams.mMaxPopupColumn = keyAttr.getInt(
+ R.styleable.Keyboard_Key_maxPopupKeyboardColumn, 5);
+
+ mParams.mIconsSet.loadIcons(keyboardAttr);
} finally {
keyAttr.recycle();
keyboardAttr.recycle();
}
}
- private void parseKeyboardContent(XmlResourceParser parser, List<Key> keys)
+ private void parseKeyboardContent(XmlResourceParser parser, boolean skip)
throws XmlPullParserException, IOException {
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
if (TAG_ROW.equals(tag)) {
- Row row = new Row(mResources, mKeyboard, parser);
+ Row row = parseRowAttributes(parser);
if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_ROW));
- if (keys != null)
+ if (!skip)
startRow(row);
- parseRowContent(parser, row, keys);
+ parseRowContent(parser, row, skip);
} else if (TAG_INCLUDE.equals(tag)) {
- parseIncludeKeyboardContent(parser, keys);
+ parseIncludeKeyboardContent(parser, skip);
} else if (TAG_SWITCH.equals(tag)) {
- parseSwitchKeyboardContent(parser, keys);
+ parseSwitchKeyboardContent(parser, skip);
} else if (TAG_KEY_STYLE.equals(tag)) {
- parseKeyStyle(parser, keys);
+ parseKeyStyle(parser, skip);
} else {
throw new IllegalStartTag(parser, TAG_ROW);
}
} else if (event == XmlPullParser.END_TAG) {
final String tag = parser.getName();
if (TAG_KEYBOARD.equals(tag)) {
- endKeyboard(mKeyboard.getVerticalGap());
+ endKeyboard();
break;
} else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
|| TAG_MERGE.equals(tag)) {
@@ -288,22 +306,36 @@ public class KeyboardParser {
}
}
- private void parseRowContent(XmlResourceParser parser, Row row, List<Key> keys)
+ private Row parseRowAttributes(XmlResourceParser parser) {
+ final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
+ R.styleable.Keyboard);
+ try {
+ if (a.hasValue(R.styleable.Keyboard_horizontalGap))
+ throw new IllegalAttribute(parser, "horizontalGap");
+ if (a.hasValue(R.styleable.Keyboard_verticalGap))
+ throw new IllegalAttribute(parser, "verticalGap");
+ return new Row(mResources, mParams, parser);
+ } finally {
+ a.recycle();
+ }
+ }
+
+ private void parseRowContent(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
if (TAG_KEY.equals(tag)) {
- parseKey(parser, row, keys);
+ parseKey(parser, row, skip);
} else if (TAG_SPACER.equals(tag)) {
- parseSpacer(parser, row, keys);
+ parseSpacer(parser, row, skip);
} else if (TAG_INCLUDE.equals(tag)) {
- parseIncludeRowContent(parser, row, keys);
+ parseIncludeRowContent(parser, row, skip);
} else if (TAG_SWITCH.equals(tag)) {
- parseSwitchRowContent(parser, row, keys);
+ parseSwitchRowContent(parser, row, skip);
} else if (TAG_KEY_STYLE.equals(tag)) {
- parseKeyStyle(parser, keys);
+ parseKeyStyle(parser, skip);
} else {
throw new IllegalStartTag(parser, TAG_KEY);
}
@@ -311,7 +343,7 @@ public class KeyboardParser {
final String tag = parser.getName();
if (TAG_ROW.equals(tag)) {
if (DEBUG) Log.d(TAG, String.format("</%s>", TAG_ROW));
- if (keys != null)
+ if (!skip)
endRow();
break;
} else if (TAG_CASE.equals(tag) || TAG_DEFAULT.equals(tag)
@@ -327,26 +359,24 @@ public class KeyboardParser {
}
}
- private void parseKey(XmlResourceParser parser, Row row, List<Key> keys)
+ private void parseKey(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
- if (keys == null) {
+ if (skip) {
checkEndTag(TAG_KEY, parser);
} else {
- Key key = new Key(mResources, row, mCurrentX, mCurrentY, parser, mKeyStyles);
+ Key key = new Key(mResources, mParams, row, mCurrentX, mCurrentY, parser, mKeyStyles);
if (DEBUG) Log.d(TAG, String.format("<%s%s keyLabel=%s code=%d popupCharacters=%s />",
TAG_KEY, (key.isEnabled() ? "" : " disabled"), key.mLabel, key.mCode,
Arrays.toString(key.mPopupCharacters)));
checkEndTag(TAG_KEY, parser);
- keys.add(key);
- if (key.mCode == Keyboard.CODE_SHIFT)
- mKeyboard.getShiftKeys().add(key);
+ mParams.onAddKey(key);
endKey(key);
}
}
- private void parseSpacer(XmlResourceParser parser, Row row, List<Key> keys)
+ private void parseSpacer(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
- if (keys == null) {
+ if (skip) {
checkEndTag(TAG_SPACER, parser);
} else {
if (DEBUG) Log.d(TAG, String.format("<%s />", TAG_SPACER));
@@ -354,14 +384,14 @@ public class KeyboardParser {
R.styleable.Keyboard);
if (keyboardAttr.hasValue(R.styleable.Keyboard_horizontalGap))
throw new IllegalAttribute(parser, "horizontalGap");
- final int keyboardWidth = mKeyboard.getDisplayWidth();
+ final int keyboardWidth = mParams.mWidth;
final int keyWidth = getDimensionOrFraction(keyboardAttr, R.styleable.Keyboard_keyWidth,
- keyboardWidth, row.mDefaultWidth);
+ keyboardWidth, row.mDefaultKeyWidth);
keyboardAttr.recycle();
final TypedArray keyAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_Key);
- int keyXPos = KeyboardParser.getDimensionOrFraction(keyAttr,
+ int keyXPos = KeyboardBuilder.getDimensionOrFraction(keyAttr,
R.styleable.Keyboard_Key_keyXPos, keyboardWidth, mCurrentX);
if (keyXPos < 0) {
// If keyXPos is negative, the actual x-coordinate will be display_width + keyXPos.
@@ -373,19 +403,19 @@ public class KeyboardParser {
}
}
- private void parseIncludeKeyboardContent(XmlResourceParser parser, List<Key> keys)
+ private void parseIncludeKeyboardContent(XmlResourceParser parser, boolean skip)
throws XmlPullParserException, IOException {
- parseIncludeInternal(parser, null, keys);
+ parseIncludeInternal(parser, null, skip);
}
- private void parseIncludeRowContent(XmlResourceParser parser, Row row, List<Key> keys)
+ private void parseIncludeRowContent(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
- parseIncludeInternal(parser, row, keys);
+ parseIncludeInternal(parser, row, skip);
}
- private void parseIncludeInternal(XmlResourceParser parser, Row row, List<Key> keys)
+ private void parseIncludeInternal(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
- if (keys == null) {
+ if (skip) {
checkEndTag(TAG_INCLUDE, parser);
} else {
final TypedArray a = mResources.obtainAttributes(Xml.asAttributeSet(parser),
@@ -399,11 +429,11 @@ public class KeyboardParser {
throw new ParseException("No keyboardLayout attribute in <include/>", parser);
if (DEBUG) Log.d(TAG, String.format("<%s keyboardLayout=%s />",
TAG_INCLUDE, mResources.getResourceEntryName(keyboardLayout)));
- parseMerge(mResources.getLayout(keyboardLayout), row, keys);
+ parseMerge(mResources.getLayout(keyboardLayout), row, skip);
}
}
- private void parseMerge(XmlResourceParser parser, Row row, List<Key> keys)
+ private void parseMerge(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
@@ -411,9 +441,9 @@ public class KeyboardParser {
final String tag = parser.getName();
if (TAG_MERGE.equals(tag)) {
if (row == null) {
- parseKeyboardContent(parser, keys);
+ parseKeyboardContent(parser, skip);
} else {
- parseRowContent(parser, row, keys);
+ parseRowContent(parser, row, skip);
}
break;
} else {
@@ -424,28 +454,28 @@ public class KeyboardParser {
}
}
- private void parseSwitchKeyboardContent(XmlResourceParser parser, List<Key> keys)
+ private void parseSwitchKeyboardContent(XmlResourceParser parser, boolean skip)
throws XmlPullParserException, IOException {
- parseSwitchInternal(parser, null, keys);
+ parseSwitchInternal(parser, null, skip);
}
- private void parseSwitchRowContent(XmlResourceParser parser, Row row, List<Key> keys)
+ private void parseSwitchRowContent(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
- parseSwitchInternal(parser, row, keys);
+ parseSwitchInternal(parser, row, skip);
}
- private void parseSwitchInternal(XmlResourceParser parser, Row row, List<Key> keys)
+ private void parseSwitchInternal(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
- if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_SWITCH, mKeyboard.mId));
+ if (DEBUG) Log.d(TAG, String.format("<%s> %s", TAG_SWITCH, mParams.mId));
boolean selected = false;
int event;
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG) {
final String tag = parser.getName();
if (TAG_CASE.equals(tag)) {
- selected |= parseCase(parser, row, selected ? null : keys);
+ selected |= parseCase(parser, row, selected ? true : skip);
} else if (TAG_DEFAULT.equals(tag)) {
- selected |= parseDefault(parser, row, selected ? null : keys);
+ selected |= parseDefault(parser, row, selected ? true : skip);
} else {
throw new IllegalStartTag(parser, TAG_KEY);
}
@@ -461,21 +491,21 @@ public class KeyboardParser {
}
}
- private boolean parseCase(XmlResourceParser parser, Row row, List<Key> keys)
+ private boolean parseCase(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
final boolean selected = parseCaseCondition(parser);
if (row == null) {
// Processing Rows.
- parseKeyboardContent(parser, selected ? keys : null);
+ parseKeyboardContent(parser, selected ? skip : true);
} else {
// Processing Keys.
- parseRowContent(parser, row, selected ? keys : null);
+ parseRowContent(parser, row, selected ? skip : true);
}
return selected;
}
private boolean parseCaseCondition(XmlResourceParser parser) {
- final KeyboardId id = mKeyboard.mId;
+ final KeyboardId id = mParams.mId;
if (id == null)
return true;
@@ -496,10 +526,10 @@ public class KeyboardParser {
R.styleable.Keyboard_Case_f2KeyMode, id.mF2KeyMode);
final boolean clobberSettingsKeyMatched = matchBoolean(a,
R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey);
- final boolean voiceEnabledMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_voiceKeyEnabled, id.mVoiceKeyEnabled);
- final boolean voiceKeyMatched = matchBoolean(a,
- R.styleable.Keyboard_Case_hasVoiceKey, id.mHasVoiceKey);
+ final boolean shortcutKeyEnabledMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_shortcutKeyEnabled, id.mShortcutKeyEnabled);
+ final boolean hasShortcutKeyMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey);
// As noted at {@link KeyboardId} class, we are interested only in enum value masked by
// {@link android.view.inputmethod.EditorInfo#IME_MASK_ACTION} and
// {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_ENTER_ACTION}. So matching
@@ -514,7 +544,7 @@ public class KeyboardParser {
R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
final boolean selected = modeMatched && navigateActionMatched && passwordInputMatched
&& hasSettingsKeyMatched && f2KeyModeMatched && clobberSettingsKeyMatched
- && voiceEnabledMatched && voiceKeyMatched && imeActionMatched &&
+ && shortcutKeyEnabledMatched && hasShortcutKeyMatched && imeActionMatched &&
localeCodeMatched && languageCodeMatched && countryCodeMatched;
if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s%s%s%s%s> %s", TAG_CASE,
@@ -526,8 +556,9 @@ public class KeyboardParser {
a.getInt(R.styleable.Keyboard_Case_f2KeyMode, -1)), "f2KeyMode"),
booleanAttr(a, R.styleable.Keyboard_Case_clobberSettingsKey,
"clobberSettingsKey"),
- booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"),
- booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"),
+ booleanAttr(
+ a, R.styleable.Keyboard_Case_shortcutKeyEnabled, "shortcutKeyEnabled"),
+ booleanAttr(a, R.styleable.Keyboard_Case_hasShortcutKey, "hasShortcutKey"),
textAttr(EditorInfoCompatUtils.imeOptionsName(
a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"),
textAttr(a.getString(R.styleable.Keyboard_Case_localeCode), "localeCode"),
@@ -583,18 +614,18 @@ public class KeyboardParser {
return false;
}
- private boolean parseDefault(XmlResourceParser parser, Row row, List<Key> keys)
+ private boolean parseDefault(XmlResourceParser parser, Row row, boolean skip)
throws XmlPullParserException, IOException {
if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT));
if (row == null) {
- parseKeyboardContent(parser, keys);
+ parseKeyboardContent(parser, skip);
} else {
- parseRowContent(parser, row, keys);
+ parseRowContent(parser, row, skip);
}
return true;
}
- private void parseKeyStyle(XmlResourceParser parser, List<Key> keys) {
+ private void parseKeyStyle(XmlResourceParser parser, boolean skip) {
TypedArray keyStyleAttr = mResources.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard_KeyStyle);
TypedArray keyAttrs = mResources.obtainAttributes(Xml.asAttributeSet(parser),
@@ -603,7 +634,7 @@ public class KeyboardParser {
if (!keyStyleAttr.hasValue(R.styleable.Keyboard_KeyStyle_styleName))
throw new ParseException("<" + TAG_KEY_STYLE
+ "/> needs styleName attribute", parser);
- if (keys != null)
+ if (!skip)
mKeyStyles.parseKeyStyleAttributes(keyStyleAttr, keyAttrs, parser);
} finally {
keyStyleAttr.recycle();
@@ -619,12 +650,13 @@ public class KeyboardParser {
}
private void startKeyboard() {
- mCurrentY += mKeyboardTopPadding;
+ mCurrentY += mParams.mTopPadding;
+ mTopEdge = true;
}
private void startRow(Row row) {
mCurrentX = 0;
- setSpacer(mCurrentX, mHorizontalEdgesPadding);
+ setSpacer(mCurrentX, mParams.mHorizontalEdgesPadding);
mCurrentRow = row;
mLeftEdge = true;
mRightEdgeKey = null;
@@ -637,25 +669,25 @@ public class KeyboardParser {
mRightEdgeKey.addEdgeFlags(Keyboard.EDGE_RIGHT);
mRightEdgeKey = null;
}
- setSpacer(mCurrentX, mHorizontalEdgesPadding);
- if (mCurrentX > mMaxRowWidth)
- mMaxRowWidth = mCurrentX;
- mCurrentY += mCurrentRow.mDefaultHeight;
+ setSpacer(mCurrentX, mParams.mHorizontalEdgesPadding);
+ mCurrentY += mCurrentRow.mRowHeight;
mCurrentRow = null;
+ mTopEdge = false;
}
private void endKey(Key key) {
- mCurrentX = key.mX - key.mGap / 2 + key.mWidth + key.mGap;
+ mCurrentX = key.mX - key.mHorizontalGap / 2 + key.mWidth + key.mHorizontalGap;
if (mLeftEdge) {
key.addEdgeFlags(Keyboard.EDGE_LEFT);
mLeftEdge = false;
}
+ if (mTopEdge) {
+ key.addEdgeFlags(Keyboard.EDGE_TOP);
+ }
mRightEdgeKey = key;
}
- private void endKeyboard(int defaultVerticalGap) {
- mCurrentY += mKeyboardBottomPadding;
- mTotalHeight = mCurrentY - defaultVerticalGap;
+ private void endKeyboard() {
}
private void setSpacer(int keyXPos, int width) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 02c261bcd..2d8b7bf11 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -21,7 +21,6 @@ import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.Log;
-import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.R;
public class KeyboardIconsSet {
@@ -31,40 +30,33 @@ public class KeyboardIconsSet {
// This should be aligned with Keyboard.keyIcon enum.
private static final int ICON_SHIFT_KEY = 1;
- private static final int ICON_TO_SYMBOL_KEY = 2;
- private static final int ICON_TO_SYMBOL_KEY_WITH_SHORTCUT = 3;
- private static final int ICON_DELETE_KEY = 4;
- private static final int ICON_SETTINGS_KEY = 5; // This is also represented as "@icon/5" in xml.
- private static final int ICON_SHORTCUT_KEY = 6;
- private static final int ICON_SPACE_KEY = 7;
- private static final int ICON_RETURN_KEY = 8;
- private static final int ICON_SEARCH_KEY = 9;
- private static final int ICON_TAB_KEY = 10;
+ private static final int ICON_DELETE_KEY = 2;
+ private static final int ICON_SETTINGS_KEY = 3; // This is also represented as "@icon/3" in XML.
+ private static final int ICON_SPACE_KEY = 4;
+ private static final int ICON_RETURN_KEY = 5;
+ private static final int ICON_SEARCH_KEY = 6;
+ private static final int ICON_TAB_KEY = 7; // This is also represented as "@icon/7" in XML.
+ private static final int ICON_SHORTCUT_KEY = 8;
+ private static final int ICON_SHORTCUT_FOR_LABEL = 9;
// This should be aligned with Keyboard.keyIconShifted enum.
- private static final int ICON_SHIFTED_SHIFT_KEY = 11;
+ private static final int ICON_SHIFTED_SHIFT_KEY = 10;
// This should be aligned with Keyboard.keyIconPreview enum.
- private static final int ICON_PREVIEW_TAB_KEY = 12;
- private static final int ICON_PREVIEW_SETTINGS_KEY = 13;
- private static final int ICON_PREVIEW_SHORTCUT_KEY = 14;
+ private static final int ICON_PREVIEW_TAB_KEY = 11;
+ private static final int ICON_PREVIEW_SETTINGS_KEY = 12;
+ private static final int ICON_PREVIEW_SHORTCUT_KEY = 13;
- private static final int ICON_LAST = 14;
+ private static final int ICON_LAST = 13;
private final Drawable mIcons[] = new Drawable[ICON_LAST + 1];
- private static final int getIconId(int attrIndex) {
+ private static final int getIconId(final int attrIndex) {
switch (attrIndex) {
case R.styleable.Keyboard_iconShiftKey:
return ICON_SHIFT_KEY;
- case R.styleable.Keyboard_iconToSymbolKey:
- return ICON_TO_SYMBOL_KEY;
- case R.styleable.Keyboard_iconToSymbolKeyWithShortcut:
- return ICON_TO_SYMBOL_KEY_WITH_SHORTCUT;
case R.styleable.Keyboard_iconDeleteKey:
return ICON_DELETE_KEY;
case R.styleable.Keyboard_iconSettingsKey:
return ICON_SETTINGS_KEY;
- case R.styleable.Keyboard_iconShortcutKey:
- return ICON_SHORTCUT_KEY;
case R.styleable.Keyboard_iconSpaceKey:
return ICON_SPACE_KEY;
case R.styleable.Keyboard_iconReturnKey:
@@ -73,6 +65,10 @@ public class KeyboardIconsSet {
return ICON_SEARCH_KEY;
case R.styleable.Keyboard_iconTabKey:
return ICON_TAB_KEY;
+ case R.styleable.Keyboard_iconShortcutKey:
+ return ICON_SHORTCUT_KEY;
+ case R.styleable.Keyboard_iconShortcutForLabel:
+ return ICON_SHORTCUT_FOR_LABEL;
case R.styleable.Keyboard_iconShiftedShiftKey:
return ICON_SHIFTED_SHIFT_KEY;
case R.styleable.Keyboard_iconPreviewTabKey:
@@ -86,16 +82,14 @@ public class KeyboardIconsSet {
}
}
- public void loadIcons(TypedArray keyboardAttrs) {
+ public void loadIcons(final TypedArray keyboardAttrs) {
final int count = keyboardAttrs.getIndexCount();
for (int i = 0; i < count; i++) {
final int attrIndex = keyboardAttrs.getIndex(i);
final int iconId = getIconId(attrIndex);
if (iconId != ICON_UNDEFINED) {
try {
- final Drawable icon = keyboardAttrs.getDrawable(attrIndex);
- Keyboard.setDefaultBounds(icon);
- mIcons[iconId] = icon;
+ mIcons[iconId] = setDefaultBounds(keyboardAttrs.getDrawable(attrIndex));
} catch (Resources.NotFoundException e) {
Log.w(TAG, "Drawable resource for icon #" + iconId + " not found");
}
@@ -103,11 +97,18 @@ public class KeyboardIconsSet {
}
}
- public Drawable getIcon(int iconId) {
+ public Drawable getIcon(final int iconId) {
if (iconId == ICON_UNDEFINED)
return null;
if (iconId < 0 || iconId >= mIcons.length)
throw new IllegalArgumentException("icon id is out of range: " + iconId);
return mIcons[iconId];
}
+
+ private static Drawable setDefaultBounds(final Drawable icon) {
+ if (icon != null) {
+ icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+ }
+ return icon;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
new file mode 100644
index 000000000..4ccaa72d2
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -0,0 +1,95 @@
+/*
+ * 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.keyboard.internal;
+
+import android.graphics.drawable.Drawable;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardId;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class KeyboardParams {
+ public KeyboardId mId;
+
+ public int mOccupiedHeight;
+ public int mOccupiedWidth;
+
+ public int mHeight;
+ public int mWidth;
+
+ public int mTopPadding;
+ public int mBottomPadding;
+ public int mHorizontalEdgesPadding;
+ public int mHorizontalCenterPadding;
+
+ public int mDefaultRowHeight;
+ public int mDefaultKeyWidth;
+ public int mHorizontalGap;
+ public int mVerticalGap;
+
+ public boolean mIsRtlKeyboard;
+ public int mPopupKeyboardResId;
+ public int mMaxPopupColumn;
+
+ public int GRID_WIDTH;
+ public int GRID_HEIGHT;
+
+ public final List<Key> mKeys = new ArrayList<Key>();
+ public final List<Key> mShiftKeys = new ArrayList<Key>();
+ public final Set<Key> mShiftLockKeys = new HashSet<Key>();
+ public final Map<Key, Drawable> mShiftedIcons = new HashMap<Key, Drawable>();
+ public final Map<Key, Drawable> mUnshiftedIcons = new HashMap<Key, Drawable>();
+ public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet();
+
+ public int mMostCommonKeyWidth = 0;
+
+ public void onAddKey(Key key) {
+ mKeys.add(key);
+ updateHistogram(key);
+ if (key.mCode == Keyboard.CODE_SHIFT) {
+ mShiftKeys.add(key);
+ if (key.mSticky) {
+ mShiftLockKeys.add(key);
+ }
+ }
+ }
+
+ public void addShiftedIcon(Key key, Drawable icon) {
+ mUnshiftedIcons.put(key, key.getIcon());
+ mShiftedIcons.put(key, icon);
+ }
+
+ private int mMaxCount = 0;
+ private final Map<Integer, Integer> mHistogram = new HashMap<Integer, Integer>();
+
+ private void updateHistogram(Key key) {
+ final Integer width = key.mWidth + key.mHorizontalGap;
+ final int count = (mHistogram.containsKey(width) ? mHistogram.get(width) : 0) + 1;
+ mHistogram.put(width, count);
+ if (count > mMaxCount) {
+ mMaxCount = count;
+ mMostCommonKeyWidth = width;
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
index 0cde4e5b5..fd98456a8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
@@ -21,7 +21,7 @@ import android.util.Log;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
public class KeyboardShiftState {
- private static final String TAG = "KeyboardShiftState";
+ private static final String TAG = KeyboardShiftState.class.getSimpleName();
private static final boolean DEBUG = KeyboardSwitcher.DEBUG_STATE;
private static final int NORMAL = 0;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
index 965c679ea..31a291cef 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MiniKeyboardBuilder.java
@@ -16,8 +16,6 @@
package com.android.inputmethod.keyboard.internal;
-import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Paint;
import android.graphics.Rect;
@@ -27,26 +25,30 @@ import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.MiniKeyboard;
import com.android.inputmethod.latin.R;
-import java.util.List;
-
-public class MiniKeyboardBuilder {
- private final Resources mRes;
- private final MiniKeyboard mKeyboard;
+public class MiniKeyboardBuilder extends
+ KeyboardBuilder<MiniKeyboardBuilder.MiniKeyboardParams> {
private final CharSequence[] mPopupCharacters;
- private final MiniKeyboardLayoutParams mParams;
- /* package */ static class MiniKeyboardLayoutParams {
- public final int mKeyWidth;
- public final int mRowHeight;
- /* package */ final int mTopRowAdjustment;
- public final int mNumRows;
- public final int mNumColumns;
- public final int mLeftKeys;
- public final int mRightKeys; // includes default key.
- public int mTopPadding;
+ public static class MiniKeyboardParams extends KeyboardParams {
+ /* package */ int mTopRowAdjustment;
+ public int mNumRows;
+ public int mNumColumns;
+ public int mLeftKeys;
+ public int mRightKeys; // includes default key.
+
+ public MiniKeyboardParams() {
+ super();
+ }
+
+ /* package for test */ MiniKeyboardParams(int numKeys, int maxColumns, int keyWidth,
+ int rowHeight, int coordXInParent, int parentKeyboardWidth) {
+ super();
+ setParameters(
+ numKeys, maxColumns, keyWidth, rowHeight, coordXInParent, parentKeyboardWidth);
+ }
/**
- * The object holding mini keyboard layout parameters.
+ * Set keyboard parameters of mini keyboard.
*
* @param numKeys number of keys in this mini keyboard.
* @param maxColumns number of maximum columns of this mini keyboard.
@@ -54,15 +56,15 @@ public class MiniKeyboardBuilder {
* @param rowHeight mini keyboard row height in pixel, including vertical gap.
* @param coordXInParent coordinate x of the popup key in parent keyboard.
* @param parentKeyboardWidth parent keyboard width in pixel.
- * parent keyboard.
*/
- public MiniKeyboardLayoutParams(int numKeys, int maxColumns, int keyWidth, int rowHeight,
+ public void setParameters(int numKeys, int maxColumns, int keyWidth, int rowHeight,
int coordXInParent, int parentKeyboardWidth) {
- if (parentKeyboardWidth / keyWidth < maxColumns)
+ if (parentKeyboardWidth / keyWidth < maxColumns) {
throw new IllegalArgumentException("Keyboard is too small to hold mini keyboard: "
+ parentKeyboardWidth + " " + keyWidth + " " + maxColumns);
- mKeyWidth = keyWidth;
- mRowHeight = rowHeight;
+ }
+ mDefaultKeyWidth = keyWidth;
+ mDefaultRowHeight = rowHeight;
final int numRows = (numKeys + maxColumns - 1) / maxColumns;
mNumRows = numRows;
@@ -108,6 +110,9 @@ public class MiniKeyboardBuilder {
} else {
mTopRowAdjustment = -1;
}
+
+ mWidth = mOccupiedWidth = mNumColumns * mDefaultKeyWidth;
+ mHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
}
// Return key position according to column count (0 is default).
@@ -160,19 +165,19 @@ public class MiniKeyboardBuilder {
}
public int getDefaultKeyCoordX() {
- return mLeftKeys * mKeyWidth;
+ return mLeftKeys * mDefaultKeyWidth;
}
public int getX(int n, int row) {
- final int x = getColumnPos(n) * mKeyWidth + getDefaultKeyCoordX();
+ final int x = getColumnPos(n) * mDefaultKeyWidth + getDefaultKeyCoordX();
if (isTopRow(row)) {
- return x + mTopRowAdjustment * (mKeyWidth / 2);
+ return x + mTopRowAdjustment * (mDefaultKeyWidth / 2);
}
return x;
}
public int getY(int row) {
- return (mNumRows - 1 - row) * mRowHeight + mTopPadding;
+ return (mNumRows - 1 - row) * mDefaultRowHeight + mTopPadding;
}
public int getRowFlags(int row) {
@@ -185,42 +190,32 @@ public class MiniKeyboardBuilder {
private boolean isTopRow(int rowCount) {
return rowCount == mNumRows - 1;
}
-
- public void setTopPadding (int topPadding) {
- mTopPadding = topPadding;
- }
-
- public int getKeyboardHeight() {
- return mNumRows * mRowHeight + mTopPadding;
- }
-
- public int getKeyboardWidth() {
- return mNumColumns * mKeyWidth;
- }
}
- public MiniKeyboardBuilder(KeyboardView view, int layoutTemplateResId, Key parentKey,
+ public MiniKeyboardBuilder(KeyboardView view, int xmlId, Key parentKey,
Keyboard parentKeyboard) {
- final Context context = view.getContext();
- mRes = context.getResources();
- final MiniKeyboard keyboard = new MiniKeyboard(
- context, layoutTemplateResId, parentKeyboard);
- mKeyboard = keyboard;
+ super(view.getContext(), new MiniKeyboardParams());
+ load(parentKeyboard.mId.cloneWithNewXml(mResources.getResourceEntryName(xmlId), xmlId));
+
+ // HACK: Current mini keyboard design totally relies on the 9-patch padding about horizontal
+ // and vertical key spacing. To keep the visual of mini keyboard as is, these hacks are
+ // needed to keep having the same horizontal and vertical key spacing.
+ mParams.mHorizontalGap = 0;
+ mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
+ // TODO: When we have correctly padded key background 9-patch drawables for mini keyboard,
+ // revert the above hacks and uncomment the following lines.
+ //mParams.mHorizontalGap = parentKeyboard.mHorizontalGap;
+ //mParams.mVerticalGap = parentKeyboard.mVerticalGap;
+
+ mParams.mIsRtlKeyboard = parentKeyboard.mIsRtlKeyboard;
mPopupCharacters = parentKey.mPopupCharacters;
- final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, keyboard.getKeyWidth());
- final MiniKeyboardLayoutParams params = new MiniKeyboardLayoutParams(
+ final int keyWidth = getMaxKeyWidth(view, mPopupCharacters, mParams.mDefaultKeyWidth);
+ mParams.setParameters(
mPopupCharacters.length, parentKey.mMaxPopupColumn,
- keyWidth, parentKeyboard.getRowHeight(),
- parentKey.mX + (parentKey.mWidth + parentKey.mGap) / 2 - keyWidth / 2,
+ keyWidth, parentKeyboard.mDefaultRowHeight,
+ parentKey.mX + (mParams.mDefaultKeyWidth - keyWidth) / 2,
view.getMeasuredWidth());
- params.setTopPadding(keyboard.getVerticalGap());
- mParams = params;
-
- keyboard.setRowHeight(params.mRowHeight);
- keyboard.setKeyboardHeight(params.getKeyboardHeight());
- keyboard.setMinWidth(params.getKeyboardWidth());
- keyboard.setDefaultCoordX(params.getDefaultKeyCoordX() + params.mKeyWidth / 2);
}
private static int getMaxKeyWidth(KeyboardView view, CharSequence[] popupCharacters,
@@ -249,17 +244,16 @@ public class MiniKeyboardBuilder {
return Math.max(minKeyWidth, maxWidth + horizontalPadding);
}
+ @Override
public MiniKeyboard build() {
- final MiniKeyboard keyboard = mKeyboard;
- final List<Key> keys = keyboard.getKeys();
- final MiniKeyboardLayoutParams params = mParams;
+ final MiniKeyboardParams params = mParams;
for (int n = 0; n < mPopupCharacters.length; n++) {
final CharSequence label = mPopupCharacters[n];
final int row = n / params.mNumColumns;
- final Key key = new Key(mRes, keyboard, label, params.getX(n, row), params.getY(row),
- params.mKeyWidth, params.mRowHeight, params.getRowFlags(row));
- keys.add(key);
+ final Key key = new Key(mResources, params, label, params.getX(n, row), params.getY(row),
+ params.mDefaultKeyWidth, params.mDefaultRowHeight, params.getRowFlags(row));
+ params.onAddKey(key);
}
- return keyboard;
+ return new MiniKeyboard(params);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java b/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java
index 8276f5d78..032489e66 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PopupCharactersParser.java
@@ -23,6 +23,8 @@ import android.util.Log;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.latin.R;
+import java.util.ArrayList;
+
/**
* String parser of popupCharacters attribute of Key.
* The string is comma separated texts each of which represents one popup key.
@@ -182,4 +184,54 @@ public class PopupCharactersParser {
super(message);
}
}
+
+ public interface CodeFilter {
+ public boolean shouldFilterOut(int code);
+ }
+
+ public static final CodeFilter DIGIT_FILTER = new CodeFilter() {
+ @Override
+ public boolean shouldFilterOut(int code) {
+ return Character.isDigit(code);
+ }
+ };
+
+ public static final CodeFilter NON_ASCII_FILTER = new CodeFilter() {
+ @Override
+ public boolean shouldFilterOut(int code) {
+ return code < 0x20 || code > 0x7e;
+ }
+ };
+
+ public static CharSequence[] filterOut(Resources res, CharSequence[] popupCharacters,
+ CodeFilter filter) {
+ if (popupCharacters == null || popupCharacters.length < 1) {
+ return null;
+ }
+ if (popupCharacters.length == 1
+ && filter.shouldFilterOut(getCode(res, popupCharacters[0].toString()))) {
+ return null;
+ }
+ ArrayList<CharSequence> filtered = null;
+ for (int i = 0; i < popupCharacters.length; i++) {
+ final CharSequence popupSpec = popupCharacters[i];
+ if (filter.shouldFilterOut(getCode(res, popupSpec.toString()))) {
+ if (filtered == null) {
+ filtered = new ArrayList<CharSequence>();
+ for (int j = 0; j < i; j++) {
+ filtered.add(popupCharacters[j]);
+ }
+ }
+ } else if (filtered != null) {
+ filtered.add(popupSpec);
+ }
+ }
+ if (filtered == null) {
+ return popupCharacters;
+ }
+ if (filtered.size() == 0) {
+ return null;
+ }
+ return filtered.toArray(new CharSequence[filtered.size()]);
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/Row.java b/java/src/com/android/inputmethod/keyboard/internal/Row.java
index b34d6d06f..d53fe12e2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/Row.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/Row.java
@@ -31,34 +31,19 @@ import com.android.inputmethod.latin.R;
*/
public class Row {
/** Default width of a key in this row. */
- public final int mDefaultWidth;
+ public final int mDefaultKeyWidth;
/** Default height of a key in this row. */
- public final int mDefaultHeight;
- /** Default horizontal gap between keys in this row. */
- public final int mDefaultHorizontalGap;
- /** Vertical gap following this row. */
- public final int mVerticalGap;
+ public final int mRowHeight;
- private final Keyboard mKeyboard;
-
- public Row(Resources res, Keyboard keyboard, XmlResourceParser parser) {
- this.mKeyboard = keyboard;
- final int keyboardWidth = keyboard.getDisplayWidth();
- final int keyboardHeight = keyboard.getKeyboardHeight();
+ public Row(Resources res, KeyboardParams params, XmlResourceParser parser) {
+ final int keyboardWidth = params.mWidth;
+ final int keyboardHeight = params.mHeight;
TypedArray a = res.obtainAttributes(Xml.asAttributeSet(parser),
R.styleable.Keyboard);
- mDefaultWidth = KeyboardParser.getDimensionOrFraction(a,
- R.styleable.Keyboard_keyWidth, keyboardWidth, keyboard.getKeyWidth());
- mDefaultHeight = KeyboardParser.getDimensionOrFraction(a,
- R.styleable.Keyboard_rowHeight, keyboardHeight, keyboard.getRowHeight());
- mDefaultHorizontalGap = KeyboardParser.getDimensionOrFraction(a,
- R.styleable.Keyboard_horizontalGap, keyboardWidth, keyboard.getHorizontalGap());
- mVerticalGap = KeyboardParser.getDimensionOrFraction(a,
- R.styleable.Keyboard_verticalGap, keyboardHeight, keyboard.getVerticalGap());
+ mDefaultKeyWidth = KeyboardBuilder.getDimensionOrFraction(a,
+ R.styleable.Keyboard_keyWidth, keyboardWidth, params.mDefaultKeyWidth);
+ mRowHeight = KeyboardBuilder.getDimensionOrFraction(a,
+ R.styleable.Keyboard_rowHeight, keyboardHeight, params.mDefaultRowHeight);
a.recycle();
}
-
- public Keyboard getKeyboard() {
- return mKeyboard;
- }
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 9748d6006..6a6a0a4ee 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -156,10 +156,11 @@ public class BinaryDictionary extends Dictionary {
}
}
+ // proximityInfo may not be null.
@Override
- public void getWords(final WordComposer codes, final WordCallback callback) {
- final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(),
- mOutputChars, mScores);
+ public void getWords(final WordComposer codes, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
+ final int count = getSuggestions(codes, proximityInfo, mOutputChars, mScores);
for (int j = 0; j < count; ++j) {
if (mScores[j] < 1) break;
@@ -179,8 +180,9 @@ public class BinaryDictionary extends Dictionary {
return mNativeDict != 0;
}
- /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard,
- char[] outputChars, int[] scores) {
+ // proximityInfo may not be null.
+ /* package for test */ int getSuggestions(final WordComposer codes,
+ final ProximityInfo proximityInfo, char[] outputChars, int[] scores) {
if (!isValidDictionary()) return -1;
final int codesSize = codes.size();
@@ -196,9 +198,8 @@ 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, proximityInfo,
+ mNativeDict, proximityInfo.getNativeProximityInfo(),
codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
mFlags, outputChars, scores);
}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 41b577cf3..3da670e2e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -20,16 +20,18 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
+import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;
+import android.util.Log;
-import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -38,89 +40,83 @@ import java.util.Locale;
* file from the dictionary provider
*/
public class BinaryDictionaryFileDumper {
+ private static final String TAG = BinaryDictionaryFileDumper.class.getSimpleName();
+
/**
* The size of the temporary buffer to copy files.
*/
static final int FILE_READ_BUFFER_SIZE = 1024;
+ private static final String DICTIONARY_PROJECTION[] = { "id" };
+
// Prevents this class to be accidentally instantiated.
private BinaryDictionaryFileDumper() {
}
/**
- * Generates a file name that matches the locale passed as an argument.
- * The file name is basically the result of the .toString() method, except we replace
- * any @File.separator with an underscore to avoid generating a file name that may not
- * be created.
- * @param locale the locale for which to get the file name
- * @param context the context to use for getting the directory
- * @return the name of the file to be created
+ * Return for a given locale or dictionary id the provider URI to get the dictionary.
*/
- private static String getCacheFileNameForLocale(Locale locale, Context context) {
- // The following assumes two things :
- // 1. That File.separator is not the same character as "_"
- // I don't think any android system will ever use "_" as a path separator
- // 2. That no two locales differ by only a File.separator versus a "_"
- // Since "_" can't be part of locale components this should be safe.
- // Examples:
- // en -> en
- // en_US_POSIX -> en_US_POSIX
- // en__foo/bar -> en__foo_bar
- final String[] separator = { File.separator };
- final String[] empty = { "_" };
- final CharSequence basename = TextUtils.replace(locale.toString(), separator, empty);
- return context.getFilesDir() + File.separator + basename;
+ private static Uri getProviderUri(String path) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath(
+ path).build();
}
/**
- * Return for a given locale the provider URI to query to get the dictionary.
+ * Queries a content provider for the list of dictionaries for a specific locale
+ * available to copy into Latin IME.
*/
- public static Uri getProviderUri(Locale locale) {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
- .authority(BinaryDictionary.DICTIONARY_PACK_AUTHORITY).appendPath(
- locale.toString()).build();
+ private static List<String> getDictIdList(final Locale locale, final Context context) {
+ final ContentResolver resolver = context.getContentResolver();
+ final Uri dictionaryPackUri = getProviderUri(locale.toString());
+
+ final Cursor c = resolver.query(dictionaryPackUri, DICTIONARY_PROJECTION, null, null, null);
+ if (null == c) return Collections.<String>emptyList();
+ if (c.getCount() <= 0 || !c.moveToFirst()) {
+ c.close();
+ return Collections.<String>emptyList();
+ }
+
+ final List<String> list = new ArrayList<String>();
+ do {
+ final String id = c.getString(0);
+ if (TextUtils.isEmpty(id)) continue;
+ list.add(id);
+ } while (c.moveToNext());
+ c.close();
+ return list;
}
/**
- * Queries a content provider for dictionary data for some locale and returns the file addresses
+ * Queries a content provider for dictionary data for some locale and cache the returned files
*
- * This will query a content provider for dictionary data for a given locale, and return
- * the addresses of a file set the members of which are suitable to be mmap'ed. It will copy
- * them to local storage if needed.
- * It should also check the dictionary versions to avoid unnecessary copies but this is
- * still in TODO state.
- * This will make the data from the content provider the cached dictionary for this locale,
- * overwriting any previous cached data.
+ * This will query a content provider for dictionary data for a given locale, and copy the
+ * files locally so that they can be mmap'ed. This may overwrite previously cached dictionaries
+ * with newer versions if a newer version is made available by the content provider.
* @returns the addresses of the files, or null if no data could be obtained.
* @throw FileNotFoundException if the provider returns non-existent data.
* @throw IOException if the provider-returned data could not be read.
*/
- public static List<AssetFileAddress> getDictSetFromContentProvider(Locale locale,
- Context context) throws FileNotFoundException, IOException {
- // TODO: check whether the dictionary is the same or not and if it is, return the cached
- // file.
- // TODO: This should be able to read a number of files from the dictionary pack, copy
- // them all and return them.
+ public static List<AssetFileAddress> cacheDictionariesFromContentProvider(final Locale locale,
+ final Context context) throws FileNotFoundException, IOException {
final ContentResolver resolver = context.getContentResolver();
- final Uri dictionaryPackUri = getProviderUri(locale);
- final AssetFileDescriptor afd = resolver.openAssetFileDescriptor(dictionaryPackUri, "r");
- if (null == afd) return null;
- final String fileName =
- copyFileTo(afd.createInputStream(), getCacheFileNameForLocale(locale, context));
- afd.close();
- return Arrays.asList(AssetFileAddress.makeFromFileName(fileName));
- }
-
- /**
- * Accepts a file as dictionary data for some locale and returns the name of a file.
- *
- * This will make the data in the input file the cached dictionary for this locale, overwriting
- * any previous cached data.
- */
- public static String getDictionaryFileFromFile(String fileName, Locale locale,
- Context context) throws FileNotFoundException, IOException {
- return copyFileTo(new FileInputStream(fileName), getCacheFileNameForLocale(locale,
- context));
+ final List<String> idList = getDictIdList(locale, context);
+ final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>();
+ for (String id : idList) {
+ final Uri wordListUri = getProviderUri(id);
+ final AssetFileDescriptor afd =
+ resolver.openAssetFileDescriptor(wordListUri, "r");
+ if (null == afd) continue;
+ final String fileName = copyFileTo(afd.createInputStream(),
+ BinaryDictionaryGetter.getCacheFileName(id, locale, context));
+ afd.close();
+ if (0 >= resolver.delete(wordListUri, null, null)) {
+ // I'd rather not print the word list ID to the log here out of security concerns
+ Log.e(TAG, "Could not have the dictionary pack delete a word list");
+ }
+ fileAddressList.add(AssetFileAddress.makeFromFileName(fileName));
+ }
+ return fileAddressList;
}
/**
@@ -135,7 +131,9 @@ public class BinaryDictionaryFileDumper {
final Locale savedLocale = Utils.setSystemLocale(res, locale);
final InputStream stream = res.openRawResource(resource);
Utils.setSystemLocale(res, savedLocale);
- return copyFileTo(stream, getCacheFileNameForLocale(locale, context));
+ return copyFileTo(stream,
+ BinaryDictionaryGetter.getCacheFileName(Integer.toString(resource),
+ locale, context));
}
/**
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 989a0e9a0..360cf21ca 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -17,13 +17,17 @@
package com.android.inputmethod.latin;
import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.util.Log;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Arrays;
+import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -37,10 +41,105 @@ class BinaryDictionaryGetter {
*/
private static final String TAG = BinaryDictionaryGetter.class.getSimpleName();
+ /**
+ * Name of the common preferences name to know which word list are on and which are off.
+ */
+ private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
+
// Prevents this from being instantiated
private BinaryDictionaryGetter() {}
/**
+ * Returns whether we may want to use this character as part of a file name.
+ *
+ * This basically only accepts ascii letters and numbers, and rejects everything else.
+ */
+ private static boolean isFileNameCharacter(int codePoint) {
+ if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit
+ if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase
+ if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase
+ return codePoint == '_'; // Underscore
+ }
+
+ /**
+ * Escapes a string for any characters that may be suspicious for a file or directory name.
+ *
+ * Concretely this does a sort of URL-encoding except it will encode everything that's not
+ * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which
+ * we cannot allow here)
+ */
+ // TODO: create a unit test for this method
+ private static String replaceFileNameDangerousCharacters(final String name) {
+ // This assumes '%' is fully available as a non-separator, normal
+ // character in a file name. This is probably true for all file systems.
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < name.length(); ++i) {
+ final int codePoint = name.codePointAt(i);
+ if (isFileNameCharacter(codePoint)) {
+ sb.appendCodePoint(codePoint);
+ } else {
+ // 6 digits - unicode is limited to 21 bits
+ sb.append(String.format((Locale)null, "%%%1$06x", codePoint));
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Reverse escaping done by replaceFileNameDangerousCharacters.
+ */
+ private static String getWordListIdFromFileName(final String fname) {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < fname.length(); ++i) {
+ final int codePoint = fname.codePointAt(i);
+ if ('%' != codePoint) {
+ sb.appendCodePoint(codePoint);
+ } else {
+ final int encodedCodePoint = Integer.parseInt(fname.substring(i + 1, i + 7), 16);
+ i += 6;
+ sb.appendCodePoint(encodedCodePoint);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Find out the cache directory associated with a specific locale.
+ */
+ private static String getCacheDirectoryForLocale(Locale locale, Context context) {
+ final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toString());
+ final String absoluteDirectoryName = context.getFilesDir() + File.separator
+ + relativeDirectoryName;
+ final File directory = new File(absoluteDirectoryName);
+ if (!directory.exists()) {
+ if (!directory.mkdirs()) {
+ Log.e(TAG, "Could not create the directory for locale" + locale);
+ }
+ }
+ return absoluteDirectoryName;
+ }
+
+ /**
+ * Generates a file name for the id and locale passed as an argument.
+ *
+ * In the current implementation the file name returned will always be unique for
+ * any id/locale pair, but please do not expect that the id can be the same for
+ * different dictionaries with different locales. An id should be unique for any
+ * dictionary.
+ * The file name is pretty much an URL-encoded version of the id inside a directory
+ * named like the locale, except it will also escape characters that look dangerous
+ * to some file systems.
+ * @param id the id of the dictionary for which to get a file name
+ * @param locale the locale for which to get the file name
+ * @param context the context to use for getting the directory
+ * @return the name of the file to be created
+ */
+ public static String getCacheFileName(String id, Locale locale, Context context) {
+ final String fileName = replaceFileNameDangerousCharacters(id);
+ return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
+ }
+
+ /**
* Returns a file address from a resource, or null if it cannot be opened.
*/
private static AssetFileAddress loadFallbackResource(final Context context,
@@ -60,12 +159,51 @@ class BinaryDictionaryGetter {
}
/**
+ * Returns the list of cached files for a specific locale.
+ *
+ * @param locale the locale to find the dictionary files for.
+ * @param context the context on which to open the files upon.
+ * @return a list of binary dictionary files, which may be null but may not be empty.
+ */
+ private static List<AssetFileAddress> getCachedDictionaryList(final Locale locale,
+ final Context context) {
+ final String directoryName = getCacheDirectoryForLocale(locale, context);
+ final File[] cacheFiles = new File(directoryName).listFiles();
+ // TODO: Never return null. Fallback on the built-in dictionary, and if that's
+ // not present or disabled, then return an empty list.
+ if (null == cacheFiles) return null;
+
+ final SharedPreferences dictPackSettings;
+ try {
+ final String dictPackName = context.getString(R.string.dictionary_pack_package_name);
+ final Context dictPackContext = context.createPackageContext(dictPackName, 0);
+ dictPackSettings = dictPackContext.getSharedPreferences(COMMON_PREFERENCES_NAME,
+ Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS);
+ } catch (NameNotFoundException e) {
+ // The dictionary pack is not installed...
+ // TODO: fallback on the built-in dict, see the TODO above
+ Log.e(TAG, "Could not find a dictionary pack");
+ return null;
+ }
+
+ final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>();
+ for (File f : cacheFiles) {
+ final String wordListId = getWordListIdFromFileName(f.getName());
+ final boolean isActive = dictPackSettings.getBoolean(wordListId, true);
+ if (!isActive) continue;
+ if (f.canRead()) {
+ fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
+ } else {
+ Log.e(TAG, "Found a cached dictionary file but cannot read it");
+ }
+ }
+ return fileList.size() > 0 ? fileList : null;
+ }
+
+ /**
* Returns a list of file addresses for a given locale, trying relevant methods in order.
*
* Tries to get binary dictionaries from various sources, in order:
- * - Uses a private method of getting a private dictionaries, as implemented by the
- * PrivateBinaryDictionaryGetter class.
- * If that fails:
* - Uses a content provider to get a public dictionary set, as per the protocol described
* in BinaryDictionaryFileDumper.
* If that fails:
@@ -74,33 +212,28 @@ class BinaryDictionaryGetter {
* - Returns null.
* @return The address of a valid file, or null.
*/
- public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context,
- int fallbackResId) {
- // Try first to query a private package signed the same way for private files.
- final List<AssetFileAddress> privateFiles =
- PrivateBinaryDictionaryGetter.getDictionaryFiles(locale, context);
- if (null != privateFiles) {
- return privateFiles;
- } else {
- try {
- // If that was no-go, try to find a publicly exported dictionary.
- 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");
- } catch (IOException e) {
- Log.e(TAG, "Unable to read source data for locale "
- + locale.toString() + ": falling back to internal dictionary");
+ public static List<AssetFileAddress> getDictionaryFiles(final Locale locale,
+ final Context context, final int fallbackResId) {
+ try {
+ // cacheDictionariesFromContentProvider returns the list of files it copied to local
+ // storage, but we don't really care about what was copied NOW: what we want is the
+ // list of everything we ever cached, so we ignore the return value.
+ BinaryDictionaryFileDumper.cacheDictionariesFromContentProvider(locale, context);
+ List<AssetFileAddress> cachedDictionaryList = getCachedDictionaryList(locale, context);
+ if (null != cachedDictionaryList) {
+ return cachedDictionaryList;
}
- final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId,
- locale);
- if (null == fallbackAsset) return null;
- return Arrays.asList(fallbackAsset);
+ // 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");
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to read source data for locale "
+ + locale.toString() + ": falling back to internal dictionary");
}
+ final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId,
+ locale);
+ if (null == fallbackAsset) return null;
+ return Arrays.asList(fallbackAsset);
}
}
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index 4baf52e52..b5b59ac12 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -38,6 +38,7 @@ 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;
@@ -50,15 +51,12 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.util.ArrayList;
import java.util.List;
-public class CandidateView extends LinearLayout implements OnClickListener {
-
+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();
// The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}.
private static final int MAX_SUGGESTIONS = 18;
private static final int WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -67,11 +65,6 @@ public class CandidateView extends LinearLayout implements OnClickListener {
private static final boolean DBG = LatinImeLogger.sDBG;
private final ViewGroup mCandidatesStrip;
- private final int mCandidateCountInStrip;
- private static final int DEFAULT_CANDIDATE_COUNT_IN_STRIP = 3;
- private final ViewGroup mCandidatesPaneControl;
- private final TextView mExpandCandidatesPane;
- private final TextView mCloseCandidatesPane;
private ViewGroup mCandidatesPane;
private ViewGroup mCandidatesPaneContainer;
private View mKeyboardView;
@@ -80,17 +73,6 @@ public class CandidateView extends LinearLayout implements OnClickListener {
private final ArrayList<TextView> mInfos = new ArrayList<TextView>();
private final ArrayList<View> mDividers = new ArrayList<View>();
- private final int mCandidateStripHeight;
- private final CharacterStyle mInvertedForegroundColorSpan;
- private final CharacterStyle mInvertedBackgroundColorSpan;
- 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;
@@ -102,9 +84,9 @@ public class CandidateView extends LinearLayout implements OnClickListener {
private boolean mShowingAutoCorrectionInverted;
private boolean mShowingAddToDictionary;
- private final CandidateViewLayoutParams mParams;
- private static final int PUNCTUATIONS_IN_STRIP = 6;
- private static final float MIN_TEXT_XSCALE = 0.75f;
+ private final SuggestionsStripParams mStripParams;
+ private final SuggestionsPaneParams mPaneParams;
+ private static final float MIN_TEXT_XSCALE = 0.70f;
private final UiHandler mHandler = new UiHandler(this);
@@ -157,118 +139,358 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
}
- private static class CandidateViewLayoutParams {
- public final TextPaint mPaint;
+ private static class CandidateViewParams {
public final int mPadding;
public final int mDividerWidth;
public final int mDividerHeight;
- public final int mControlWidth;
- private final int mAutoCorrectHighlight;
+ public final int mCandidateStripHeight;
- public final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+ protected final List<TextView> mWords;
+ protected final List<View> mDividers;
+ protected final List<TextView> mInfos;
- public int mCountInStrip;
- // True if the mCountInStrip suggestions can fit in suggestion strip in equally divided
- // width without squeezing the text.
- public boolean mCanUseFixedWidthColumns;
- public int mMaxWidth;
- public int mAvailableWidthForWords;
- public int mConstantWidthForPaddings;
- public int mVariableWidthForWords;
- public float mScaleX;
+ protected CandidateViewParams(List<TextView> words, List<View> dividers,
+ List<TextView> infos) {
+ mWords = words;
+ mDividers = dividers;
+ mInfos = infos;
- public CandidateViewLayoutParams(Resources res, TextView word, View divider, View control,
- int autoCorrectHighlight) {
- mPaint = new TextPaint();
- final float textSize = res.getDimension(R.dimen.candidate_text_size);
- mPaint.setTextSize(textSize);
+ final TextView word = words.get(0);
+ final View divider = dividers.get(0);
mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight();
divider.measure(WRAP_CONTENT, MATCH_PARENT);
mDividerWidth = divider.getMeasuredWidth();
mDividerHeight = divider.getMeasuredHeight();
- mControlWidth = control.getMeasuredWidth();
- mAutoCorrectHighlight = autoCorrectHighlight;
+
+ final Resources res = word.getResources();
+ mCandidateStripHeight = res.getDimensionPixelOffset(R.dimen.candidate_strip_height);
}
+ }
- public void layoutStrip(SuggestedWords suggestions, int maxWidth, int maxCount) {
- final int size = suggestions.size();
- if (size == 0) return;
- setupTexts(suggestions, size, mAutoCorrectHighlight);
- mCountInStrip = Math.min(maxCount, size);
- mScaleX = 1.0f;
+ private static class SuggestionsPaneParams extends CandidateViewParams {
+ public SuggestionsPaneParams(List<TextView> words, List<View> dividers,
+ List<TextView> infos) {
+ super(words, dividers, infos);
+ }
+
+ public int layout(SuggestedWords suggestions, ViewGroup paneView, int from, int textColor,
+ int paneWidth) {
+ final int count = Math.min(mWords.size(), suggestions.size());
+ View centeringFrom = null, lastView = null;
+ int x = 0, y = 0;
+ for (int index = from; index < count; index++) {
+ final int pos = index;
+ final TextView word = mWords.get(pos);
+ final View divider = mDividers.get(pos);
+ final TextPaint paint = word.getPaint();
+ word.setTextColor(textColor);
+ final CharSequence styled = suggestions.getWord(pos);
+
+ final TextView info;
+ if (DBG) {
+ final CharSequence debugInfo = getDebugInfo(suggestions, index);
+ if (debugInfo != null) {
+ info = mInfos.get(index);
+ info.setText(debugInfo);
+ } else {
+ info = null;
+ }
+ } else {
+ info = null;
+ }
- do {
- mMaxWidth = maxWidth;
- if (size > mCountInStrip) {
- mMaxWidth -= mControlWidth;
+ final CharSequence text;
+ final float scaleX;
+ paint.setTextScaleX(1.0f);
+ final int textWidth = getTextWidth(styled, paint);
+ int available = paneWidth - x - mPadding;
+ if (textWidth >= available) {
+ // Needs new row, centering previous row.
+ centeringCandidates(paneView, centeringFrom, lastView, x, paneWidth);
+ x = 0;
+ y += mCandidateStripHeight;
}
+ if (x != 0) {
+ // Add divider if this isn't the left most suggestion in current row.
+ paneView.addView(divider);
+ FrameLayoutCompatUtils.placeViewAt(divider, x, y
+ + (mCandidateStripHeight - mDividerHeight) / 2, mDividerWidth,
+ mDividerHeight);
+ x += mDividerWidth;
+ }
+ available = paneWidth - x - mPadding;
+ text = getEllipsizedText(styled, available, paint);
+ scaleX = paint.getTextScaleX();
+ word.setText(text);
+ word.setTextScaleX(scaleX);
+ paneView.addView(word);
+ lastView = word;
+ if (x == 0)
+ centeringFrom = word;
+ word.measure(WRAP_CONTENT,
+ MeasureSpec.makeMeasureSpec(mCandidateStripHeight, MeasureSpec.EXACTLY));
+ final int width = word.getMeasuredWidth();
+ final int height = word.getMeasuredHeight();
+ FrameLayoutCompatUtils.placeViewAt(word, x, y + (mCandidateStripHeight - height)
+ / 2, width, height);
+ x += width;
+ if (info != null) {
+ paneView.addView(info);
+ lastView = info;
+ info.measure(WRAP_CONTENT, WRAP_CONTENT);
+ final int infoWidth = info.getMeasuredWidth();
+ FrameLayoutCompatUtils.placeViewAt(info, x - infoWidth, y, infoWidth,
+ info.getMeasuredHeight());
+ }
+ }
+ if (x != 0) {
+ // Centering last candidates row.
+ centeringCandidates(paneView, centeringFrom, lastView, x, paneWidth);
+ }
+
+ return count - from;
+ }
+ }
+
+ private static class SuggestionsStripParams extends CandidateViewParams {
+ private static final int DEFAULT_CANDIDATE_COUNT_IN_STRIP = 3;
+ private static final int DEFAULT_CENTER_CANDIDATE_PERCENTILE = 40;
+ private static final int PUNCTUATIONS_IN_STRIP = 6;
+
+ private final int mColorTypedWord;
+ private final int mColorAutoCorrect;
+ private final int mColorSuggestedCandidate;
+ private final int mCandidateCountInStrip;
+ private final float mCenterCandidateWeight;
+ private final int mCenterCandidateIndex;
+ private final Drawable mMoreCandidateHint;
+
+ 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 final TextPaint mPaint;
+ private final int mAutoCorrectHighlight;
- tryLayout();
+ private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
+
+ public final boolean mAutoCorrectionVisualFlashEnabled;
+ public boolean mMoreSuggestionsAvailable;
+
+ public SuggestionsStripParams(Context context, AttributeSet attrs, int defStyle,
+ List<TextView> words, List<View> dividers, List<TextView> infos) {
+ super(words, dividers, infos);
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle);
+ mAutoCorrectionVisualFlashEnabled = a.getBoolean(
+ R.styleable.CandidateView_autoCorrectionVisualFlashEnabled, false);
+ 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);
+ mCandidateCountInStrip = a.getInt(
+ R.styleable.CandidateView_candidateCountInStrip,
+ DEFAULT_CANDIDATE_COUNT_IN_STRIP);
+ mCenterCandidateWeight = a.getInt(
+ R.styleable.CandidateView_centerCandidatePercentile,
+ DEFAULT_CENTER_CANDIDATE_PERCENTILE) / 100.0f;
+ a.recycle();
+
+ mCenterCandidateIndex = mCandidateCountInStrip / 2;
+ final Resources res = context.getResources();
+ mMoreCandidateHint = res.getDrawable(R.drawable.more_suggestions_hint);
+
+ mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff);
+ mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
- if (mCanUseFixedWidthColumns) {
- return;
+ mPaint = new TextPaint();
+ final float textSize = res.getDimension(R.dimen.candidate_text_size);
+ mPaint.setTextSize(textSize);
+ }
+
+ public int getTextColor() {
+ return mColorTypedWord;
+ }
+
+ private CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect) {
+ if (!isAutoCorrect)
+ return word;
+ final int len = word.length();
+ final Spannable spannedWord = new SpannableString(word);
+ if ((mAutoCorrectHighlight & AUTO_CORRECT_BOLD) != 0)
+ spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ if ((mAutoCorrectHighlight & AUTO_CORRECT_UNDERLINE) != 0)
+ spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ return spannedWord;
+ }
+
+ 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 == mCenterCandidateIndex) {
+ return centerPos;
+ } else if (index == centerPos) {
+ return mCenterCandidateIndex;
+ } else {
+ return index;
+ }
+ }
+
+ private int getCandidateTextColor(int index, SuggestedWords suggestions, int pos) {
+ // TODO: Need to revisit this logic with bigram suggestions
+ final boolean isSuggestedCandidate = (pos != 0);
+
+ final int color;
+ if (index == mCenterCandidateIndex && willAutoCorrect(suggestions)) {
+ color = mColorAutoCorrect;
+ } else if (isSuggestedCandidate) {
+ color = mColorSuggestedCandidate;
+ } else {
+ color = mColorTypedWord;
+ }
+
+ final SuggestedWordInfo info = (pos < suggestions.size())
+ ? suggestions.getInfo(pos) : null;
+ if (info != null && info.isPreviousSuggestedWord()) {
+ 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 ((mAutoCorrectHighlight & 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 int layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup paneView,
+ int stripWidth) {
+ if (suggestions.isPunctuationSuggestions()) {
+ return layoutPunctuationSuggestions(suggestions, stripView);
+ }
+
+ final int countInStrip = mCandidateCountInStrip;
+ 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 candidate strip.
+ stripView.addView(divider);
}
- if (mVariableWidthForWords <= mAvailableWidthForWords) {
- return;
+
+ final CharSequence styled = mTexts.get(pos);
+ final TextView word = mWords.get(pos);
+ if (index == mCenterCandidateIndex && mMoreSuggestionsAvailable) {
+ // TODO: This "more suggestions hint" should have nicely designed icon.
+ word.setCompoundDrawablesWithIntrinsicBounds(
+ null, null, null, mMoreCandidateHint);
+ } else {
+ word.setCompoundDrawables(null, null, null, null);
}
- final float scaleX = mAvailableWidthForWords / (float)mVariableWidthForWords;
- if (scaleX >= MIN_TEXT_XSCALE) {
- mScaleX = scaleX;
- return;
+ // Disable this candidate if the suggestion is null or empty.
+ word.setEnabled(!TextUtils.isEmpty(styled));
+ word.setTextColor(getCandidateTextColor(index, suggestions, pos));
+ final int width = getCandidateWidth(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);
+ setLayoutWeight(word, getCandidateWeight(index), mCandidateStripHeight);
+
+ if (DBG) {
+ final CharSequence debugInfo = getDebugInfo(suggestions, pos);
+ if (debugInfo != null) {
+ final TextView info = mInfos.get(pos);
+ info.setText(debugInfo);
+ paneView.addView(info);
+ info.measure(WRAP_CONTENT, WRAP_CONTENT);
+ final int infoWidth = info.getMeasuredWidth();
+ final int y = info.getMeasuredHeight();
+ FrameLayoutCompatUtils.placeViewAt(info, x, 0, infoWidth, y);
+ x += infoWidth * 2;
+ }
}
+ }
+
+ return countInStrip;
+ }
+
+ private int getCandidateWidth(int index, int maxWidth) {
+ final int paddings = mPadding * mCandidateCountInStrip;
+ final int dividers = mDividerWidth * (mCandidateCountInStrip - 1);
+ final int availableWidth = maxWidth - paddings - dividers;
+ return (int)(availableWidth * getCandidateWeight(index));
+ }
- mCountInStrip--;
- } while (mCountInStrip > 1);
- }
-
- public void tryLayout() {
- final int maxCount = mCountInStrip;
- final int dividers = mDividerWidth * (maxCount - 1);
- mConstantWidthForPaddings = dividers + mPadding * maxCount;
- mAvailableWidthForWords = mMaxWidth - mConstantWidthForPaddings;
-
- mPaint.setTextScaleX(mScaleX);
- final int maxFixedWidthForWord = (mMaxWidth - dividers) / maxCount - mPadding;
- mCanUseFixedWidthColumns = true;
- mVariableWidthForWords = 0;
- for (int i = 0; i < maxCount; i++) {
- final int width = getTextWidth(mTexts.get(i), mPaint);
- if (width > maxFixedWidthForWord)
- mCanUseFixedWidthColumns = false;
- mVariableWidthForWords += width;
+ private float getCandidateWeight(int index) {
+ if (index == mCenterCandidateIndex) {
+ return mCenterCandidateWeight;
+ } else {
+ // TODO: Revisit this for cases of 5 or more suggestions
+ return (1.0f - mCenterCandidateWeight) / (mCandidateCountInStrip - 1);
}
}
- private void setupTexts(SuggestedWords suggestions, int count, int autoCorrectHighlight) {
+ private void setupTexts(SuggestedWords suggestions, int countInStrip) {
mTexts.clear();
- for (int i = 0; i < count; i++) {
- final CharSequence suggestion = suggestions.getWord(i);
- if (suggestion == null) {
- // Skip an empty suggestion, but we need to add a place-holder for it in order
- // to avoid an exception in the loop in updateSuggestions().
- mTexts.add("");
- continue;
- }
-
- final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion
- && ((i == 1 && !suggestions.mTypedWordValid)
- || (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 CharSequence styled = getStyledCandidateWord(suggestion, isAutoCorrect,
- autoCorrectHighlight);
+ final int count = Math.min(suggestions.size(), countInStrip);
+ for (int pos = 0; pos < count; pos++) {
+ final CharSequence word = suggestions.getWord(pos);
+ final boolean isAutoCorrect = pos == 1 && willAutoCorrect(suggestions);
+ final CharSequence styled = getStyledCandidateWord(word, isAutoCorrect);
mTexts.add(styled);
}
+ for (int pos = count; pos < countInStrip; pos++) {
+ // Make this inactive for touches in layout().
+ mTexts.add(null);
+ }
}
- @Override
- public String toString() {
- return String.format(
- "count=%d width=%d avail=%d fixcol=%s scaleX=%4.2f const=%d var=%d",
- mCountInStrip, mMaxWidth, mAvailableWidthForWords, mCanUseFixedWidthColumns,
- mScaleX, mConstantWidthForPaddings, mVariableWidthForWords);
+ private int 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 candidate 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);
+ stripView.addView(word);
+ setLayoutWeight(word, 1.0f, mCandidateStripHeight);
+ }
+ mMoreSuggestionsAvailable = false;
+ return countInStrip;
}
}
@@ -295,18 +517,7 @@ public class CandidateView extends LinearLayout implements OnClickListener {
setBackgroundDrawable(LinearLayoutCompatUtils.getBackgroundDrawable(
context, attrs, defStyle, R.style.CandidateViewStyle));
- 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);
- mCandidateCountInStrip = a.getInt(
- R.styleable.CandidateView_candidateCountInStrip, DEFAULT_CANDIDATE_COUNT_IN_STRIP);
- a.recycle();
-
- Resources res = context.getResources();
- LayoutInflater inflater = LayoutInflater.from(context);
+ final LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.candidates_strip, this);
mPreviewPopup = new PopupWindow(context);
@@ -317,56 +528,26 @@ public class CandidateView extends LinearLayout implements OnClickListener {
mPreviewPopup.setBackgroundDrawable(null);
mCandidatesStrip = (ViewGroup)findViewById(R.id.candidates_strip);
- mCandidateStripHeight = res.getDimensionPixelOffset(R.dimen.candidate_strip_height);
- for (int i = 0; i < MAX_SUGGESTIONS; i++) {
+ for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) {
final TextView word = (TextView)inflater.inflate(R.layout.candidate_word, null);
- word.setTag(i);
+ word.setTag(pos);
word.setOnClickListener(this);
+ word.setOnLongClickListener(this);
mWords.add(word);
+ final View divider = inflater.inflate(R.layout.candidate_divider, null);
+ divider.setTag(pos);
+ divider.setOnClickListener(this);
+ mDividers.add(divider);
mInfos.add((TextView)inflater.inflate(R.layout.candidate_info, null));
- mDividers.add(inflater.inflate(R.layout.candidate_divider, null));
}
mTouchToSave = findViewById(R.id.touch_to_save);
mWordToSave = (TextView)findViewById(R.id.word_to_save);
mWordToSave.setOnClickListener(this);
- mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff);
- mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord);
-
- final TypedArray keyboardViewAttr = context.obtainStyledAttributes(
- attrs, R.styleable.KeyboardView, R.attr.keyboardViewStyle, R.style.KeyboardView);
- final Drawable expandBackground = keyboardViewAttr.getDrawable(
- R.styleable.KeyboardView_keyBackground);
- final Drawable closeBackground = keyboardViewAttr.getDrawable(
- R.styleable.KeyboardView_keyBackground);
- final int keyTextColor = keyboardViewAttr.getColor(
- R.styleable.KeyboardView_keyTextColor, 0xFF000000);
- keyboardViewAttr.recycle();
-
- mCandidatesPaneControl = (ViewGroup)findViewById(R.id.candidates_pane_control);
- mExpandCandidatesPane = (TextView)findViewById(R.id.expand_candidates_pane);
- mExpandCandidatesPane.setBackgroundDrawable(expandBackground);
- mExpandCandidatesPane.setTextColor(keyTextColor);
- mExpandCandidatesPane.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- expandCandidatesPane();
- }
- });
- mCloseCandidatesPane = (TextView)findViewById(R.id.close_candidates_pane);
- mCloseCandidatesPane.setBackgroundDrawable(closeBackground);
- mCloseCandidatesPane.setTextColor(keyTextColor);
- mCloseCandidatesPane.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View view) {
- closeCandidatesPane();
- }
- });
- mCandidatesPaneControl.measure(WRAP_CONTENT, WRAP_CONTENT);
-
- mParams = new CandidateViewLayoutParams(res,
- mWords.get(0), mDividers.get(0), mCandidatesPaneControl, mAutoCorrectHighlight);
+ mStripParams = new SuggestionsStripParams(context, attrs, defStyle, mWords, mDividers,
+ mInfos);
+ mPaneParams = new SuggestionsPaneParams(mWords, mDividers, mInfos);
}
/**
@@ -387,7 +568,6 @@ public class CandidateView extends LinearLayout implements OnClickListener {
if (suggestions == null)
return;
mSuggestions = suggestions;
- mExpandCandidatesPane.setEnabled(false);
if (mShowingAutoCorrectionInverted) {
mHandler.postUpdateSuggestions();
} else {
@@ -395,181 +575,30 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
}
- private static CharSequence getStyledCandidateWord(CharSequence word, boolean isAutoCorrect,
- int autoCorrectHighlight) {
- if (!isAutoCorrect)
- return word;
- final Spannable spannedWord = new SpannableString(word);
- if ((autoCorrectHighlight & AUTO_CORRECT_BOLD) != 0)
- spannedWord.setSpan(BOLD_SPAN, 0, word.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- if ((autoCorrectHighlight & 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;
- final int paneWidth = getWidth();
- final CandidateViewLayoutParams params = mParams;
-
clear();
closeCandidatesPane();
- if (suggestions.size() == 0)
+ if (mSuggestions.size() == 0)
return;
- params.layoutStrip(suggestions, paneWidth, suggestions.isPunctuationSuggestions()
- ? PUNCTUATIONS_IN_STRIP : mCandidateCountInStrip);
-
- final int count = Math.min(mWords.size(), suggestions.size());
- if (count <= params.mCountInStrip && !DBG) {
- mCandidatesPaneControl.setVisibility(GONE);
- } else {
- mCandidatesPaneControl.setVisibility(VISIBLE);
- mExpandCandidatesPane.setVisibility(VISIBLE);
- mExpandCandidatesPane.setEnabled(true);
- }
-
- final int countInStrip = params.mCountInStrip;
- View centeringFrom = null, lastView = null;
- int x = 0, y = 0, infoX = 0;
- for (int i = 0; i < count; i++) {
- final int pos;
- if (i <= 1) {
- final boolean willAutoCorrect = !suggestions.mTypedWordValid
- && suggestions.mHasMinimalSuggestion;
- pos = willAutoCorrect ? 1 - i : i;
- } else {
- pos = i;
- }
- final CharSequence suggestion = suggestions.getWord(pos);
- if (suggestion == null) continue;
-
- final SuggestedWordInfo suggestionInfo = (suggestedWordInfoList != null)
- ? suggestedWordInfoList.get(pos) : null;
- final boolean isAutoCorrect = suggestions.mHasMinimalSuggestion
- && ((pos == 1 && !suggestions.mTypedWordValid)
- || (pos == 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 = (pos != 0);
- final boolean isPunctuationSuggestions = (suggestion.length() == 1 && count > 1);
-
- final TextView word = mWords.get(pos);
- final TextPaint paint = word.getPaint();
- // 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));
- final CharSequence styled = params.mTexts.get(pos);
-
- final TextView info;
- if (DBG && suggestionInfo != null
- && !TextUtils.isEmpty(suggestionInfo.getDebugString())) {
- info = mInfos.get(i);
- info.setText(suggestionInfo.getDebugString());
- } else {
- info = null;
- }
+ final int width = getWidth();
+ final int countInStrip = mStripParams.layout(
+ mSuggestions, mCandidatesStrip, mCandidatesPane, width);
+ final int countInPane = mPaneParams.layout(
+ mSuggestions, mCandidatesPane, countInStrip, mStripParams.getTextColor(), width);
+ }
- final CharSequence text;
- final float scaleX;
- if (i < countInStrip) {
- if (i == 0 && params.mCountInStrip == 1) {
- text = getEllipsizedText(styled, params.mMaxWidth, paint);
- scaleX = paint.getTextScaleX();
- } else {
- text = styled;
- scaleX = params.mScaleX;
- }
- word.setText(text);
- word.setTextScaleX(scaleX);
- if (i != 0) {
- // Add divider if this isn't the left most suggestion in candidate strip.
- mCandidatesStrip.addView(mDividers.get(i));
- }
- mCandidatesStrip.addView(word);
- if (params.mCanUseFixedWidthColumns) {
- setLayoutWeight(word, 1.0f, mCandidateStripHeight);
- } else {
- final int width = getTextWidth(text, paint) + params.mPadding;
- setLayoutWeight(word, width, mCandidateStripHeight);
- }
- if (info != null) {
- mCandidatesPane.addView(info);
- info.measure(WRAP_CONTENT, WRAP_CONTENT);
- final int width = info.getMeasuredWidth();
- y = info.getMeasuredHeight();
- FrameLayoutCompatUtils.placeViewAt(info, infoX, 0, width, y);
- infoX += width * 2;
- }
- } else {
- paint.setTextScaleX(1.0f);
- final int textWidth = getTextWidth(styled, paint);
- int available = paneWidth - x - params.mPadding;
- if (textWidth >= available) {
- // Needs new row, centering previous row.
- centeringCandidates(centeringFrom, lastView, x, paneWidth);
- x = 0;
- y += mCandidateStripHeight;
- }
- if (x != 0) {
- // Add divider if this isn't the left most suggestion in current row.
- final View divider = mDividers.get(i);
- mCandidatesPane.addView(divider);
- FrameLayoutCompatUtils.placeViewAt(
- divider, x, y + (mCandidateStripHeight - params.mDividerHeight) / 2,
- params.mDividerWidth, params.mDividerHeight);
- x += params.mDividerWidth;
- }
- available = paneWidth - x - params.mPadding;
- text = getEllipsizedText(styled, available, paint);
- scaleX = paint.getTextScaleX();
- word.setText(text);
- word.setTextScaleX(scaleX);
- mCandidatesPane.addView(word);
- lastView = word;
- if (x == 0) centeringFrom = word;
- word.measure(WRAP_CONTENT,
- MeasureSpec.makeMeasureSpec(mCandidateStripHeight, MeasureSpec.EXACTLY));
- final int width = word.getMeasuredWidth();
- final int height = word.getMeasuredHeight();
- FrameLayoutCompatUtils.placeViewAt(
- word, x, y + (mCandidateStripHeight - height) / 2, width, height);
- x += width;
- if (info != null) {
- mCandidatesPane.addView(info);
- lastView = info;
- info.measure(WRAP_CONTENT, WRAP_CONTENT);
- final int infoWidth = info.getMeasuredWidth();
- FrameLayoutCompatUtils.placeViewAt(
- info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
+ 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;
}
}
}
- if (x != 0) {
- // Centering last candidates row.
- centeringCandidates(centeringFrom, lastView, x, paneWidth);
- }
+ return null;
}
private static void setLayoutWeight(View v, float weight, int height) {
@@ -582,13 +611,13 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
}
- private void centeringCandidates(View from, View to, int width, int paneWidth) {
- final ViewGroup pane = mCandidatesPane;
- final int fromIndex = pane.indexOfChild(from);
- final int toIndex = pane.indexOfChild(to);
- final int offset = (paneWidth - width) / 2;
+ private static void centeringCandidates(ViewGroup parent, View from, View to, int width,
+ int parentWidth) {
+ final int fromIndex = parent.indexOfChild(from);
+ final int toIndex = parent.indexOfChild(to);
+ final int offset = (parentWidth - width) / 2;
for (int index = fromIndex; index <= toIndex; index++) {
- offsetMargin(pane.getChildAt(index), offset, 0);
+ offsetMargin(parent.getChildAt(index), offset, 0);
}
}
@@ -604,16 +633,20 @@ public class CandidateView extends LinearLayout implements OnClickListener {
private static CharSequence getEllipsizedText(CharSequence text, int maxWidth,
TextPaint paint) {
+ if (text == null) return null;
paint.setTextScaleX(1.0f);
final int width = getTextWidth(text, paint);
- final float scaleX = Math.min(maxWidth / (float)width, 1.0f);
+ 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 ellipsezed text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
+ // 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);
@@ -652,31 +685,33 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
private void expandCandidatesPane() {
- mExpandCandidatesPane.setVisibility(GONE);
- mCloseCandidatesPane.setVisibility(VISIBLE);
mCandidatesPaneContainer.setMinimumHeight(mKeyboardView.getMeasuredHeight());
mCandidatesPaneContainer.setVisibility(VISIBLE);
mKeyboardView.setVisibility(GONE);
}
private void closeCandidatesPane() {
- mExpandCandidatesPane.setVisibility(VISIBLE);
- mCloseCandidatesPane.setVisibility(GONE);
mCandidatesPaneContainer.setVisibility(GONE);
mKeyboardView.setVisibility(VISIBLE);
}
+ private void toggleCandidatesPane() {
+ if (mCandidatesPaneContainer.getVisibility() == VISIBLE) {
+ closeCandidatesPane();
+ } else {
+ expandCandidatesPane();
+ }
+ }
+
public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) {
- if ((mAutoCorrectHighlight & AUTO_CORRECT_INVERT) == 0)
+ if (!mStripParams.mAutoCorrectionVisualFlashEnabled) {
+ return;
+ }
+ final CharSequence inverted = mStripParams.getInvertedText(autoCorrectedWord);
+ if (inverted == null)
return;
final TextView tv = mWords.get(1);
- final Spannable word = new SpannableString(autoCorrectedWord);
- final int wordLength = word.length();
- word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength,
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- word.setSpan(mInvertedForegroundColorSpan, 0, wordLength,
- Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- tv.setText(word);
+ tv.setText(inverted);
mShowingAutoCorrectionInverted = true;
}
@@ -688,7 +723,6 @@ public class CandidateView extends LinearLayout implements OnClickListener {
mWordToSave.setText(word);
mShowingAddToDictionary = true;
mCandidatesStrip.setVisibility(GONE);
- mCandidatesPaneControl.setVisibility(GONE);
mTouchToSave.setVisibility(VISIBLE);
}
@@ -721,7 +755,7 @@ public class CandidateView extends LinearLayout implements OnClickListener {
return;
final TextView previewText = mPreviewText;
- previewText.setTextColor(mColorTypedWord);
+ previewText.setTextColor(mStripParams.mColorTypedWord);
previewText.setText(word);
previewText.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
@@ -747,6 +781,15 @@ public class CandidateView extends LinearLayout implements OnClickListener {
}
@Override
+ public boolean onLongClick(View view) {
+ if (mStripParams.mMoreSuggestionsAvailable) {
+ toggleCandidatesPane();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public void onClick(View view) {
if (view == mWordToSave) {
addToDictionary(((TextView)view).getText());
@@ -754,6 +797,11 @@ public class CandidateView extends LinearLayout implements OnClickListener {
return;
}
+ if (view == mCandidatesPane) {
+ closeCandidatesPane();
+ return;
+ }
+
final Object tag = view.getTag();
if (!(tag instanceof Integer))
return;
diff --git a/java/src/com/android/inputmethod/latin/ContactsDictionary.java b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
index 66a041508..8a7dfb839 100644
--- a/java/src/com/android/inputmethod/latin/ContactsDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsDictionary.java
@@ -49,20 +49,28 @@ public class ContactsDictionary extends ExpandableDictionary {
private long mLastLoadedContacts;
- public ContactsDictionary(Context context, int dicTypeId) {
+ public ContactsDictionary(final Context context, final int dicTypeId) {
super(context, dicTypeId);
+ registerObserver(context);
+ loadDictionary();
+ }
+
+ private synchronized void registerObserver(final Context context) {
// Perform a managed query. The Activity will handle closing and requerying the cursor
// when needed.
+ if (mObserver != null) return;
ContentResolver cres = context.getContentResolver();
-
cres.registerContentObserver(
- Contacts.CONTENT_URI, true,mObserver = new ContentObserver(null) {
+ Contacts.CONTENT_URI, true, mObserver = new ContentObserver(null) {
@Override
public void onChange(boolean self) {
setRequiresReload(true);
}
});
- loadDictionary();
+ }
+
+ public void reopen(final Context context) {
+ registerObserver(context);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index c7737b9a2..c35b42877 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -1,12 +1,12 @@
/*
* Copyright (C) 2008 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
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
/**
* Abstract base class for a dictionary that can do a fuzzy search for words based on a set of key
* strokes.
@@ -25,7 +27,7 @@ public abstract class Dictionary {
* Whether or not to replicate the typed word in the suggested list, even if it's valid.
*/
protected static final boolean INCLUDE_TYPED_WORD_IF_VALID = false;
-
+
/**
* The weight to give to a word if it's length is the same as the number of typed characters.
*/
@@ -57,13 +59,15 @@ public abstract class Dictionary {
}
/**
- * Searches for words in the dictionary that match the characters in the composer. Matched
+ * Searches for words in the dictionary that match the characters in the composer. Matched
* words are added through the callback object.
* @param composer the key sequence to match
* @param callback the callback object to send matched words to as possible candidates
+ * @param proximityInfo the object for key proximity. May be ignored by some implementations.
* @see WordCallback#addWord(char[], int, int, int, int, DataType)
*/
- abstract public void getWords(final WordComposer composer, final WordCallback callback);
+ abstract public void getWords(final WordComposer composer, final WordCallback callback,
+ final ProximityInfo proximityInfo);
/**
* Searches for pairs in the bigram dictionary that matches the previous word and all the
@@ -83,7 +87,7 @@ public abstract class Dictionary {
* @return true if the word exists, false otherwise
*/
abstract public boolean isValidWord(CharSequence word);
-
+
/**
* Compares the contents of the character array with the typed word and returns true if they
* are the same.
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index 107840331..739153044 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -16,6 +16,8 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -47,9 +49,10 @@ public class DictionaryCollection extends Dictionary {
}
@Override
- public void getWords(final WordComposer composer, final WordCallback callback) {
+ public void getWords(final WordComposer composer, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
for (final Dictionary dict : mDictionaries)
- dict.getWords(composer, callback);
+ dict.getWords(composer, callback, proximityInfo);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 97a4a1816..35d1541ff 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.os.AsyncTask;
import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.ProximityInfo;
import java.util.LinkedList;
@@ -193,7 +194,8 @@ public class ExpandableDictionary extends Dictionary {
}
@Override
- public void getWords(final WordComposer codes, final WordCallback callback) {
+ public void getWords(final WordComposer codes, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
synchronized (mUpdatingLock) {
// If we need to update, start off a background task
if (mRequiresReload) startDictionaryLoadingTaskLocked();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d9d421411..a932f03ac 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -64,6 +64,7 @@ import com.android.inputmethod.keyboard.Key;
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.KeyboardSwitcher.KeyboardLayoutState;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.LatinKeyboard;
import com.android.inputmethod.keyboard.LatinKeyboardView;
@@ -112,6 +113,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Key events coming any faster than this are long-presses.
private static final int QUICK_PRESS = 200;
+ private static final int SCREEN_ORIENTATION_CHANGE_DETECTION_DELAY = 2;
+ private static final int ACCUMULATE_START_INPUT_VIEW_DELAY = 20;
+ private static final int RESTORE_KEYBOARD_STATE_DELAY = 500;
+
/**
* The name of the scheme used by the Package Manager to warn of a new package installation,
* replacement or removal.
@@ -165,7 +170,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private WordComposer mWordComposer = new WordComposer();
private CharSequence mBestWord;
private boolean mHasUncommittedTypedChars;
- private boolean mHasDictionary;
// 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.
@@ -186,8 +190,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private long mLastKeyTime;
private AudioManager mAudioManager;
- // Align sound effect volume on music volume
- private static final float FX_VOLUME = -1.0f;
+ private static float mFxVolume = -1.0f; // just a default value to be updated runtime
private boolean mSilentModeOn; // System-wide current configuration
// TODO: Move this flag to VoiceProxy
@@ -218,6 +221,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private static final int MSG_SET_BIGRAM_PREDICTIONS = 7;
private static final int MSG_CONFIRM_ORIENTATION_CHANGE = 8;
private static final int MSG_START_INPUT_VIEW = 9;
+ private static final int MSG_RESTORE_KEYBOARD_LAYOUT = 10;
private static class OrientationChangeArgs {
public final int mOldWidth;
@@ -302,6 +306,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
case MSG_START_INPUT_VIEW:
latinIme.onStartInputView((EditorInfo)msg.obj, false);
break;
+ case MSG_RESTORE_KEYBOARD_LAYOUT:
+ removeMessages(MSG_UPDATE_SHIFT_STATE);
+ ((KeyboardLayoutState)msg.obj).restore();
+ break;
}
}
@@ -392,22 +400,38 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return hasMessages(MSG_SPACE_TYPED);
}
+ public void postRestoreKeyboardLayout() {
+ final LatinIME latinIme = getOuterInstance();
+ final KeyboardLayoutState state = latinIme.mKeyboardSwitcher.getKeyboardState();
+ if (state.isValid()) {
+ removeMessages(MSG_RESTORE_KEYBOARD_LAYOUT);
+ sendMessageDelayed(
+ obtainMessage(MSG_RESTORE_KEYBOARD_LAYOUT, state),
+ RESTORE_KEYBOARD_STATE_DELAY);
+ }
+ }
+
private void postConfirmOrientationChange(OrientationChangeArgs args) {
removeMessages(MSG_CONFIRM_ORIENTATION_CHANGE);
- // Will confirm whether orientation change has finished or not after 2ms again.
- sendMessageDelayed(obtainMessage(MSG_CONFIRM_ORIENTATION_CHANGE, args), 2);
+ // Will confirm whether orientation change has finished or not again.
+ sendMessageDelayed(obtainMessage(MSG_CONFIRM_ORIENTATION_CHANGE, args),
+ SCREEN_ORIENTATION_CHANGE_DETECTION_DELAY);
}
public void startOrientationChanging(int oldw, int oldh) {
postConfirmOrientationChange(new OrientationChangeArgs(oldw, oldh));
+ final LatinIME latinIme = getOuterInstance();
+ latinIme.mKeyboardSwitcher.getKeyboardState().save();
+ postRestoreKeyboardLayout();
}
public boolean postStartInputView(EditorInfo attribute) {
if (hasMessages(MSG_CONFIRM_ORIENTATION_CHANGE) || hasMessages(MSG_START_INPUT_VIEW)) {
removeMessages(MSG_START_INPUT_VIEW);
- // Postpone onStartInputView 20ms afterward and see if orientation change has
- // finished.
- sendMessageDelayed(obtainMessage(MSG_START_INPUT_VIEW, attribute), 20);
+ // Postpone onStartInputView by ACCUMULATE_START_INPUT_VIEW_DELAY and see if
+ // orientation change has finished.
+ sendMessageDelayed(obtainMessage(MSG_START_INPUT_VIEW, attribute),
+ ACCUMULATE_START_INPUT_VIEW_DELAY);
return true;
}
return false;
@@ -484,7 +508,8 @@ 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();
+ resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
+ updateSoundEffectVolume();
}
private void initSuggest() {
@@ -493,8 +518,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final Resources res = mResources;
final Locale savedLocale = Utils.setSystemLocale(res, keyboardLocale);
+ final ContactsDictionary oldContactsDictionary;
if (mSuggest != null) {
+ oldContactsDictionary = mSuggest.getContactsDictionary();
mSuggest.close();
+ } else {
+ oldContactsDictionary = null;
}
int mainDicResId = Utils.getMainDictionaryResourceId(res);
@@ -508,7 +537,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mSuggest.setUserDictionary(mUserDictionary);
mIsUserDictionaryAvaliable = mUserDictionary.isEnabled();
- resetContactsDictionary();
+ resetContactsDictionary(oldContactsDictionary);
mUserUnigramDictionary
= new UserUnigramDictionary(this, this, localeStr, Suggest.DIC_USER_UNIGRAM);
@@ -523,11 +552,36 @@ 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);
+ /**
+ * Resets the contacts dictionary in mSuggest according to the user settings.
+ *
+ * This method takes an optional contacts dictionary to use. Since the contacts dictionary
+ * does not depend on the locale, it can be reused across different instances of Suggest.
+ * The dictionary will also be opened or closed as necessary depending on the settings.
+ *
+ * @param oldContactsDictionary an optional dictionary to use, or null
+ */
+ private void resetContactsDictionary(final ContactsDictionary oldContactsDictionary) {
+ final boolean shouldSetDictionary = (null != mSuggest && mSettingsValues.mUseContactsDict);
+
+ final ContactsDictionary dictionaryToUse;
+ if (!shouldSetDictionary) {
+ // Make sure the dictionary is closed. If it is already closed, this is a no-op,
+ // so it's safe to call it anyways.
+ if (null != oldContactsDictionary) oldContactsDictionary.close();
+ dictionaryToUse = null;
+ } else if (null != oldContactsDictionary) {
+ // Make sure the old contacts dictionary is opened. If it is already open, this is a
+ // no-op, so it's safe to call it anyways.
+ oldContactsDictionary.reopen(this);
+ dictionaryToUse = oldContactsDictionary;
+ } else {
+ dictionaryToUse = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
+ }
+
+ if (null != mSuggest) {
+ mSuggest.setContactsDictionary(dictionaryToUse);
+ }
}
/* package private */ void resetSuggestMainDict() {
@@ -557,7 +611,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// If orientation changed while predicting, commit the change
if (conf.orientation != mDisplayOrientation) {
mHandler.startOrientationChanging(mDisplayWidth, mDisplayHeight);
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
commitTyped(ic);
if (ic != null) ic.finishComposingText(); // For voice input
if (isShowingOptionDialog())
@@ -596,6 +650,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void onStartInputView(EditorInfo attribute, boolean restarting) {
+ mHandler.postRestoreKeyboardLayout();
if (mHandler.postStartInputView(attribute)) {
return;
}
@@ -649,7 +704,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (mSubtypeSwitcher.isKeyboardMode()) {
switcher.loadKeyboard(attribute, mSettingsValues);
- switcher.updateShiftState();
}
if (mCandidateView != null)
@@ -658,8 +712,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// Delay updating suggestions because keyboard input view may not be shown at this point.
mHandler.postUpdateSuggestions();
- updateCorrectionMode();
-
inputView.setKeyPreviewPopupEnabled(mSettingsValues.mKeyPreviewPopupOn,
mSettingsValues.mKeyPreviewPopupDismissDelay);
inputView.setProximityCorrectionEnabled(true);
@@ -738,7 +790,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
super.onFinishInput();
LatinImeLogger.commit();
- mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
mVoiceProxy.flushVoiceInputLogs(mConfigurationChanging);
@@ -751,6 +802,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void onFinishInputView(boolean finishingInput) {
super.onFinishInputView(finishingInput);
+ mKeyboardSwitcher.onFinishInputView();
KeyboardView inputView = mKeyboardSwitcher.getKeyboardView();
if (inputView != null) inputView.cancelAllMessages();
// Remove pending messages related to update suggestions
@@ -789,7 +841,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final boolean selectionChanged = (newSelStart != candidatesEnd
|| newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
- if (((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
+ if (!mExpectingUpdateSelection
+ && ((mComposingStringBuilder.length() > 0 && mHasUncommittedTypedChars)
|| mVoiceProxy.isVoiceInputHighlighted())
&& (selectionChanged || candidatesCleared)) {
if (candidatesCleared) {
@@ -807,7 +860,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
setPunctuationSuggestions();
}
TextEntryState.reset();
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.finishComposingText();
}
@@ -872,7 +925,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void hideWindow() {
LatinImeLogger.commit();
- mKeyboardSwitcher.onAutoCorrectionStateChanged(false);
+ mKeyboardSwitcher.onHideWindow();
if (TRACE) Debug.stopMethodTracing();
if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
@@ -917,7 +970,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
if (onEvaluateInputViewShown() && mCandidateViewContainer != null) {
final boolean shouldShowCandidates = shown
&& (needsInputViewShown ? mKeyboardSwitcher.isInputViewShown() : true);
- if (isExtractViewShown()) {
+ if (isFullscreenMode()) {
// No need to have extra space to show the key preview.
mCandidateViewContainer.setMinimumHeight(0);
mCandidateViewContainer.setVisibility(
@@ -1012,7 +1065,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
event.getAction(), event.getKeyCode(), event.getRepeatCount(),
event.getDeviceId(), event.getScanCode(),
KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_SHIFT_ON);
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null)
ic.sendKeyEvent(newEvent);
return true;
@@ -1022,12 +1075,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return super.onKeyUp(keyCode, event);
}
- public void commitTyped(InputConnection inputConnection) {
+ public void commitTyped(final InputConnection ic) {
if (!mHasUncommittedTypedChars) return;
mHasUncommittedTypedChars = false;
if (mComposingStringBuilder.length() > 0) {
- if (inputConnection != null) {
- inputConnection.commitText(mComposingStringBuilder, 1);
+ if (ic != null) {
+ ic.commitText(mComposingStringBuilder, 1);
}
mCommittedLength = mComposingStringBuilder.length();
TextEntryState.acceptedTyped(mComposingStringBuilder);
@@ -1038,7 +1091,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
public boolean getCurrentAutoCapsState() {
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
EditorInfo ei = getCurrentInputEditorInfo();
if (mSettingsValues.mAutoCap && ic != null && ei != null
&& ei.inputType != InputType.TYPE_NULL) {
@@ -1062,25 +1115,13 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
- private static boolean canBeFollowedByPeriod(final int codePoint) {
- // TODO: Check again whether there really ain't a better way to check this.
- // TODO: This should probably be language-dependant...
- return Character.isLetterOrDigit(codePoint)
- || codePoint == Keyboard.CODE_SINGLE_QUOTE
- || codePoint == Keyboard.CODE_DOUBLE_QUOTE
- || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
- || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
- || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
- || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
- }
-
private void maybeDoubleSpace() {
if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
final CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
if (lastThree != null && lastThree.length() == 3
- && canBeFollowedByPeriod(lastThree.charAt(0))
+ && Utils.canBeFollowedByPeriod(lastThree.charAt(0))
&& lastThree.charAt(1) == Keyboard.CODE_SPACE
&& lastThree.charAt(2) == Keyboard.CODE_SPACE
&& mHandler.isAcceptingDoubleSpaces()) {
@@ -1096,10 +1137,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
- private void maybeRemovePreviousPeriod(CharSequence text) {
- final InputConnection ic = getCurrentInputConnection();
- if (ic == null) return;
-
+ // "ic" must not null
+ private void maybeRemovePreviousPeriod(final InputConnection ic, CharSequence text) {
// When the text's first character is '.', remove the previous period
// if there is one.
CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
@@ -1139,25 +1178,31 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
private void onSettingsKeyPressed() {
- if (isShowingOptionDialog())
- return;
+ if (isShowingOptionDialog()) return;
if (InputMethodServiceCompatWrapper.CAN_HANDLE_ON_CURRENT_INPUT_METHOD_SUBTYPE_CHANGED) {
showSubtypeSelectorAndSettings();
- } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
+ } else if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, false /* exclude aux subtypes */)) {
showOptionsMenu();
} else {
launchSettings();
}
}
- private void onSettingsKeyLongPressed() {
- if (!isShowingOptionDialog()) {
- if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm)) {
+ // Virtual codes representing custom requests. These are used in onCustomRequest() below.
+ public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
+
+ @Override
+ public boolean onCustomRequest(int requestCode) {
+ if (isShowingOptionDialog()) return false;
+ switch (requestCode) {
+ case CODE_SHOW_INPUT_METHOD_PICKER:
+ if (Utils.hasMultipleEnabledIMEsOrSubtypes(mImm, true /* include aux subtypes */)) {
mImm.showInputMethodPicker();
- } else {
- launchSettings();
+ return true;
}
+ return false;
}
+ return false;
}
private boolean isShowingOptionDialog() {
@@ -1201,9 +1246,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
case Keyboard.CODE_SETTINGS:
onSettingsKeyPressed();
break;
- case Keyboard.CODE_SETTINGS_LONGPRESS:
- onSettingsKeyLongPressed();
- break;
case Keyboard.CODE_CAPSLOCK:
switcher.toggleCapsLock();
break;
@@ -1238,12 +1280,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
@Override
public void onTextInput(CharSequence text) {
mVoiceProxy.commitVoiceInput();
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
mRecorrection.abortRecorrection(false);
ic.beginBatchEdit();
commitTyped(ic);
- maybeRemovePreviousPeriod(text);
+ maybeRemovePreviousPeriod(ic, text);
ic.commitText(text, 1);
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
@@ -1293,14 +1335,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
TextEntryState.backspace();
if (TextEntryState.isUndoCommit()) {
- revertLastWord(deleteChar);
+ revertLastWord(ic);
ic.endBatchEdit();
return;
}
if (justReplacedDoubleSpace) {
- if (revertDoubleSpace()) {
- ic.endBatchEdit();
- return;
+ if (revertDoubleSpace(ic)) {
+ ic.endBatchEdit();
+ return;
}
}
@@ -1315,7 +1357,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// different behavior only in the case of picking the first
// suggestion (typed word). It's intentional to have made this
// inconsistent with backspacing after selecting other suggestions.
- revertLastWord(true /* deleteChar */);
+ revertLastWord(ic);
} else {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
if (mDeleteCount > DELETE_ACCELERATE_AT) {
@@ -1361,7 +1403,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
int code = primaryCode;
- if (isAlphabet(code) && isSuggestionsRequested() && !isCursorTouchingWord()) {
+ if ((isAlphabet(code) || mSettingsValues.isSymbolExcludedFromWordSeparators(code))
+ && isSuggestionsRequested() && !isCursorTouchingWord()) {
if (!mHasUncommittedTypedChars) {
mHasUncommittedTypedChars = true;
mComposingStringBuilder.setLength(0);
@@ -1398,7 +1441,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
mComposingStringBuilder.append((char) code);
mWordComposer.add(code, keyCodes, x, y);
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
// If it's the first letter, make note of auto-caps state
if (mWordComposer.size() == 1) {
@@ -1443,7 +1486,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// in Italian dov' should not be expanded to dove' because the elision
// requires the last vowel to be removed.
final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
- && !mInputTypeNoAutoCorrect && mHasDictionary;
+ && !mInputTypeNoAutoCorrect;
if (shouldAutoCorrect && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
pickedDefault = pickDefaultSuggestion(primaryCode);
} else {
@@ -1586,7 +1629,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
// getSuggestedWordBuilder handles gracefully a null value of prevWord
final SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
- mKeyboardSwitcher.getKeyboardView(), wordComposer, prevWord);
+ mKeyboardSwitcher.getKeyboardView(), wordComposer, prevWord,
+ mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
boolean autoCorrectionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
final CharSequence typedWord = wordComposer.getTypedWord();
@@ -1663,15 +1707,15 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mSettingsValues.mWordSeparators);
final boolean recorrecting = TextEntryState.isRecorrecting();
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
}
if (mApplicationSpecifiedCompletionOn && mApplicationSpecifiedCompletions != null
&& index >= 0 && index < mApplicationSpecifiedCompletions.length) {
- CompletionInfo ci = mApplicationSpecifiedCompletions[index];
if (ic != null) {
- ic.commitCompletion(ci);
+ final CompletionInfo completionInfo = mApplicationSpecifiedCompletions[index];
+ ic.commitCompletion(completionInfo);
}
mCommittedLength = suggestion.length();
if (mCandidateView != null) {
@@ -1698,7 +1742,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final int rawPrimaryCode = suggestion.charAt(0);
// Maybe apply the "bidi mirrored" conversions for parentheses
final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
- final int primaryCode = keyboard.isRtlKeyboard()
+ final int primaryCode = keyboard.mIsRtlKeyboard
? Key.getRtlParenthesisCode(rawPrimaryCode) : rawPrimaryCode;
final CharSequence beforeText = ic != null ? ic.getTextBeforeCursor(1, 0) : "";
@@ -1737,9 +1781,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
sendMagicSpace();
}
- // We should show the hint if the user pressed the first entry AND either:
+ // We should show the "Touch again to save" hint if the user pressed the first entry
+ // AND either:
// - There is no dictionary (we know that because we tried to load it => null != mSuggest
- // AND mHasDictionary is false)
+ // AND mSuggest.hasMainDictionary() is false)
// - There is a dictionary and the word is not in it
// Please note that if mSuggest is null, it means that everything is off: suggestion
// and correction, so we shouldn't try to show the hint
@@ -1747,7 +1792,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
// to do with the autocorrection setting.
final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null
// If there is no dictionary the hint should be shown.
- && (!mHasDictionary
+ && (!mSuggest.hasMainDictionary()
// If "suggestion" is not in the dictionary, the hint should be shown.
|| !AutoCorrection.isValidWord(
mSuggest.getUnigramDictionaries(), suggestion, true));
@@ -1783,10 +1828,10 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
* Commits the chosen word to the text field and saves it for later retrieval.
*/
private void commitBestWord(CharSequence bestWord) {
- KeyboardSwitcher switcher = mKeyboardSwitcher;
+ final KeyboardSwitcher switcher = mKeyboardSwitcher;
if (!switcher.isKeyboardAvailable())
return;
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
mVoiceProxy.rememberReplacedWord(bestWord, mSettingsValues.mWordSeparators);
SuggestedWords suggestedWords = mCandidateView.getSuggestions();
@@ -1811,7 +1856,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
final CharSequence prevWord = EditingUtils.getThisWord(getCurrentInputConnection(),
mSettingsValues.mWordSeparators);
SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
- mKeyboardSwitcher.getKeyboardView(), sEmptyWordComposer, prevWord);
+ mKeyboardSwitcher.getKeyboardView(), sEmptyWordComposer, prevWord,
+ mKeyboardSwitcher.getLatinKeyboard().getProximityInfo());
if (builder.size() > 0) {
// Explicitly supply an empty typed word (the no-second-arg version of
@@ -1878,7 +1924,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
public boolean isCursorTouchingWord() {
- InputConnection ic = getCurrentInputConnection();
+ final InputConnection ic = getCurrentInputConnection();
if (ic == null) return false;
CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
CharSequence toRight = ic.getTextAfterCursor(1, 0);
@@ -1895,36 +1941,34 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
return false;
}
- private boolean sameAsTextBeforeCursor(InputConnection ic, CharSequence text) {
+ // "ic" must not null
+ private boolean sameAsTextBeforeCursor(final InputConnection ic, CharSequence text) {
CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
return TextUtils.equals(text, beforeText);
}
- private void revertLastWord(boolean deleteChar) {
+ // "ic" must not null
+ private void revertLastWord(final InputConnection ic) {
if (mHasUncommittedTypedChars || mComposingStringBuilder.length() <= 0) {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
return;
}
- final InputConnection ic = getCurrentInputConnection();
- final CharSequence punctuation = ic.getTextBeforeCursor(1, 0);
- if (deleteChar) ic.deleteSurroundingText(1, 0);
+ final CharSequence separator = ic.getTextBeforeCursor(1, 0);
+ ic.deleteSurroundingText(1, 0);
final CharSequence textToTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
- final int toDeleteLength = (!TextUtils.isEmpty(textToTheLeft)
- && mSettingsValues.isWordSeparator(textToTheLeft.charAt(0)))
- ? mCommittedLength - 1 : mCommittedLength;
- ic.deleteSurroundingText(toDeleteLength, 0);
-
- // Re-insert punctuation only when the deleted character was word separator and the
- // composing text wasn't equal to the auto-corrected text.
- if (deleteChar
- && !TextUtils.isEmpty(punctuation)
- && mSettingsValues.isWordSeparator(punctuation.charAt(0))
+ ic.deleteSurroundingText(mCommittedLength, 0);
+
+ // Re-insert "separator" only when the deleted character was word separator and the
+ // composing text wasn't equal to the auto-corrected text which can be found before
+ // the cursor.
+ if (!TextUtils.isEmpty(separator)
+ && mSettingsValues.isWordSeparator(separator.charAt(0))
&& !TextUtils.equals(mComposingStringBuilder, textToTheLeft)) {
ic.commitText(mComposingStringBuilder, 1);
TextEntryState.acceptedTyped(mComposingStringBuilder);
- ic.commitText(punctuation, 1);
- TextEntryState.typedCharacter(punctuation.charAt(0), true,
+ ic.commitText(separator, 1);
+ TextEntryState.typedCharacter(separator.charAt(0), true,
WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
// Clear composing text
mComposingStringBuilder.setLength(0);
@@ -1937,9 +1981,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mHandler.postUpdateSuggestions();
}
- private boolean revertDoubleSpace() {
+ // "ic" must not null
+ private boolean revertDoubleSpace(final InputConnection ic) {
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);
@@ -1978,16 +2022,16 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettingsValues);
initSuggest();
loadSettings();
- mKeyboardSwitcher.updateShiftState();
}
@Override
public void onPress(int primaryCode, boolean withSliding) {
- if (mKeyboardSwitcher.isVibrateAndSoundFeedbackRequired()) {
+ final KeyboardSwitcher switcher = mKeyboardSwitcher;
+ switcher.registerWindowWidth();
+ if (switcher.isVibrateAndSoundFeedbackRequired()) {
vibrate();
playKeyClick(primaryCode);
}
- KeyboardSwitcher switcher = mKeyboardSwitcher;
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
switcher.onPressShift(withSliding);
@@ -2024,14 +2068,24 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
};
+ // update sound effect volume
+ private void updateSoundEffectVolume() {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (mAudioManager == null) return;
+ }
+ // This aligns with the current media volume minus 6dB
+ mFxVolume = (float) mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
+ / (float) mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) / 4.0f;
+ }
+
// update flags for silent mode
private void updateRingerMode() {
if (mAudioManager == null) {
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ if (mAudioManager == null) return;
}
- if (mAudioManager != null) {
- mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
- }
+ mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
}
private void playKeyClick(int primaryCode) {
@@ -2043,8 +2097,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
if (isSoundOn()) {
- // FIXME: Volume and enable should come from UI settings
- // FIXME: These should be triggered after auto-repeat logic
int sound = AudioManager.FX_KEYPRESS_STANDARD;
switch (primaryCode) {
case Keyboard.CODE_DELETE:
@@ -2057,7 +2109,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
sound = AudioManager.FX_KEYPRESS_SPACEBAR;
break;
}
- mAudioManager.playSoundEffect(sound, FX_VOLUME);
+ mAudioManager.playSoundEffect(sound, mFxVolume);
}
}
@@ -2083,9 +2135,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
private void updateCorrectionMode() {
// TODO: cleanup messy flags
- mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
final boolean shouldAutoCorrect = mSettingsValues.mAutoCorrectEnabled
- && !mInputTypeNoAutoCorrect && mHasDictionary;
+ && !mInputTypeNoAutoCorrect;
mCorrectionMode = (shouldAutoCorrect && mSettingsValues.mAutoCorrectEnabled)
? Suggest.CORRECTION_FULL
: (shouldAutoCorrect ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
@@ -2122,14 +2173,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
protected void launchSettings() {
- launchSettings(Settings.class);
+ launchSettingsClass(Settings.class);
}
public void launchDebugSettings() {
- launchSettings(DebugSettings.class);
+ launchSettingsClass(DebugSettings.class);
}
- protected void launchSettings(Class<? extends PreferenceActivity> settingsClass) {
+ protected void launchSettingsClass(Class<? extends PreferenceActivity> settingsClass) {
handleClose();
Intent intent = new Intent();
intent.setClass(LatinIME.this, settingsClass);
@@ -2163,8 +2214,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
};
final AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setIcon(R.drawable.ic_dialog_keyboard)
- .setNegativeButton(android.R.string.cancel, null)
.setItems(items, listener)
.setTitle(title);
showOptionDialogInternal(builder.create());
@@ -2191,8 +2240,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
};
final AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setIcon(R.drawable.ic_dialog_keyboard)
- .setNegativeButton(android.R.string.cancel, null)
.setItems(items, listener)
.setTitle(title);
showOptionDialogInternal(builder.create());
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index e44ae29d9..4c2627be3 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -61,7 +61,7 @@ public class Settings extends InputMethodSettingsActivity
public static final String PREF_KEY_PREVIEW_POPUP_ON = "popup_on";
public static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled";
public static final String PREF_AUTO_CAP = "auto_cap";
- public static final String PREF_SETTINGS_KEY = "settings_key";
+ public static final String PREF_SHOW_SETTINGS_KEY = "show_settings_key";
public static final String PREF_VOICE_SETTINGS_KEY = "voice_mode";
public static final String PREF_INPUT_LANGUAGE = "input_language";
public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
@@ -103,6 +103,7 @@ public class Settings extends InputMethodSettingsActivity
public final String mMagicSpaceSwappers;
public final String mSuggestPuncs;
public final SuggestedWords mSuggestPuncList;
+ private final String mSymbolsExcludedFromWordSeparators;
// From preferences:
public final boolean mSoundOn; // Sound setting private to Latin IME (see mSilentModeOn)
@@ -118,6 +119,7 @@ public class Settings extends InputMethodSettingsActivity
public final boolean mBigramPredictionEnabled;
public final boolean mUseContactsDict;
+ private final boolean mShowSettingsKey;
private final boolean mVoiceKeyEnabled;
private final boolean mVoiceKeyOnMain;
@@ -151,10 +153,13 @@ public class Settings extends InputMethodSettingsActivity
mMagicSpaceSwappers = res.getString(R.string.magic_space_swapping_symbols);
String wordSeparators = mMagicSpaceStrippers + mMagicSpaceSwappers
+ res.getString(R.string.magic_space_promoting_symbols);
- final String notWordSeparators = res.getString(R.string.non_word_separator_symbols);
- for (int i = notWordSeparators.length() - 1; i >= 0; --i) {
- wordSeparators = wordSeparators.replace(notWordSeparators.substring(i, i + 1), "");
+ final String symbolsExcludedFromWordSeparators =
+ res.getString(R.string.symbols_excluded_from_word_separators);
+ for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) {
+ wordSeparators = wordSeparators.replace(
+ symbolsExcludedFromWordSeparators.substring(i, i + 1), "");
}
+ mSymbolsExcludedFromWordSeparators = symbolsExcludedFromWordSeparators;
mWordSeparators = wordSeparators;
mSuggestPuncs = res.getString(R.string.suggested_punctuations);
// TODO: it would be nice not to recreate this each time we change the configuration
@@ -165,21 +170,20 @@ public class Settings extends InputMethodSettingsActivity
mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
res.getBoolean(R.bool.config_default_sound_enabled));
-
mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res);
mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res);
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
-
mAutoCorrectEnabled = isAutoCorrectEnabled(prefs, res);
mBigramSuggestionEnabled = mAutoCorrectEnabled
&& isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled);
mBigramPredictionEnabled = mBigramSuggestionEnabled
&& isBigramPredictionEnabled(prefs, res);
-
mAutoCorrectionThreshold = getAutoCorrectionThreshold(prefs, res);
-
mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true);
-
+ final boolean defaultShowSettingsKey = res.getBoolean(
+ R.bool.config_default_show_settings_key);
+ mShowSettingsKey = prefs.getBoolean(Settings.PREF_SHOW_SETTINGS_KEY,
+ defaultShowSettingsKey);
final String voiceModeMain = res.getString(R.string.voice_mode_main);
final String voiceModeOff = res.getString(R.string.voice_mode_off);
final String voiceMode = prefs.getString(PREF_VOICE_SETTINGS_KEY, voiceModeMain);
@@ -197,6 +201,10 @@ public class Settings extends InputMethodSettingsActivity
return mWordSeparators.contains(String.valueOf((char)code));
}
+ public boolean isSymbolExcludedFromWordSeparators(int code) {
+ return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code));
+ }
+
public boolean isMagicSpaceStripper(int code) {
return mMagicSpaceStrippers.contains(String.valueOf((char)code));
}
@@ -284,6 +292,10 @@ public class Settings extends InputMethodSettingsActivity
return builder.setIsPunctuationSuggestions().build();
}
+ public boolean isSettingsKeyEnabled(EditorInfo attribute) {
+ return mShowSettingsKey;
+ }
+
public boolean isVoiceKeyEnabled(EditorInfo attribute) {
final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled();
final int inputType = (attribute != null) ? attribute.inputType : 0;
@@ -298,9 +310,9 @@ public class Settings extends InputMethodSettingsActivity
private PreferenceScreen mInputLanguageSelection;
private ListPreference mVoicePreference;
- private ListPreference mSettingsKeyPreference;
+ private CheckBoxPreference mShowSettingsKeyPreference;
private ListPreference mShowCorrectionSuggestionsPreference;
- private ListPreference mAutoCorrectionThreshold;
+ private ListPreference mAutoCorrectionThresholdPreference;
private ListPreference mKeyPreviewPopupDismissDelay;
// Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary
private CheckBoxPreference mBigramSuggestion;
@@ -317,7 +329,7 @@ public class Settings extends InputMethodSettingsActivity
private void ensureConsistencyOfAutoCorrectionSettings() {
final String autoCorrectionOff = getResources().getString(
R.string.auto_correction_threshold_mode_index_off);
- final String currentSetting = mAutoCorrectionThreshold.getValue();
+ final String currentSetting = mAutoCorrectionThresholdPreference.getValue();
mBigramSuggestion.setEnabled(!currentSetting.equals(autoCorrectionOff));
mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff));
}
@@ -345,7 +357,7 @@ public class Settings extends InputMethodSettingsActivity
mInputLanguageSelection = (PreferenceScreen) findPreference(PREF_SUBTYPES);
mInputLanguageSelection.setOnPreferenceClickListener(this);
mVoicePreference = (ListPreference) findPreference(PREF_VOICE_SETTINGS_KEY);
- mSettingsKeyPreference = (ListPreference) findPreference(PREF_SETTINGS_KEY);
+ mShowSettingsKeyPreference = (CheckBoxPreference) findPreference(PREF_SHOW_SETTINGS_KEY);
mShowCorrectionSuggestionsPreference =
(ListPreference) findPreference(PREF_SHOW_SUGGESTIONS_SETTING);
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
@@ -355,7 +367,8 @@ public class Settings extends InputMethodSettingsActivity
mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
.equals(mVoiceModeOff));
- mAutoCorrectionThreshold = (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
+ mAutoCorrectionThresholdPreference =
+ (ListPreference) findPreference(PREF_AUTO_CORRECTION_THRESHOLD);
mBigramSuggestion = (CheckBoxPreference) findPreference(PREF_BIGRAM_SUGGESTIONS);
mBigramPrediction = (CheckBoxPreference) findPreference(PREF_BIGRAM_PREDICTIONS);
mDebugSettingsPreference = findPreference(PREF_DEBUG_SETTINGS);
@@ -376,7 +389,7 @@ public class Settings extends InputMethodSettingsActivity
final boolean showSettingsKeyOption = res.getBoolean(
R.bool.config_enable_show_settings_key_option);
if (!showSettingsKeyOption) {
- generalSettings.removePreference(mSettingsKeyPreference);
+ generalSettings.removePreference(mShowSettingsKeyPreference);
}
final boolean showVoiceKeyOption = res.getBoolean(
@@ -457,7 +470,6 @@ public class Settings extends InputMethodSettingsActivity
} else {
getPreferenceScreen().removePreference(mVoicePreference);
}
- updateSettingsKeySummary();
updateShowCorrectionSuggestionsSummary();
updateKeyPreviewPopupDelaySummary();
}
@@ -489,7 +501,6 @@ public class Settings extends InputMethodSettingsActivity
mVoiceOn = !(prefs.getString(PREF_VOICE_SETTINGS_KEY, mVoiceModeOff)
.equals(mVoiceModeOff));
updateVoiceModeSummary();
- updateSettingsKeySummary();
updateShowCorrectionSuggestionsSummary();
updateKeyPreviewPopupDelaySummary();
}
@@ -513,12 +524,6 @@ public class Settings extends InputMethodSettingsActivity
mShowCorrectionSuggestionsPreference.getValue())]);
}
- private void updateSettingsKeySummary() {
- mSettingsKeyPreference.setSummary(
- getResources().getStringArray(R.array.settings_key_modes)
- [mSettingsKeyPreference.findIndexOfValue(mSettingsKeyPreference.getValue())]);
- }
-
private void updateKeyPreviewPopupDelaySummary() {
final ListPreference lp = mKeyPreviewPopupDismissDelay;
lp.setSummary(lp.getEntries()[lp.findIndexOfValue(lp.getValue())]);
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index f10b1b845..0a391a77e 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -318,7 +318,7 @@ public class SubtypeSwitcher {
// when the API level is 10 or previous.
mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
}
- }.execute();
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public Drawable getShortcutIcon() {
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index c2452b947..a2d66f398 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,6 +22,8 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.View;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -86,6 +88,7 @@ public class Suggest implements Dictionary.WordCallback {
private AutoCorrection mAutoCorrection;
private Dictionary mMainDict;
+ private ContactsDictionary mContactsDict;
private WhitelistDictionary mWhiteListDictionary;
private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
@@ -189,10 +192,16 @@ public class Suggest implements Dictionary.WordCallback {
mCorrectionMode = mode;
}
+ // The main dictionary could have been loaded asynchronously. Don't cache the return value
+ // of this method.
public boolean hasMainDictionary() {
return mMainDict != null;
}
+ public ContactsDictionary getContactsDictionary() {
+ return mContactsDict;
+ }
+
public Map<String, Dictionary> getUnigramDictionaries() {
return mUnigramDictionaries;
}
@@ -214,7 +223,8 @@ public class Suggest implements Dictionary.WordCallback {
* the contacts dictionary by passing null to this method. In this case no contacts dictionary
* won't be used.
*/
- public void setContactsDictionary(Dictionary contactsDictionary) {
+ public void setContactsDictionary(ContactsDictionary contactsDictionary) {
+ mContactsDict = contactsDictionary;
addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
}
@@ -263,9 +273,10 @@ public class Suggest implements Dictionary.WordCallback {
* @param prevWordForBigram previous word (used only for bigram)
* @return suggested words object.
*/
- public SuggestedWords getSuggestions(View view, WordComposer wordComposer,
- CharSequence prevWordForBigram) {
- return getSuggestedWordBuilder(view, wordComposer, prevWordForBigram).build();
+ public SuggestedWords getSuggestions(final View view, final WordComposer wordComposer,
+ final CharSequence prevWordForBigram, final ProximityInfo proximityInfo) {
+ return getSuggestedWordBuilder(view, wordComposer, prevWordForBigram,
+ proximityInfo).build();
}
private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
@@ -299,8 +310,9 @@ public class Suggest implements Dictionary.WordCallback {
}
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
- public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
- CharSequence prevWordForBigram) {
+ public SuggestedWords.Builder getSuggestedWordBuilder(final View view,
+ final WordComposer wordComposer, CharSequence prevWordForBigram,
+ final ProximityInfo proximityInfo) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
mAutoCorrection.init();
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
@@ -365,7 +377,7 @@ public class Suggest implements Dictionary.WordCallback {
if (key.equals(DICT_KEY_USER_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
continue;
final Dictionary dictionary = mUnigramDictionaries.get(key);
- dictionary.getWords(wordComposer, this);
+ dictionary.getWords(wordComposer, this, proximityInfo);
}
}
CharSequence autoText = null;
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index b77cbd199..c1c46fa47 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -31,7 +31,7 @@ public class SuggestedWords {
public final boolean mTypedWordValid;
public final boolean mHasMinimalSuggestion;
public final boolean mIsPunctuationSuggestions;
- public final List<SuggestedWordInfo> mSuggestedWordInfoList;
+ private final List<SuggestedWordInfo> mSuggestedWordInfoList;
private SuggestedWords(List<CharSequence> words, boolean typedWordValid,
boolean hasMinimalSuggestion, boolean isPunctuationSuggestions,
@@ -55,6 +55,10 @@ public class SuggestedWords {
return mWords.get(pos);
}
+ public SuggestedWordInfo getInfo(int pos) {
+ return mSuggestedWordInfoList != null ? mSuggestedWordInfoList.get(pos) : null;
+ }
+
public boolean hasAutoCorrectionWord() {
return mHasMinimalSuggestion && size() > 1 && !mTypedWordValid;
}
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index f93d24fe6..6608d8268 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -26,6 +26,8 @@ import android.net.Uri;
import android.os.RemoteException;
import android.provider.UserDictionary.Words;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
public class UserDictionary extends ExpandableDictionary {
private static final String[] PROJECTION_QUERY = {
@@ -150,8 +152,9 @@ public class UserDictionary extends ExpandableDictionary {
}
@Override
- public synchronized void getWords(final WordComposer codes, final WordCallback callback) {
- super.getWords(codes, callback);
+ public synchronized void getWords(final WordComposer codes, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
+ super.getWords(codes, callback, proximityInfo);
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index 6bdc0a857..1a6260a4e 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -111,35 +111,43 @@ public class Utils {
}
}
- public static boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManagerCompatWrapper imm) {
+ public static boolean hasMultipleEnabledIMEsOrSubtypes(
+ final InputMethodManagerCompatWrapper imm,
+ final boolean shouldIncludeAuxiliarySubtypes) {
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>();
+ // Number of the filtered IMEs
+ int filteredImisCount = 0;
- outerloop:
for (InputMethodInfoCompatWrapper imi : enabledImis) {
// We can return true immediately after we find two or more filtered IMEs.
- if (filteredImis.size() > 1) return true;
+ if (filteredImisCount > 1) return true;
final List<InputMethodSubtypeCompatWrapper> subtypes =
imm.getEnabledInputMethodSubtypeList(imi, true);
- // IMEs that have no subtypes should be included.
+ // IMEs that have no subtypes should be counted.
if (subtypes.isEmpty()) {
- filteredImis.add(imi);
+ ++filteredImisCount;
continue;
}
- // IMEs that have one or more non-auxiliary subtypes should be included.
+
+ int auxCount = 0;
for (InputMethodSubtypeCompatWrapper subtype : subtypes) {
- if (!subtype.isAuxiliary()) {
- filteredImis.add(imi);
- continue outerloop;
+ if (subtype.isAuxiliary()) {
+ ++auxCount;
}
}
+ final int nonAuxCount = subtypes.size() - auxCount;
+
+ // IMEs that have one or more non-auxiliary subtypes should be counted.
+ // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary
+ // subtypes should be counted as well.
+ if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) {
+ ++filteredImisCount;
+ continue;
+ }
}
- return filteredImis.size() > 1
+ return filteredImisCount > 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;
@@ -190,6 +198,18 @@ public class Utils {
}
}
+ public static boolean canBeFollowedByPeriod(final int codePoint) {
+ // TODO: Check again whether there really ain't a better way to check this.
+ // TODO: This should probably be language-dependant...
+ return Character.isLetterOrDigit(codePoint)
+ || codePoint == Keyboard.CODE_SINGLE_QUOTE
+ || codePoint == Keyboard.CODE_DOUBLE_QUOTE
+ || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS
+ || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET
+ || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET
+ || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET;
+ }
+
/* package */ static class RingCharBuffer {
private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
@@ -546,11 +566,11 @@ public class Utils {
}
}
- public static int getKeyboardMode(EditorInfo attribute) {
- if (attribute == null)
+ public static int getKeyboardMode(EditorInfo editorInfo) {
+ if (editorInfo == null)
return KeyboardId.MODE_TEXT;
- final int inputType = attribute.inputType;
+ final int inputType = editorInfo.inputType;
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
switch (inputType & InputType.TYPE_MASK_CLASS) {
@@ -587,11 +607,11 @@ public class Utils {
}
public static boolean inPrivateImeOptions(String packageName, String key,
- EditorInfo attribute) {
- if (attribute == null)
+ EditorInfo editorInfo) {
+ if (editorInfo == null)
return false;
return containsInCsv(packageName != null ? packageName + "." + key : key,
- attribute.privateImeOptions);
+ editorInfo.privateImeOptions);
}
/**
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
index 4377373d2..639c96681 100644
--- a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
+++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
@@ -21,6 +21,8 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
import java.util.HashMap;
public class WhitelistDictionary extends Dictionary {
@@ -89,7 +91,8 @@ public class WhitelistDictionary extends Dictionary {
// Not used for WhitelistDictionary. We use getWhitelistedWord() in Suggest.java instead
@Override
- public void getWords(WordComposer composer, WordCallback callback) {
+ public void getWords(final WordComposer composer, final WordCallback callback,
+ final ProximityInfo proximityInfo) {
}
@Override
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 156510b40..649774d78 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -16,19 +16,183 @@
package com.android.inputmethod.latin.spellcheck;
+import android.content.Intent;
+import android.content.res.Resources;
import android.service.textservice.SpellCheckerService;
+import android.service.textservice.SpellCheckerService.Session;
+import android.util.Log;
import android.view.textservice.SuggestionsInfo;
import android.view.textservice.TextInfo;
+import com.android.inputmethod.compat.ArraysCompatUtils;
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.ProximityInfo;
+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.Arrays;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
/**
* Service for spell checking, using LatinIME's dictionaries and mechanisms.
*/
public class AndroidSpellCheckerService extends SpellCheckerService {
+ private static final String TAG = AndroidSpellCheckerService.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final int POOL_SIZE = 2;
+
+ private final static String[] emptyArray = new String[0];
+ private Map<String, DictionaryPool> mDictionaryPools =
+ Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+
@Override
- public SuggestionsInfo getSuggestions(TextInfo textInfo, int suggestionsLimit,
- String locale) {
- // TODO: implement this
- String[] candidates = new String[] {"candidate1", "candidate2", "candidate3"};
- return new SuggestionsInfo(0, candidates);
+ public Session createSession() {
+ return new AndroidSpellCheckerSession();
+ }
+
+ private static class SuggestionsGatherer implements WordCallback {
+ private final int DEFAULT_SUGGESTION_LENGTH = 16;
+ private final String[] mSuggestions;
+ private final int[] mScores;
+ private final int mMaxLength;
+ private int mLength = 0;
+
+ SuggestionsGatherer(final int maxLength) {
+ mMaxLength = maxLength;
+ mSuggestions = new String[mMaxLength];
+ mScores = new int[mMaxLength];
+ }
+
+ @Override
+ synchronized public boolean addWord(char[] word, int wordOffset, int wordLength, int score,
+ int dicTypeId, DataType dataType) {
+ 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 insertIndex = positionIndex >= 0 ? positionIndex : -positionIndex - 1;
+
+ if (mLength < mMaxLength) {
+ final int copyLen = mLength - insertIndex;
+ ++mLength;
+ System.arraycopy(mScores, insertIndex, mScores, insertIndex + 1, copyLen);
+ System.arraycopy(mSuggestions, insertIndex, mSuggestions, insertIndex + 1, copyLen);
+ } else {
+ if (insertIndex == 0) return true;
+ System.arraycopy(mScores, 1, mScores, 0, insertIndex);
+ System.arraycopy(mSuggestions, 1, mSuggestions, 0, insertIndex);
+ }
+ mScores[insertIndex] = score;
+ mSuggestions[insertIndex] = new String(word, wordOffset, wordLength);
+
+ return true;
+ }
+
+ public String[] getGatheredSuggestions() {
+ if (0 == mLength) return null;
+
+ final String[] results = new String[mLength];
+ for (int i = mLength - 1; i >= 0; --i) {
+ results[mLength - i - 1] = mSuggestions[i];
+ }
+ return results;
+ }
+ }
+
+ @Override
+ public boolean onUnbind(final Intent intent) {
+ final Map<String, DictionaryPool> oldPools = mDictionaryPools;
+ mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>());
+ for (DictionaryPool pool : oldPools.values()) {
+ pool.close();
+ }
+ return false;
+ }
+
+ private DictionaryPool getDictionaryPool(final String locale) {
+ DictionaryPool pool = mDictionaryPools.get(locale);
+ if (null == pool) {
+ final Locale localeObject = Utils.constructLocaleFromString(locale);
+ pool = new DictionaryPool(POOL_SIZE, this, localeObject);
+ mDictionaryPools.put(locale, pool);
+ }
+ return pool;
+ }
+
+ public DictAndProximity createDictAndProximity(final Locale locale) {
+ final ProximityInfo proximityInfo = ProximityInfo.createSpellCheckerProximityInfo();
+ final Resources resources = getResources();
+ final int fallbackResourceId = Utils.getMainDictionaryResourceId(resources);
+ final Dictionary dictionary =
+ DictionaryFactory.createDictionaryFromManager(this, locale, fallbackResourceId);
+ return new DictAndProximity(dictionary, proximityInfo);
+ }
+
+ private class AndroidSpellCheckerSession extends Session {
+ // Immutable, but need the locale which is not available in the constructor yet
+ DictionaryPool mDictionaryPool;
+
+ @Override
+ public void onCreate() {
+ mDictionaryPool = getDictionaryPool(getLocale());
+ }
+
+ // 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 argument. It may split or group words, and
+ * even perform grammatical analysis.
+ */
+ @Override
+ public SuggestionsInfo onGetSuggestions(final TextInfo textInfo,
+ final int suggestionsLimit) {
+ final String text = textInfo.getText();
+
+ final SuggestionsGatherer suggestionsGatherer =
+ new SuggestionsGatherer(suggestionsLimit);
+ final WordComposer composer = new WordComposer();
+ final int length = text.length();
+ for (int i = 0; i < length; ++i) {
+ final int character = text.codePointAt(i);
+ final int proximityIndex = SpellCheckerProximityInfo.getIndexOf(character);
+ final int[] proximities;
+ if (-1 == proximityIndex) {
+ proximities = new int[] { character };
+ } else {
+ proximities = Arrays.copyOfRange(SpellCheckerProximityInfo.PROXIMITY,
+ proximityIndex, proximityIndex + SpellCheckerProximityInfo.ROW_SIZE);
+ }
+ composer.add(character, proximities,
+ WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
+ }
+
+ boolean isInDict = true;
+ try {
+ final DictAndProximity dictInfo = mDictionaryPool.take();
+ dictInfo.mDictionary.getWords(composer, suggestionsGatherer,
+ dictInfo.mProximityInfo);
+ isInDict = dictInfo.mDictionary.isValidWord(text);
+ if (!mDictionaryPool.offer(dictInfo)) {
+ Log.e(TAG, "Can't re-insert a dictionary into its pool");
+ }
+ } catch (InterruptedException e) {
+ // I don't think this can happen.
+ return new SuggestionsInfo(0, new String[0]);
+ }
+
+ final String[] suggestions = suggestionsGatherer.getGatheredSuggestions();
+
+ final int flags =
+ (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0)
+ | (null != suggestions
+ ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0);
+ return new SuggestionsInfo(flags, suggestions);
+ }
}
}
diff --git a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
index eb740e111..3dbbd40cd 100644
--- a/java/src/com/android/inputmethod/latin/PrivateBinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java
@@ -14,16 +14,19 @@
* the License.
*/
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.spellcheck;
-import android.content.Context;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.keyboard.ProximityInfo;
-import java.util.List;
-import java.util.Locale;
-
-class PrivateBinaryDictionaryGetter {
- private PrivateBinaryDictionaryGetter() {}
- public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context) {
- return null;
+/**
+ * A simple container for both a Dictionary and a ProximityInfo.
+ */
+public class DictAndProximity {
+ public final Dictionary mDictionary;
+ public final ProximityInfo mProximityInfo;
+ public DictAndProximity(final Dictionary dictionary, final ProximityInfo proximityInfo) {
+ mDictionary = dictionary;
+ mProximityInfo = proximityInfo;
}
}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
new file mode 100644
index 000000000..ee294f6b0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -0,0 +1,78 @@
+/*
+ * 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 java.util.Locale;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * A blocking queue that creates dictionaries up to a certain limit as necessary.
+ */
+public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> {
+ private final AndroidSpellCheckerService mService;
+ private final int mMaxSize;
+ private final Locale mLocale;
+ private int mSize;
+ private volatile boolean mClosed;
+
+ public DictionaryPool(final int maxSize, final AndroidSpellCheckerService service,
+ final Locale locale) {
+ super();
+ mMaxSize = maxSize;
+ mService = service;
+ mLocale = locale;
+ mSize = 0;
+ mClosed = false;
+ }
+
+ @Override
+ public DictAndProximity take() throws InterruptedException {
+ final DictAndProximity dict = poll();
+ if (null != dict) return dict;
+ synchronized(this) {
+ if (mSize >= mMaxSize) {
+ // Our pool is already full. Wait until some dictionary is ready.
+ return super.take();
+ } else {
+ ++mSize;
+ return mService.createDictAndProximity(mLocale);
+ }
+ }
+ }
+
+ public void close() {
+ synchronized(this) {
+ mClosed = true;
+ for (DictAndProximity dict : this) {
+ dict.mDictionary.close();
+ }
+ clear();
+ }
+ }
+
+ @Override
+ public boolean offer(final DictAndProximity dict) {
+ if (mClosed) {
+ dict.mDictionary.close();
+ return false;
+ } else {
+ return super.offer(dict);
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java
deleted file mode 100644
index 63c6d69d7..000000000
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellChecker.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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();
- }
-}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
new file mode 100644
index 000000000..abcf7e52a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java
@@ -0,0 +1,94 @@
+/*
+ * 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 com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+public class SpellCheckerProximityInfo {
+ final private static int NUL = KeyDetector.NOT_A_CODE;
+
+ // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside
+ // native code - this value is passed at creation of the binary object and reused
+ // as the size of the passed array afterwards so they can't be different.
+ final public static int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
+
+ // This is a map from the code point to the index in the PROXIMITY array.
+ // At the time the native code to read the binary dictionary needs the proximity info be passed
+ // as a flat array spaced by MAX_PROXIMITY_CHARS_SIZE columns, one for each input character.
+ // Since we need to build such an array, we want to be able to search in our big proximity data
+ // quickly by character, and a map is probably the best way to do this.
+ final private static TreeMap<Integer, Integer> INDICES = new TreeMap<Integer, Integer>();
+
+ // The proximity here is the union of
+ // - the proximity for a QWERTY keyboard.
+ // - the proximity for an AZERTY keyboard.
+ // - the proximity for a QWERTZ keyboard.
+ // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other.
+ //
+ // The reasoning behind this construction is, almost any alphabetic text we may want
+ // to spell check has been entered with one of the keyboards above. Also, specifically
+ // to English, many spelling errors consist of the last vowel of the word being wrong
+ // because in English vowels tend to merge with each other in pronunciation.
+ final public static int[] PROXIMITY = {
+ 'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ 'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL,
+ 's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL,
+ 'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+
+ 'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL,
+ 'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ 'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,
+ };
+ static {
+ for (int i = 0; i < PROXIMITY.length; i += ROW_SIZE) {
+ if (NUL != PROXIMITY[i]) INDICES.put(PROXIMITY[i], i);
+ }
+ }
+ public static int getIndexOf(int characterCode) {
+ final Integer result = INDICES.get(characterCode);
+ if (null == result) return -1;
+ return result;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
new file mode 100644
index 000000000..483679a55
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.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.spellcheck;
+
+import com.android.inputmethod.latin.R;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+import java.util.List;
+
+/**
+ * Spell checker preference screen.
+ */
+public class SpellCheckerSettingsActivity extends PreferenceActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public Intent getIntent() {
+ final Intent modIntent = new Intent(super.getIntent());
+ modIntent.putExtra(EXTRA_SHOW_FRAGMENT, SpellCheckerSettingsFragment.class.getName());
+ modIntent.putExtra(EXTRA_NO_HEADERS, true);
+ return modIntent;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
new file mode 100644
index 000000000..9b821be35
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -0,0 +1,41 @@
+/**
+ * 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 com.android.inputmethod.latin.R;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+/**
+ * Preference screen.
+ */
+public class SpellCheckerSettingsFragment extends PreferenceFragment {
+ private static final String TAG = SpellCheckerSettingsFragment.class.getSimpleName();
+
+ /**
+ * Empty constructor for fragment generation.
+ */
+ public SpellCheckerSettingsFragment() {
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ addPreferencesFromResource(R.xml.spell_checker_settings);
+ }
+}