diff options
Diffstat (limited to 'java/src')
26 files changed, 585 insertions, 613 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 23886ad97..7396f0518 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -95,7 +95,7 @@ public class Key { public boolean mPressed; /** If this is a sticky key, is it on? */ public boolean mOn; - /** Key is enabled or not. */ + /** Key is enabled and responds on press */ public boolean mEnabled = true; private final static int[] KEY_STATE_NORMAL_ON = { @@ -226,6 +226,7 @@ public class Key { mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false); mModifier = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier, false); mSticky = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky, false); + mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true); mEdgeFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyEdgeFlags, 0) | row.mRowEdgeFlags; diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java index e7a9d8513..1a4f90195 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java @@ -17,10 +17,12 @@ package com.android.inputmethod.keyboard; import java.util.Arrays; +import java.util.HashMap; import java.util.List; public abstract class KeyDetector { public static final int NOT_A_KEY = -1; + public static final int NOT_A_CODE = -1; protected Keyboard mKeyboard; @@ -104,8 +106,35 @@ public abstract class KeyDetector { * * @param x The x-coordinate of a touch point * @param y The y-coordinate of a touch point - * @param allKeys All nearby key indices are returned in this array + * @param allCodes All nearby key code except functional key are returned in this array * @return The nearest key index */ - abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys); + abstract public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes); + + /** + * Compute the most common key width in order to use it as proximity key detection threshold. + * + * @param keyboard The keyboard to compute the most common key width + * @return The most common key width in the keyboard + */ + public static int getMostCommonKeyWidth(final Keyboard keyboard) { + if (keyboard == null) return 0; + final List<Key> keys = keyboard.getKeys(); + if (keys == null || keys.size() == 0) return 0; + final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>(); + int maxCount = 0; + int mostCommonWidth = 0; + for (final Key key : keys) { + 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; + } + } + return mostCommonWidth; + } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/KeyStyles.java index 44ec53181..169f2e6c3 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyStyles.java +++ b/java/src/com/android/inputmethod/keyboard/KeyStyles.java @@ -188,6 +188,7 @@ public class KeyStyles { readBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier); readBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky); readBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable); + readBoolean(keyAttr, R.styleable.Keyboard_Key_enabled); } private void readDrawable(TypedArray a, int index) { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index db86740c3..d09f6786e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -17,6 +17,7 @@ package com.android.inputmethod.keyboard; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.Utils; import android.view.inputmethod.EditorInfo; @@ -41,29 +42,35 @@ public class KeyboardId { public final int mMode; public final int mXmlId; public final int mColorScheme; + public final boolean mPasswordInput; public final boolean mHasSettingsKey; public final boolean mVoiceKeyEnabled; public final boolean mHasVoiceKey; - public final int mImeOptions; + public final int mImeAction; public final boolean mEnableShiftLock; public final String mXmlName; private final int mHashCode; - public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int mode, - int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled, boolean hasVoiceKey, - int imeOptions, boolean enableShiftLock) { + public KeyboardId(String xmlName, int xmlId, int colorScheme, Locale locale, int orientation, + int mode, EditorInfo attribute, boolean hasSettingsKey, boolean voiceKeyEnabled, + boolean hasVoiceKey, boolean enableShiftLock) { + final int inputType = (attribute != null) ? attribute.inputType : 0; + final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; this.mLocale = locale; this.mOrientation = orientation; this.mMode = mode; this.mXmlId = xmlId; this.mColorScheme = colorScheme; + this.mPasswordInput = Utils.isPasswordInputType(inputType) + || Utils.isVisiblePasswordInputType(inputType); this.mHasSettingsKey = hasSettingsKey; this.mVoiceKeyEnabled = voiceKeyEnabled; this.mHasVoiceKey = hasVoiceKey; - // We are interested only in IME_MASK_ACTION enum value and IME_FLAG_NO_ENTER_ACTION. - this.mImeOptions = imeOptions - & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION); + // 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; @@ -73,10 +80,11 @@ public class KeyboardId { mode, xmlId, colorScheme, + mPasswordInput, hasSettingsKey, voiceKeyEnabled, hasVoiceKey, - imeOptions, + mImeAction, enableShiftLock, }); } @@ -112,10 +120,11 @@ public class KeyboardId { && other.mMode == this.mMode && other.mXmlId == this.mXmlId && other.mColorScheme == this.mColorScheme + && other.mPasswordInput == this.mPasswordInput && other.mHasSettingsKey == this.mHasSettingsKey && other.mVoiceKeyEnabled == this.mVoiceKeyEnabled && other.mHasVoiceKey == this.mHasVoiceKey - && other.mImeOptions == this.mImeOptions + && other.mImeAction == this.mImeAction && other.mEnableShiftLock == this.mEnableShiftLock; } @@ -126,17 +135,19 @@ public class KeyboardId { @Override public String toString() { - return String.format("[%s.xml %s %s %s imeOptions=%s %s%s%s%s%s]", + return String.format("[%s.xml %s %s %s imeAction=%s %s%s%s%s%s%s]", mXmlName, mLocale, (mOrientation == 1 ? "port" : "land"), modeName(mMode), - imeOptionsName(mImeOptions), - colorSchemeName(mColorScheme), + imeOptionsName(mImeAction), + (mPasswordInput ? " passwordInput" : ""), (mHasSettingsKey ? " hasSettingsKey" : ""), (mVoiceKeyEnabled ? " voiceKeyEnabled" : ""), (mHasVoiceKey ? " hasVoiceKey" : ""), - (mEnableShiftLock ? " enableShiftLock" : "")); + (mEnableShiftLock ? " enableShiftLock" : ""), + colorSchemeName(mColorScheme) + ); } public static String modeName(int mode) { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java index e8324e5fd..df64ad5af 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java @@ -103,7 +103,7 @@ import java.util.List; */ public class KeyboardParser { - private static final String TAG = "KeyboardParser"; + private static final String TAG = KeyboardParser.class.getSimpleName(); private static final boolean DEBUG = false; // Keyboard XML Tags @@ -279,8 +279,8 @@ public class KeyboardParser { checkEndTag(TAG_KEY, parser); } else { Key key = new Key(mResources, row, mCurrentX, mCurrentY, parser, mKeyStyles); - if (DEBUG) Log.d(TAG, String.format("<%s keyLabel=%s code=%d popupCharacters=%s />", - TAG_KEY, key.mLabel, key.mCode, + if (DEBUG) Log.d(TAG, String.format("<%s%s keyLabel=%s code=%d popupCharacters=%s />", + TAG_KEY, (key.mEnabled ? "" : " disabled"), key.mLabel, key.mCode, Arrays.toString(key.mPopupCharacters))); checkEndTag(TAG_KEY, parser); keys.add(key); @@ -419,6 +419,8 @@ public class KeyboardParser { try { final boolean modeMatched = matchInteger(a, R.styleable.Keyboard_Case_mode, id.mMode); + final boolean passwordInputMatched = matchBoolean(a, + R.styleable.Keyboard_Case_passwordInput, id.mPasswordInput); final boolean settingsKeyMatched = matchBoolean(a, R.styleable.Keyboard_Case_hasSettingsKey, id.mHasSettingsKey); final boolean voiceEnabledMatched = matchBoolean(a, @@ -427,24 +429,30 @@ public class KeyboardParser { R.styleable.Keyboard_Case_hasVoiceKey, id.mHasVoiceKey); final boolean colorSchemeMatched = matchInteger(viewAttr, R.styleable.KeyboardView_colorScheme, id.mColorScheme); - // As noted at KeyboardSwitcher.KeyboardId class, we are interested only in - // enum value masked by IME_MASK_ACTION and IME_FLAG_NO_ENTER_ACTION. So matching + // 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 // this attribute with id.mImeOptions as integer value is enough for our purpose. - final boolean imeOptionsMatched = matchInteger(a, - R.styleable.Keyboard_Case_imeOptions, id.mImeOptions); - final boolean selected = modeMatched && settingsKeyMatched && voiceEnabledMatched - && voiceKeyMatched && colorSchemeMatched && imeOptionsMatched; - - if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s> %s", TAG_CASE, + final boolean imeActionMatched = matchInteger(a, + R.styleable.Keyboard_Case_imeAction, id.mImeAction); + final boolean languageCodeMatched = matchString(a, + R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage()); + final boolean selected = modeMatched && passwordInputMatched && settingsKeyMatched + && voiceEnabledMatched && voiceKeyMatched && colorSchemeMatched + && imeActionMatched && languageCodeMatched; + + if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s> %s", TAG_CASE, textAttr(KeyboardId.modeName( a.getInt(R.styleable.Keyboard_Case_mode, -1)), "mode"), textAttr(KeyboardId.colorSchemeName( a.getInt(R.styleable.KeyboardView_colorScheme, -1)), "colorSchemeName"), + booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, "passwordInput"), booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"), booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"), booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"), textAttr(KeyboardId.imeOptionsName( - a.getInt(R.styleable.Keyboard_Case_imeOptions, -1)), "imeOptions"), + a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"), + textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"), Boolean.toString(selected))); return selected; @@ -466,6 +474,12 @@ public class KeyboardParser { return !a.hasValue(index) || a.getBoolean(index, false) == value; } + private static boolean matchString(TypedArray a, int index, String value) { + // If <case> does not have "index" attribute, that means this <case> is wild-card for the + // attribute. + return !a.hasValue(index) || a.getString(index).equals(value); + } + private boolean parseDefault(XmlResourceParser parser, Row row, List<Key> keys) throws XmlPullParserException, IOException { if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT)); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 2648ff3d4..3297dc325 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -28,6 +28,7 @@ import android.content.SharedPreferences; import android.content.res.Resources; import android.util.Log; import android.view.InflateException; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import java.lang.ref.SoftReference; @@ -66,8 +67,9 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache = new HashMap<KeyboardId, SoftReference<LatinKeyboard>>(); + // TODO: clean mMode up and use mAttribute instead. private int mMode = KeyboardId.MODE_TEXT; /* default value */ - private int mImeOptions; + private EditorInfo mAttribute; private boolean mIsSymbols; /** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of * what user actually typed. */ @@ -128,10 +130,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha final int orientation = res.getConfiguration().orientation; final int mode = mMode; final int colorScheme = getColorScheme(); - final boolean hasSettingsKey = mHasSettingsKey; - final boolean voiceKeyEnabled = mVoiceKeyEnabled; - final boolean hasVoiceKey = voiceKeyEnabled && !mVoiceButtonOnPrimary; - final int imeOptions = mImeOptions; + final boolean hasVoiceKey = mVoiceKeyEnabled && !mVoiceButtonOnPrimary; // Note: This comment is only applied for phone number keyboard layout. // On non-xlarge device, "@integer/key_switch_alpha_symbol" key code is used to switch // between "phone keyboard" and "phone symbols keyboard". But on xlarge device, @@ -140,50 +139,43 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha // mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard" // respectively here for xlarge device's layout switching. int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols; - mSymbolsId = new KeyboardId( - res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme, - hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); + final String xmlName = res.getResourceEntryName(xmlId); + mSymbolsId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode, + mAttribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true); xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift; - mSymbolsShiftedId = new KeyboardId( - res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme, - hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true); + mSymbolsShiftedId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode, + mAttribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true); } private boolean hasVoiceKey(boolean isSymbols) { return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary); } - public void loadKeyboard(int mode, int imeOptions, boolean voiceKeyEnabled, + public void loadKeyboard(int mode, EditorInfo attribute, boolean voiceKeyEnabled, boolean voiceButtonOnPrimary) { mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA; try { - if (mInputView == null) return; - final Keyboard oldKeyboard = mInputView.getKeyboard(); - loadKeyboardInternal(mode, imeOptions, voiceKeyEnabled, voiceButtonOnPrimary, false); - final Keyboard newKeyboard = mInputView.getKeyboard(); - if (newKeyboard.isAlphaKeyboard()) { - final boolean localeChanged = (oldKeyboard == null) - || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); - mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged); - } + loadKeyboardInternal(mode, attribute, voiceKeyEnabled, voiceButtonOnPrimary, false); } catch (RuntimeException e) { - Log.w(TAG, e); - LatinImeLogger.logOnException(mode + "," + imeOptions, e); + // Get KeyboardId to record which keyboard has been failed to load. + final KeyboardId id = getKeyboardId(mode, attribute, false); + Log.w(TAG, "loading keyboard failed: " + id, e); + LatinImeLogger.logOnException(id.toString(), e); } } - private void loadKeyboardInternal(int mode, int imeOptions, boolean voiceButtonEnabled, + private void loadKeyboardInternal(int mode, EditorInfo attribute, boolean voiceButtonEnabled, boolean voiceButtonOnPrimary, boolean isSymbols) { if (mInputView == null) return; mMode = mode; - mImeOptions = imeOptions; + mAttribute = attribute; mVoiceKeyEnabled = voiceButtonEnabled; mVoiceButtonOnPrimary = voiceButtonOnPrimary; mIsSymbols = isSymbols; // Update the settings key state because number of enabled IMEs could have been changed mHasSettingsKey = getSettingsKeyMode(mPrefs, mInputMethodService); - final KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols); + final KeyboardId id = getKeyboardId(mode, attribute, isSymbols); final Keyboard oldKeyboard = mInputView.getKeyboard(); if (oldKeyboard != null && oldKeyboard.mId.equals(id)) @@ -192,7 +184,15 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha makeSymbolsKeyboardIds(); mCurrentId = id; mInputView.setPreviewEnabled(mInputMethodService.getPopupOn()); - mInputView.setKeyboard(getKeyboard(id)); + setKeyboard(getKeyboard(id)); + } + + private void setKeyboard(final Keyboard newKeyboard) { + final Keyboard oldKeyboard = mInputView.getKeyboard(); + mInputView.setKeyboard(newKeyboard); + final boolean localeChanged = (oldKeyboard == null) + || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale); + mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged); } private LatinKeyboard getKeyboard(KeyboardId id) { @@ -228,7 +228,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha return keyboard; } - private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) { + private KeyboardId getKeyboardId(int mode, EditorInfo attribute, boolean isSymbols) { final boolean hasVoiceKey = hasVoiceKey(isSymbols); final int charColorId = getColorScheme(); final int xmlId; @@ -260,8 +260,8 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha final int orientation = res.getConfiguration().orientation; final Locale locale = mSubtypeSwitcher.getInputLocale(); return new KeyboardId( - res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, charColorId, - mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, imeOptions, enableShiftLock); + res.getResourceEntryName(xmlId), xmlId, charColorId, locale, orientation, mode, + attribute, mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, enableShiftLock); } public int getKeyboardMode() { @@ -278,22 +278,19 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha public boolean isKeyboardAvailable() { if (mInputView != null) - return mInputView.getLatinKeyboard() != null; + return mInputView.getKeyboard() != null; return false; } - private LatinKeyboard getLatinKeyboard() { - if (mInputView != null) - return mInputView.getLatinKeyboard(); + public LatinKeyboard getLatinKeyboard() { + if (mInputView != null) { + final Keyboard keyboard = mInputView.getKeyboard(); + if (keyboard instanceof LatinKeyboard) + return (LatinKeyboard)keyboard; + } return null; } - public void setPreferredLetters(int[] frequencies) { - LatinKeyboard latinKeyboard = getLatinKeyboard(); - if (latinKeyboard != null) - latinKeyboard.setPreferredLetters(frequencies); - } - public void keyReleased() { LatinKeyboard latinKeyboard = getLatinKeyboard(); if (latinKeyboard != null) @@ -556,7 +553,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha // indicator, we need to call enableShiftLock() and setShiftLocked(false). keyboard.setShifted(false); } - mInputView.setKeyboard(keyboard); + setKeyboard(keyboard); } public boolean isInMomentaryAutoModeSwitchState() { @@ -572,7 +569,7 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha } private void toggleKeyboardMode() { - loadKeyboardInternal(mMode, mImeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary, + loadKeyboardInternal(mMode, mAttribute, mVoiceKeyEnabled, mVoiceButtonOnPrimary, !mIsSymbols); if (mIsSymbols) { mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 19f1fa8ee..7fee022e0 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.PorterDuff; @@ -68,8 +69,7 @@ import java.util.WeakHashMap; * @attr ref R.styleable#KeyboardView_popupLayout */ public class KeyboardView extends View implements PointerTracker.UIProxy { - private static final String TAG = "KeyboardView"; - private static final boolean DEBUG = false; + private static final String TAG = KeyboardView.class.getSimpleName(); private static final boolean DEBUG_SHOW_ALIGN = false; private static final boolean DEBUG_KEYBOARD_GRID = false; @@ -115,7 +115,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { private int[] mOffsetInWindow; private int mOldPreviewKeyIndex = KeyDetector.NOT_A_KEY; private boolean mShowPreview = true; - private boolean mShowTouchPoints = true; private int mPopupPreviewOffsetX; private int mPopupPreviewOffsetY; private int mWindowY; @@ -158,18 +157,20 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { // Drawing /** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/ private boolean mDrawPending; + /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ + private boolean mKeyboardChanged; /** The dirty region in the keyboard bitmap */ private final Rect mDirtyRect = new Rect(); + /** The key to invalidate. */ + private Key mInvalidatedKey; + /** The dirty region for single key drawing */ + private final Rect mInvalidatedKeyRect = new Rect(); /** The keyboard bitmap for faster updates */ private Bitmap mBuffer; - /** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */ - private boolean mKeyboardChanged; - private Key mInvalidatedKey; /** The canvas for the above mutable keyboard bitmap */ private Canvas mCanvas; private final Paint mPaint; private final Rect mPadding; - private final Rect mClipRegion = new Rect(0, 0, 0, 0); // This map caches key label text height in pixel as value and key label text size as map key. private final HashMap<Integer, Integer> mTextHeightCache = new HashMap<Integer, Integer>(); // Distance from horizontal center of the key, proportional to key label text height and width. @@ -506,10 +507,9 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance); } requestLayout(); - // Hint to reallocate the buffer if the size changed mKeyboardChanged = true; invalidateAllKeys(); - computeProximityThreshold(keyboard, mKeys); + mKeyDetector.setProximityThreshold(KeyDetector.getMostCommonKeyWidth(keyboard)); mMiniKeyboardCache.clear(); } @@ -601,37 +601,6 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { } } - /** - * Compute the most common key width and use it as proximity key detection threshold. - * @param keyboard - * @param keys - */ - private void computeProximityThreshold(Keyboard keyboard, Key[] keys) { - if (keyboard == null || keys == null || keys.length == 0) return; - final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>(); - int maxCount = 0; - int mostCommonWidth = 0; - for (Key key : keys) { - 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; - } - } - mKeyDetector.setProximityThreshold(mostCommonWidth); - } - - @Override - public void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - // Release the buffer, if any and it will be reallocated on the next draw - mBuffer = null; - } - @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); @@ -641,19 +610,18 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { canvas.drawBitmap(mBuffer, 0, 0, null); } - @SuppressWarnings("unused") private void onBufferDraw() { + final int width = getWidth(); + final int height = getHeight(); + if (width == 0 || height == 0) + return; if (mBuffer == null || mKeyboardChanged) { - if (mBuffer == null || mKeyboardChanged && - (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) { - // Make sure our bitmap is at least 1x1 - final int width = Math.max(1, getWidth()); - final int height = Math.max(1, getHeight()); - mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - mCanvas = new Canvas(mBuffer); - } - invalidateAllKeys(); mKeyboardChanged = false; + mDirtyRect.union(0, 0, width, height); + } + if (mBuffer == null || mBuffer.getWidth() != width || mBuffer.getHeight() != height) { + mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mCanvas = new Canvas(mBuffer); } final Canvas canvas = mCanvas; canvas.clipRect(mDirtyRect, Op.REPLACE); @@ -662,30 +630,19 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { final Paint paint = mPaint; final Drawable keyBackground = mKeyBackground; - final Rect clipRegion = mClipRegion; final Rect padding = mPadding; final int kbdPaddingLeft = getPaddingLeft(); final int kbdPaddingTop = getPaddingTop(); final Key[] keys = mKeys; - final Key invalidKey = mInvalidatedKey; final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase(); + final boolean drawSingleKey = (mInvalidatedKey != null + && mInvalidatedKeyRect.contains(mDirtyRect)); - boolean drawSingleKey = false; - if (invalidKey != null && canvas.getClipBounds(clipRegion)) { - // TODO we should use Rect.inset and Rect.contains here. - // Is clipRegion completely contained within the invalidated key? - if (invalidKey.mX + kbdPaddingLeft - 1 <= clipRegion.left && - invalidKey.mY + kbdPaddingTop - 1 <= clipRegion.top && - invalidKey.mX + invalidKey.mWidth + kbdPaddingLeft + 1 >= clipRegion.right && - invalidKey.mY + invalidKey.mHeight + kbdPaddingTop + 1 >= clipRegion.bottom) { - drawSingleKey = true; - } - } canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR); final int keyCount = keys.length; for (int i = 0; i < keyCount; i++) { final Key key = keys[i]; - if (drawSingleKey && invalidKey != key) { + if (drawSingleKey && key != mInvalidatedKey) { continue; } int[] drawableState = key.getCurrentDrawableState(); @@ -739,16 +696,23 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { } else { positionX = (key.mWidth + padding.left - padding.right) / 2; paint.setTextAlign(Align.CENTER); - if (DEBUG_SHOW_ALIGN && label.length() > 1) - drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint()); + if (DEBUG_SHOW_ALIGN) { + if (label.length() > 1) + drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint()); + } } if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) { paint.setColor(mKeyTextColorDisabled); } else { paint.setColor(mKeyTextColor); } - // Set a drop shadow for the text - paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); + if (key.mEnabled) { + // Set a drop shadow for the text + paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor); + } else { + // Make label invisible + paint.setColor(Color.TRANSPARENT); + } canvas.drawText(label, positionX, baseline, paint); // Turn off drop shadow paint.setShadowLayer(0, 0, 0, 0); @@ -811,32 +775,13 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { canvas.drawLine(0, i * ch, cw * mKeyboard.GRID_WIDTH, i * ch, p); } - mInvalidatedKey = null; // Overlay a dark rectangle to dim the keyboard if (mMiniKeyboardView != null) { paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24); - canvas.drawRect(0, 0, getWidth(), getHeight(), paint); - } - - if (DEBUG) { - if (mShowTouchPoints) { - for (PointerTracker tracker : mPointerTrackers) { - int startX = tracker.getStartX(); - int startY = tracker.getStartY(); - int lastX = tracker.getLastX(); - int lastY = tracker.getLastY(); - paint.setAlpha(128); - paint.setColor(0xFFFF0000); - canvas.drawCircle(startX, startY, 3, paint); - canvas.drawLine(startX, startY, lastX, lastY, paint); - paint.setColor(0xFF0000FF); - canvas.drawCircle(lastX, lastY, 3, paint); - paint.setColor(0xFF00FF00); - canvas.drawCircle((startX + lastX) / 2, (startY + lastY) / 2, 2, paint); - } - } + canvas.drawRect(0, 0, width, height, paint); } + mInvalidatedKey = null; mDrawPending = false; mDirtyRect.setEmpty(); } @@ -1050,12 +995,11 @@ public class KeyboardView extends View implements PointerTracker.UIProxy { if (key == null) return; mInvalidatedKey = key; - // TODO we should clean up this and record key's region to use in onBufferDraw. - mDirtyRect.union(key.mX + getPaddingLeft(), key.mY + getPaddingTop(), - key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop()); + mInvalidatedKeyRect.set(0, 0, key.mWidth, key.mHeight); + mInvalidatedKeyRect.offset(key.mX + getPaddingLeft(), key.mY + getPaddingTop()); + mDirtyRect.union(mInvalidatedKeyRect); onBufferDraw(); - invalidate(key.mX + getPaddingLeft(), key.mY + getPaddingTop(), - key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop()); + invalidate(mInvalidatedKeyRect); } private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) { diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java index ffb8d6410..5820049bb 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java @@ -21,6 +21,7 @@ import com.android.inputmethod.latin.SubtypeSwitcher; import android.content.Context; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -31,17 +32,12 @@ import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.util.Log; import java.util.List; import java.util.Locale; // TODO: We should remove this class public class LatinKeyboard extends Keyboard { - - private static final boolean DEBUG_PREFERRED_LETTER = false; - private static final String TAG = "LatinKeyboard"; - public static final int OPACITY_FULLY_OPAQUE = 255; private static final int SPACE_LED_LENGTH_PERCENT = 80; @@ -69,15 +65,7 @@ public class LatinKeyboard extends Keyboard { private final Drawable mEnabledShortcutIcon; private final Drawable mDisabledShortcutIcon; - private int[] mPrefLetterFrequencies; - private int mPrefLetter; - private int mPrefLetterX; - private int mPrefLetterY; - private int mPrefDistance; - private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f; - private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f; - private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f; // Minimum width of space key preview (proportional to keyboard width) private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f; // Height in space key the language name will be drawn. (proportional to space key height) @@ -167,6 +155,8 @@ public class LatinKeyboard extends Keyboard { } private void updateSpacebarForLocale(boolean isAutoCorrection) { + if (mSpaceKey == null) + return; final Resources res = mContext.getResources(); // If application locales are explicitly selected. if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) { @@ -265,7 +255,7 @@ public class LatinKeyboard extends Keyboard { final boolean allowVariableTextSize = true; final String language = layoutSpacebar(paint, subtypeSwitcher.getInputLocale(), mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height, - getTextSizeFromTheme(textStyle, defaultTextSize), + getTextSizeFromTheme(mContext.getTheme(), textStyle, defaultTextSize), allowVariableTextSize); // Draw language text with shadow @@ -334,18 +324,9 @@ public class LatinKeyboard extends Keyboard { return mSpaceDragLastDiff > 0 ? 1 : -1; } - public void setPreferredLetters(int[] frequencies) { - mPrefLetterFrequencies = frequencies; - mPrefLetter = 0; - } - public void keyReleased() { mCurrentlyInSpace = false; mSpaceDragLastDiff = 0; - mPrefLetter = 0; - mPrefLetterX = 0; - mPrefLetterY = 0; - mPrefDistance = Integer.MAX_VALUE; if (mSpaceKey != null) { updateLocaleDrag(Integer.MAX_VALUE); } @@ -381,80 +362,6 @@ public class LatinKeyboard extends Keyboard { return isOnSpace; } } - } else if (mPrefLetterFrequencies != null) { - // New coordinate? Reset - if (mPrefLetterX != x || mPrefLetterY != y) { - mPrefLetter = 0; - mPrefDistance = Integer.MAX_VALUE; - } - // Handle preferred next letter - final int[] pref = mPrefLetterFrequencies; - if (mPrefLetter > 0) { - if (DEBUG_PREFERRED_LETTER) { - if (mPrefLetter == code && !key.isOnKey(x, y)) { - Log.d(TAG, "CORRECTED !!!!!!"); - } - } - return mPrefLetter == code; - } else { - final boolean isOnKey = key.isOnKey(x, y); - int[] nearby = getNearestKeys(x, y); - List<Key> nearbyKeys = getKeys(); - if (isOnKey) { - // If it's a preferred letter - if (inPrefList(code, pref)) { - // Check if its frequency is much lower than a nearby key - mPrefLetter = code; - mPrefLetterX = x; - mPrefLetterY = y; - for (int i = 0; i < nearby.length; i++) { - Key k = nearbyKeys.get(nearby[i]); - if (k != key && inPrefList(k.mCode, pref)) { - final int dist = distanceFrom(k, x, y); - if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_LOW_PROB) && - (pref[k.mCode] > pref[mPrefLetter] * 3)) { - mPrefLetter = k.mCode; - mPrefDistance = dist; - if (DEBUG_PREFERRED_LETTER) { - Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!"); - } - break; - } - } - } - - return mPrefLetter == code; - } - } - - // Get the surrounding keys and intersect with the preferred list - // For all in the intersection - // if distance from touch point is within a reasonable distance - // make this the pref letter - // If no pref letter - // return inside; - // else return thiskey == prefletter; - - for (int i = 0; i < nearby.length; i++) { - Key k = nearbyKeys.get(nearby[i]); - if (inPrefList(k.mCode, pref)) { - final int dist = distanceFrom(k, x, y); - if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_HIGH_PROB) - && dist < mPrefDistance) { - mPrefLetter = k.mCode; - mPrefLetterX = x; - mPrefLetterY = y; - mPrefDistance = dist; - } - } - } - // Didn't find any - if (mPrefLetter == 0) { - return isOnKey; - } else { - return mPrefLetter == code; - } - } } // Lock into the spacebar @@ -463,19 +370,6 @@ public class LatinKeyboard extends Keyboard { return key.isOnKey(x, y); } - private boolean inPrefList(int code, int[] pref) { - if (code < pref.length && code >= 0) return pref[code] > 0; - return false; - } - - private int distanceFrom(Key k, int x, int y) { - if (y > k.mY && y < k.mY + k.mHeight) { - return Math.abs(k.mX + k.mWidth / 2 - x); - } else { - return Integer.MAX_VALUE; - } - } - @Override public int[] getNearestKeys(int x, int y) { if (mCurrentlyInSpace) { @@ -487,8 +381,8 @@ public class LatinKeyboard extends Keyboard { } } - private int getTextSizeFromTheme(int style, int defValue) { - TypedArray array = mContext.getTheme().obtainStyledAttributes( + private static int getTextSizeFromTheme(Theme theme, int style, int defValue) { + TypedArray array = theme.obtainStyledAttributes( style, new int[] { android.R.attr.textSize }); int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue); return textSize; diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java index af2fd5ce1..6c3ad5edb 100644 --- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java @@ -66,7 +66,8 @@ public class LatinKeyboardView extends KeyboardView { } } - public void setLatinKeyboard(LatinKeyboard newKeyboard) { + @Override + public void setKeyboard(Keyboard newKeyboard) { final LatinKeyboard oldKeyboard = getLatinKeyboard(); if (oldKeyboard != null) { // Reset old keyboard state before switching to new keyboard. @@ -80,7 +81,7 @@ public class LatinKeyboardView extends KeyboardView { mLastRowY = (newKeyboard.getHeight() * 3) / 4; } - public LatinKeyboard getLatinKeyboard() { + private LatinKeyboard getLatinKeyboard() { Keyboard keyboard = getKeyboard(); if (keyboard instanceof LatinKeyboard) { return (LatinKeyboard)keyboard; @@ -141,6 +142,9 @@ public class LatinKeyboardView extends KeyboardView { * KeyboardView. */ private boolean handleSuddenJump(MotionEvent me) { + // If device has distinct multi touch panel, there is no need to check sudden jump. + if (hasDistinctMultitouch()) + return false; final int action = me.getAction(); final int x = (int) me.getX(); final int y = (int) me.getY(); diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java index f04991eb7..a8750d378 100644 --- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java @@ -35,24 +35,24 @@ public class MiniKeyboardKeyDetector extends KeyDetector { } @Override - public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { + public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) { final Key[] keys = getKeys(); final int touchX = getTouchX(x); final int touchY = getTouchY(y); - int closestKeyIndex = NOT_A_KEY; - int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; + int nearestIndex = NOT_A_KEY; + int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare; final int keyCount = keys.length; for (int index = 0; index < keyCount; index++) { final int dist = keys[index].squaredDistanceToEdge(touchX, touchY); - if (dist < closestKeyDist) { - closestKeyIndex = index; - closestKeyDist = dist; + if (dist < nearestDist) { + nearestIndex = index; + nearestDist = dist; } } - if (allKeys != null && closestKeyIndex != NOT_A_KEY) - allKeys[0] = keys[closestKeyIndex].mCode; - return closestKeyIndex; + if (allCodes != null && nearestIndex != NOT_A_KEY) + allCodes[0] = keys[nearestIndex].mCode; + return nearestIndex; } } diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index a981f724f..ac375473a 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -129,32 +129,42 @@ public class PointerTracker { } // Returns true if keyboard has been changed by this callback. - private boolean callListenerOnPressAndCheckKeyboardLayoutChange(int primaryCode) { + private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key) { if (DEBUG_LISTENER) - Log.d(TAG, "onPress : " + keyCodePrintable(primaryCode)); - mListener.onPress(primaryCode); - final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; - mKeyboardLayoutHasBeenChanged = false; - return keyboardLayoutHasBeenChanged; + Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode)); + if (key.mEnabled) { + mListener.onPress(key.mCode); + final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged; + mKeyboardLayoutHasBeenChanged = false; + return keyboardLayoutHasBeenChanged; + } + return false; } - private void callListenerOnCodeInput(int primaryCode, int[] keyCodes, int x, int y) { + // Note that we need primaryCode argument because the keyboard may in shifted state and the + // primaryCode is different from {@link Key#mCode}. + private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) { if (DEBUG_LISTENER) Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode) + " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y); - mListener.onCodeInput(primaryCode, keyCodes, x, y); + if (key.mEnabled) + mListener.onCodeInput(primaryCode, keyCodes, x, y); } - private void callListenerOnTextInput(CharSequence text) { + private void callListenerOnTextInput(Key key) { if (DEBUG_LISTENER) - Log.d(TAG, "onTextInput: text=" + text); - mListener.onTextInput(text); + Log.d(TAG, "onTextInput: text=" + key.mOutputText); + if (key.mEnabled) + mListener.onTextInput(key.mOutputText); } - private void callListenerOnRelease(int primaryCode) { + // Note that we need primaryCode argument because the keyboard may in shifted state and the + // primaryCode is different from {@link Key#mCode}. + private void callListenerOnRelease(Key key, int primaryCode) { if (DEBUG_LISTENER) Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode)); - mListener.onRelease(primaryCode); + if (key.mEnabled) + mListener.onRelease(primaryCode); } private void callListenerOnCancelInput() { @@ -313,7 +323,7 @@ public class PointerTracker { // This onPress call may have changed keyboard layout. Those cases are detected at // {@link #setKeyboard}. In those cases, we should update keyIndex according to the new // keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex].mCode)) + if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex])) keyIndex = mKeyState.onDownKey(x, y, eventTime); } if (isValidKeyIndex(keyIndex)) { @@ -346,7 +356,7 @@ public class PointerTracker { // This onPress call may have changed keyboard layout. Those cases are detected at // {@link #setKeyboard}. In those cases, we should update keyIndex according to the // new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex).mCode)) + if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex))) keyIndex = keyState.onMoveKey(x, y); keyState.onMoveToNewKey(keyIndex, x, y); startLongPressTimer(keyIndex); @@ -355,13 +365,13 @@ public class PointerTracker { // onRelease() first to notify that the previous key has been released, then call // onPress() to notify that the new key is being pressed. mIsInSlidingKeyInput = true; - callListenerOnRelease(oldKey.mCode); + callListenerOnRelease(oldKey, oldKey.mCode); mHandler.cancelLongPressTimers(); if (mIsAllowedSlidingKeyInput) { // This onPress call may have changed keyboard layout. Those cases are detected // at {@link #setKeyboard}. In those cases, we should update keyIndex according // to the new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex).mCode)) + if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex))) keyIndex = keyState.onMoveKey(x, y); keyState.onMoveToNewKey(keyIndex, x, y); startLongPressTimer(keyIndex); @@ -390,7 +400,7 @@ public class PointerTracker { // The pointer has been slid out from the previous key, we must call onRelease() to // notify that the previous key has been released. mIsInSlidingKeyInput = true; - callListenerOnRelease(oldKey.mCode); + callListenerOnRelease(oldKey, oldKey.mCode); mHandler.cancelLongPressTimers(); if (mIsAllowedSlidingKeyInput) { keyState.onMoveToNewKey(keyIndex, x ,y); @@ -490,15 +500,6 @@ public class PointerTracker { return mKeyState.getDownTime(); } - // These package scope methods are only for debugging purpose. - /* package */ int getStartX() { - return mKeyState.getStartX(); - } - - /* package */ int getStartY() { - return mKeyState.getStartY(); - } - private boolean isMinorMoveBounce(int x, int y, int newKey) { if (mKeys == null || mKeyHysteresisDistanceSquared < 0) throw new IllegalStateException("keyboard and/or hysteresis not set"); @@ -548,8 +549,8 @@ public class PointerTracker { return; } if (key.mOutputText != null) { - callListenerOnTextInput(key.mOutputText); - callListenerOnRelease(key.mCode); + callListenerOnTextInput(key); + callListenerOnRelease(key, key.mCode); } else { int code = key.mCode; final int[] codes = mKeyDetector.newCodeArray(); @@ -570,9 +571,8 @@ public class PointerTracker { codes[1] = codes[0]; codes[0] = code; } - if (key.mEnabled) - callListenerOnCodeInput(code, codes, x, y); - callListenerOnRelease(code); + callListenerOnCodeInput(key, code, codes, x, y); + callListenerOnRelease(key, code); } } diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java index 250bb95eb..a62ed96a3 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java @@ -23,8 +23,6 @@ package com.android.inputmethod.keyboard; private final KeyDetector mKeyDetector; // The position and time at which first down event occurred. - private int mStartX; - private int mStartY; private long mDownTime; private long mUpTime; @@ -54,14 +52,6 @@ package com.android.inputmethod.keyboard; return mKeyY; } - public int getStartX() { - return mStartX; - } - - public int getStartY() { - return mStartY; - } - public long getDownTime() { return mDownTime; } @@ -79,8 +69,6 @@ package com.android.inputmethod.keyboard; } public int onDownKey(int x, int y, long eventTime) { - mStartX = x; - mStartY = y; mDownTime = eventTime; return onMoveToNewKey(onMoveKeyInternal(x, y), x, y); } diff --git a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java index 0920da2cb..c3fd1984b 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java @@ -16,49 +16,106 @@ package com.android.inputmethod.keyboard; +import android.util.Log; + import java.util.Arrays; public class ProximityKeyDetector extends KeyDetector { + private static final String TAG = ProximityKeyDetector.class.getSimpleName(); + private static final boolean DEBUG = false; + private static final int MAX_NEARBY_KEYS = 12; // working area - private int[] mDistances = new int[MAX_NEARBY_KEYS]; + private final int[] mDistances = new int[MAX_NEARBY_KEYS]; + private final int[] mIndices = new int[MAX_NEARBY_KEYS]; @Override protected int getMaxNearbyKeys() { return MAX_NEARBY_KEYS; } + private void initializeNearbyKeys() { + Arrays.fill(mDistances, Integer.MAX_VALUE); + Arrays.fill(mIndices, NOT_A_KEY); + } + + /** + * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance. + * + * @param keyIndex index of the key. + * @param distance distance between the key's edge and user touched point. + * @return order of the key in the nearby buffer, 0 if it is the nearest key. + */ + private int sortNearbyKeys(int keyIndex, int distance) { + final int[] distances = mDistances; + final int[] indices = mIndices; + for (int insertPos = 0; insertPos < distances.length; insertPos++) { + if (distance < distances[insertPos]) { + final int nextPos = insertPos + 1; + if (nextPos < distances.length) { + System.arraycopy(distances, insertPos, distances, nextPos, + distances.length - nextPos); + System.arraycopy(indices, insertPos, indices, nextPos, + indices.length - nextPos); + } + distances[insertPos] = distance; + indices[insertPos] = keyIndex; + return insertPos; + } + } + return distances.length; + } + + private void getNearbyKeyCodes(final int[] allCodes) { + final Key[] keys = getKeys(); + final int[] indices = mIndices; + + // allCodes[0] should always have the key code even if it is a non-letter key. + if (indices[0] == NOT_A_KEY) { + allCodes[0] = NOT_A_CODE; + return; + } + + int numCodes = 0; + for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) { + final int index = indices[j]; + if (index == NOT_A_KEY) + break; + final int code = keys[index].mCode; + // filter out a non-letter key from nearby keys + if (code < Keyboard.CODE_SPACE) + continue; + allCodes[numCodes++] = code; + } + } + @Override - public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) { + public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) { final Key[] keys = getKeys(); final int touchX = getTouchX(x); final int touchY = getTouchY(y); + initializeNearbyKeys(); int primaryIndex = NOT_A_KEY; - final int[] distances = mDistances; - Arrays.fill(distances, Integer.MAX_VALUE); for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) { final Key key = keys[index]; final boolean isInside = key.isInside(touchX, touchY); - if (isInside) - primaryIndex = index; - final int dist = key.squaredDistanceToEdge(touchX, touchY); - if (isInside || (mProximityCorrectOn && dist < mProximityThresholdSquare)) { - if (allKeys == null) continue; - // Find insertion point - for (int j = 0; j < distances.length; j++) { - if (distances[j] > dist) { - final int nextPos = j + 1; - System.arraycopy(distances, j, distances, nextPos, - distances.length - nextPos); - System.arraycopy(allKeys, j, allKeys, nextPos, - allKeys.length - nextPos); - distances[j] = dist; - allKeys[j] = key.mCode; - break; - } - } + final int distance = key.squaredDistanceToEdge(touchX, touchY); + if (isInside || (mProximityCorrectOn && distance < mProximityThresholdSquare)) { + final int insertedPosition = sortNearbyKeys(index, distance); + if (insertedPosition == 0 && isInside) + primaryIndex = index; + } + } + + if (allCodes != null && allCodes.length > 0) { + getNearbyKeyCodes(allCodes); + if (DEBUG) { + Log.d(TAG, "x=" + x + " y=" + y + + " primary=" + + (primaryIndex == NOT_A_KEY ? "none" : keys[primaryIndex].mCode) + + " codes=" + Arrays.toString(allCodes)); } } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 813f7d32f..ff7e2b88a 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.AssetFileDescriptor; import android.util.Log; +import java.io.File; import java.util.Arrays; /** @@ -72,9 +73,40 @@ public class BinaryDictionary extends Dictionary { public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) { synchronized (sInstance) { sInstance.closeInternal(); - if (resId != 0) { - sInstance.loadDictionary(context, resId); + try { + final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); + if (afd == null) { + Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); + return null; + } + final String sourceDir = context.getApplicationInfo().sourceDir; + final File packagePath = new File(sourceDir); + // TODO: Come up with a way to handle a directory. + if (!packagePath.isFile()) { + Log.e(TAG, "sourceDir is not a file: " + sourceDir); + return null; + } + sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength()); sInstance.mDicTypeId = dicTypeId; + } catch (android.content.res.Resources.NotFoundException e) { + Log.e(TAG, "Could not find the resource. resId=" + resId); + return null; + } + } + return sInstance; + } + + // For unit test + /* package */ static BinaryDictionary initDictionary(File dictionary, long startOffset, + long length, int dicTypeId) { + synchronized (sInstance) { + sInstance.closeInternal(); + if (dictionary.isFile()) { + sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length); + sInstance.mDicTypeId = dicTypeId; + } else { + Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath()); + return null; } } return sInstance; @@ -86,33 +118,21 @@ public class BinaryDictionary extends Dictionary { private native void closeNative(int dict); private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize, - char[] outputChars, int[] frequencies, - int[] nextLettersFrequencies, int nextLettersSize); + char[] outputChars, int[] frequencies); private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength, int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies, int maxWordLength, int maxBigrams, int maxAlternatives); - private final void loadDictionary(Context context, int resId) { - try { - final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); - if (afd == null) { - Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); - return; - } - mNativeDict = openNative(context.getApplicationInfo().sourceDir, - afd.getStartOffset(), afd.getLength(), + private final void loadDictionary(String path, long startOffset, long length) { + mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES); - mDictLength = afd.getLength(); - } catch (android.content.res.Resources.NotFoundException e) { - Log.e(TAG, "Could not find the resource. resId=" + resId); - return; - } + mDictLength = length; } @Override public void getBigrams(final WordComposer codes, final CharSequence previousWord, - final WordCallback callback, int[] nextLettersFrequencies) { + final WordCallback callback) { if (mNativeDict == 0) return; char[] chars = previousWord.toString().toCharArray(); @@ -144,15 +164,14 @@ public class BinaryDictionary extends Dictionary { } @Override - public void getWords(final WordComposer codes, final WordCallback callback, - int[] nextLettersFrequencies) { + public void getWords(final WordComposer codes, final WordCallback callback) { if (mNativeDict == 0) return; final int codesSize = codes.size(); // Won't deal with really long words. if (codesSize > MAX_WORD_LENGTH - 1) return; - Arrays.fill(mInputCodes, -1); + Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE); for (int i = 0; i < codesSize; i++) { int[] alternatives = codes.getCodesAt(i); System.arraycopy(alternatives, 0, mInputCodes, i * MAX_ALTERNATIVES, @@ -162,8 +181,7 @@ public class BinaryDictionary extends Dictionary { Arrays.fill(mFrequencies, 0); int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars, - mFrequencies, nextLettersFrequencies, - nextLettersFrequencies != null ? nextLettersFrequencies.length : 0); + mFrequencies); for (int j = 0; j < count; ++j) { if (mFrequencies[j] < 1) break; diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java index fc45c7c75..9699ad136 100644 --- a/java/src/com/android/inputmethod/latin/CandidateView.java +++ b/java/src/com/android/inputmethod/latin/CandidateView.java @@ -54,7 +54,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); private static final int MAX_SUGGESTIONS = 16; - private static boolean DBG = LatinImeLogger.sDBG; + private static final boolean DBG = LatinImeLogger.sDBG; private final ArrayList<View> mWords = new ArrayList<View>(); private final boolean mConfigCandidateHighlightFontColorEnabled; @@ -226,10 +226,14 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo } final String debugString = info.getDebugString(); if (DBG) { - if (!TextUtils.isEmpty(debugString)) { + if (TextUtils.isEmpty(debugString)) { + dv.setVisibility(GONE); + } else { dv.setText(debugString); dv.setVisibility(VISIBLE); } + } else { + dv.setVisibility(GONE); } } else { dv.setVisibility(GONE); @@ -249,8 +253,10 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo final TextView tv = (TextView)mWords.get(1).findViewById(R.id.candidate_word); 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); + word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + word.setSpan(mInvertedForegroundColorSpan, 0, wordLength, + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); tv.setText(word); mShowingAutoCorrectionInverted = true; } diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java index 03211f36b..2f1e7c2b8 100644 --- a/java/src/com/android/inputmethod/latin/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/DebugSettings.java @@ -20,6 +20,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; +import android.os.Process; import android.preference.CheckBoxPreference; import android.preference.PreferenceActivity; import android.util.Log; @@ -30,6 +31,7 @@ public class DebugSettings extends PreferenceActivity private static final String TAG = "DebugSettings"; private static final String DEBUG_MODE_KEY = "debug_mode"; + private boolean mServiceNeedsRestart = false; private CheckBoxPreference mDebugMode; @Override @@ -39,16 +41,24 @@ public class DebugSettings extends PreferenceActivity SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); prefs.registerOnSharedPreferenceChangeListener(this); + mServiceNeedsRestart = false; mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY); updateDebugMode(); } @Override + protected void onStop() { + super.onStop(); + if (mServiceNeedsRestart) Process.killProcess(Process.myPid()); + } + + @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { if (key.equals(DEBUG_MODE_KEY)) { if (mDebugMode != null) { mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false)); updateDebugMode(); + mServiceNeedsRestart = true; } } } diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 74933595c..56f0cc503 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -61,14 +61,9 @@ public abstract class Dictionary { * 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 nextLettersFrequencies array of frequencies of next letters that could follow the - * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have - * a non-zero value on returning from this method. - * Pass in null if you don't want the dictionary to look up next letters. * @see WordCallback#addWord(char[], int, int) */ - abstract public void getWords(final WordComposer composer, final WordCallback callback, - int[] nextLettersFrequencies); + abstract public void getWords(final WordComposer composer, final WordCallback callback); /** * Searches for pairs in the bigram dictionary that matches the previous word and all the @@ -76,13 +71,9 @@ public abstract class Dictionary { * @param composer the key sequence to match * @param previousWord the word before * @param callback the callback object to send possible word following previous word - * @param nextLettersFrequencies array of frequencies of next letters that could follow the - * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have - * a non-zero value on returning from this method. - * Pass in null if you don't want the dictionary to look up next letters. */ public void getBigrams(final WordComposer composer, final CharSequence previousWord, - final WordCallback callback, int[] nextLettersFrequencies) { + final WordCallback callback) { // empty base implementation } diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 0fc86c335..b10e7a61e 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -37,7 +37,6 @@ public class ExpandableDictionary extends Dictionary { private int mDicTypeId; private int mMaxDepth; private int mInputLength; - private int[] mNextLettersFrequencies; private StringBuilder sb = new StringBuilder(MAX_WORD_LENGTH); private static final char QUOTE = '\''; @@ -191,8 +190,7 @@ public class ExpandableDictionary extends Dictionary { } @Override - public void getWords(final WordComposer codes, final WordCallback callback, - int[] nextLettersFrequencies) { + public void getWords(final WordComposer codes, final WordCallback callback) { synchronized (mUpdatingLock) { // If we need to update, start off a background task if (mRequiresReload) startDictionaryLoadingTaskLocked(); @@ -201,7 +199,6 @@ public class ExpandableDictionary extends Dictionary { } mInputLength = codes.size(); - mNextLettersFrequencies = nextLettersFrequencies; if (mCodes.length < mInputLength) mCodes = new int[mInputLength][]; // Cache the codes so that we don't have to lookup an array list for (int i = 0; i < mInputLength; i++) { @@ -282,11 +279,6 @@ public class ExpandableDictionary extends Dictionary { DataType.UNIGRAM)) { return; } - // Add to frequency of next letters for predictive correction - if (mNextLettersFrequencies != null && depth >= inputIndex && skipPos < 0 - && mNextLettersFrequencies.length > word[inputIndex]) { - mNextLettersFrequencies[word[inputIndex]]++; - } } if (children != null) { getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex, @@ -427,7 +419,7 @@ public class ExpandableDictionary extends Dictionary { @Override public void getBigrams(final WordComposer codes, final CharSequence previousWord, - final WordCallback callback, int[] nextLettersFrequencies) { + final WordCallback callback) { if (!reloadDictionaryIfRequired()) { runReverseLookUp(previousWord, callback); } @@ -516,7 +508,7 @@ public class ExpandableDictionary extends Dictionary { } } - static char toLowerCase(char c) { + private static char toLowerCase(char c) { char baseChar = c; if (c < BASE_CHARS.length) { baseChar = BASE_CHARS[c]; @@ -535,7 +527,7 @@ public class ExpandableDictionary extends Dictionary { * if c is not a combined character, or the base character if it * is combined. */ - static final char BASE_CHARS[] = { + private static final char BASE_CHARS[] = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 5ce1b7e95..c9e7f8b39 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -81,8 +81,7 @@ import java.util.Locale; /** * Input method implementation for Qwerty'ish keyboard. */ -public class LatinIME extends InputMethodService implements KeyboardActionListener, - SharedPreferences.OnSharedPreferenceChangeListener { +public class LatinIME extends InputMethodService implements KeyboardActionListener { private static final String TAG = "LatinIME"; private static final boolean PERF_DEBUG = false; private static final boolean TRACE = false; @@ -186,7 +185,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Keeps track of most recently inserted text (multi-character key) for reverting private CharSequence mEnteredText; - private boolean mRefreshKeyboardRequired; private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>(); @@ -323,7 +321,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR); final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView(); if (inputView != null) { - final LatinKeyboard keyboard = inputView.getLatinKeyboard(); + final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard(); // The language is never displayed when the delay is zero. if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0) inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f @@ -359,9 +357,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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.default_recorrection_enabled)); + res.getBoolean(R.bool.config_default_recorrection_enabled)); } else { - mReCorrectionEnabled = res.getBoolean(R.bool.default_recorrection_enabled); + mReCorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled); } mConfigEnableShowSubtypeSettings = res.getBoolean( @@ -395,7 +393,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); registerReceiver(mReceiver, filter); mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler); - prefs.registerOnSharedPreferenceChangeListener(this); } /** @@ -497,12 +494,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return container; } - private static boolean isPasswordVariation(int variation) { - return variation == InputType.TYPE_TEXT_VARIATION_PASSWORD - || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD - || variation == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD; - } - private static boolean isEmailVariation(int variation) { return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; @@ -523,20 +514,15 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSubtypeSwitcher.updateParametersOnStartInputView(); - if (mRefreshKeyboardRequired) { - mRefreshKeyboardRequired = false; - onRefreshKeyboard(); - } - TextEntryState.newSession(this); // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to // know now whether this is a password text field, because we need to know now whether we // want to enable the voice button. - mVoiceConnector.resetVoiceStates(isPasswordVariation( - attribute.inputType & InputType.TYPE_MASK_VARIATION)); + mVoiceConnector.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType) + || Utils.isVisiblePasswordInputType(attribute.inputType)); - final int mode = initializeInputAttributesAndGetMode(attribute.inputType); + final int mode = initializeInputAttributesAndGetMode(attribute); inputView.closing(); mEnteredText = null; @@ -547,7 +533,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen loadSettings(attribute); if (mSubtypeSwitcher.isKeyboardMode()) { - switcher.loadKeyboard(mode, attribute.imeOptions, + switcher.loadKeyboard(mode, attribute, mVoiceConnector.isVoiceButtonEnabled(), mVoiceConnector.isVoiceButtonOnPrimary()); switcher.updateShiftState(); @@ -571,7 +557,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); } - private int initializeInputAttributesAndGetMode(int inputType) { + // TODO: Separate calculating keyboard mode from initializing attributes, and make it an + // utility method in {@link Utils}. + private int initializeInputAttributesAndGetMode(EditorInfo attribute) { + if (attribute == null) return KeyboardId.MODE_TEXT; + final int inputType = attribute.inputType; final int variation = inputType & InputType.TYPE_MASK_VARIATION; mAutoSpace = false; mInputTypeNoAutoCorrect = false; @@ -591,16 +581,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case InputType.TYPE_CLASS_TEXT: mIsSettingsSuggestionStripOn = true; // Make sure that passwords are not displayed in candidate view - if (isPasswordVariation(variation)) { + if (Utils.isPasswordInputType(inputType) + || Utils.isVisiblePasswordInputType(inputType)) { mIsSettingsSuggestionStripOn = false; } - if (isEmailVariation(variation) + if (LatinIME.isEmailVariation(variation) || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) { mAutoSpace = false; } else { mAutoSpace = true; } - if (isEmailVariation(variation)) { + if (LatinIME.isEmailVariation(variation)) { mIsSettingsSuggestionStripOn = false; mode = KeyboardId.MODE_EMAIL; } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) { @@ -1467,26 +1458,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void switchToKeyboardView() { - mHandler.post(new Runnable() { - @Override - public void run() { - if (DEBUG) { - Log.d(TAG, "Switch to keyboard view."); - } - View v = mKeyboardSwitcher.getInputView(); - if (v != null) { - // Confirms that the keyboard view doesn't have parent view. - ViewParent p = v.getParent(); - if (p != null && p instanceof ViewGroup) { - ((ViewGroup) p).removeView(v); - } - setInputView(v); - } - setCandidatesViewShown(isCandidateStripVisible()); - updateInputViewShown(); - mHandler.postUpdateSuggestions(); + if (DEBUG) { + Log.d(TAG, "Switch to keyboard view."); + } + View v = mKeyboardSwitcher.getInputView(); + if (v != null) { + // Confirms that the keyboard view doesn't have parent view. + ViewParent p = v.getParent(); + if (p != null && p instanceof ViewGroup) { + ((ViewGroup) p).removeView(v); } - }); + setInputView(v); + } + setCandidatesViewShown(isCandidateStripVisible()); + updateInputViewShown(); + mHandler.postUpdateSuggestions(); } public void clearSuggestions() { @@ -1508,8 +1494,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } public void updateSuggestions() { - mKeyboardSwitcher.setPreferredLetters(null); - // Check if we have a suggestion engine attached. if ((mSuggest == null || !isSuggestionsRequested()) && !mVoiceConnector.isVoiceInputHighlighted()) { @@ -1528,7 +1512,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void showCorrections(WordAlternatives alternatives) { - mKeyboardSwitcher.setPreferredLetters(null); SuggestedWords.Builder builder = alternatives.getAlternatives(); builder.setTypedWordValid(false).setHasMinimalSuggestion(false); showSuggestions(builder.build(), alternatives.getOriginalWord()); @@ -1541,9 +1524,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder( mKeyboardSwitcher.getInputView(), word, prevWord); - int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies(); - mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies); - boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted && mSuggest.hasAutoCorrection(); final CharSequence typedWord = word.getTypedWord(); @@ -1712,7 +1692,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen saveWordInHistory(suggestion); mHasValidSuggestions = false; mCommittedLength = suggestion.length(); - switcher.setPreferredLetters(null); } /** @@ -1930,28 +1909,12 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSubtypeSwitcher.toggleLanguage(reset, next); } // Reload keyboard because the current language has been changed. - KeyboardSwitcher switcher = mKeyboardSwitcher; final EditorInfo attribute = getCurrentInputEditorInfo(); - final int mode = initializeInputAttributesAndGetMode((attribute != null) - ? attribute.inputType : 0); - final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; - switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(), - mVoiceConnector.isVoiceButtonOnPrimary()); + final int mode = initializeInputAttributesAndGetMode(attribute); + mKeyboardSwitcher.loadKeyboard(mode, attribute, + mVoiceConnector.isVoiceButtonEnabled(), mVoiceConnector.isVoiceButtonOnPrimary()); initSuggest(); - switcher.updateShiftState(); - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, - String key) { - mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, key); - if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) { - mRefreshKeyboardRequired = true; - } else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) { - mReCorrectionEnabled = sharedPreferences.getBoolean( - Settings.PREF_RECORRECTION_ENABLED, - mResources.getBoolean(R.bool.default_recorrection_enabled)); - } + mKeyboardSwitcher.updateShiftState(); } @Override @@ -2082,7 +2045,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void updateAutoTextEnabled() { if (mSuggest == null) return; - mSuggest.setAutoTextEnabled(mQuickFixes + mSuggest.setQuickFixesEnabled(mQuickFixes && SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage()); } @@ -2121,7 +2084,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); mVibrateOn = vibrator != null && vibrator.hasVibrator() && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false); - mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false); + mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, + mResources.getBoolean(R.bool.config_default_sound_enabled)); mPopupOn = isPopupEnabled(prefs); mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); @@ -2272,10 +2236,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen di.dismiss(); switch (position) { case 0: - launchSettings(); + mImm.showInputMethodPicker(); break; case 1: - mImm.showInputMethodPicker(); + launchSettings(); break; } } diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 12338ce61..341d5add0 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -225,7 +225,9 @@ public class Settings extends PreferenceActivity final String action; if (android.os.Build.VERSION.SDK_INT >= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) { - action = "android.settings.INPUT_METHOD_AND_SUBTYPE_ENABLER"; + // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS + // TODO: Can this be a constant instead of literal String constant? + action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; } else { action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION"; } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index f4262cc99..ee9dab3b7 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -371,12 +371,10 @@ public class SubtypeSwitcher { ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); mIsNetworkConnected = !noConnection; - final LatinKeyboardView inputView = KeyboardSwitcher.getInstance().getInputView(); - if (inputView != null) { - final LatinKeyboard keyboard = inputView.getLatinKeyboard(); - if (keyboard != null) { - keyboard.updateShortcutKey(isShortcutAvailable(), inputView); - } + final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance(); + final LatinKeyboard keyboard = switcher.getLatinKeyboard(); + if (keyboard != null) { + keyboard.updateShortcutKey(isShortcutAvailable(), switcher.getInputView()); } } @@ -426,8 +424,15 @@ public class SubtypeSwitcher { if (mConfigUseSpacebarLanguageSwitcher) { return mLanguageSwitcher.getEnabledLanguages(); } else { + int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size(); + // Workaround for explicitly specifying the voice language + if (enabledLanguageCount == 1) { + mEnabledLanguagesOfCurrentInputMethod.add( + mEnabledLanguagesOfCurrentInputMethod.get(0)); + ++enabledLanguageCount; + } return mEnabledLanguagesOfCurrentInputMethod.toArray( - new String[mEnabledLanguagesOfCurrentInputMethod.size()]); + new String[enabledLanguageCount]); } } @@ -465,14 +470,6 @@ public class SubtypeSwitcher { } } - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (mConfigUseSpacebarLanguageSwitcher) { - if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) { - mLanguageSwitcher.loadLocales(sharedPreferences); - } - } - } - /** * Change system locale for this application * @param newLocale diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index ced355bb2..6466f7980 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -22,6 +22,7 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -64,7 +65,7 @@ public class Suggest implements Dictionary.WordCallback { static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000; - private static boolean DBG = LatinImeLogger.sDBG; + private static final boolean DBG = LatinImeLogger.sDBG; private BinaryDictionary mMainDict; @@ -80,18 +81,12 @@ public class Suggest implements Dictionary.WordCallback { private static final int PREF_MAX_BIGRAMS = 60; - private boolean mAutoTextEnabled; + private boolean mQuickFixesEnabled; private double mAutoCorrectionThreshold; private int[] mPriorities = new int[mPrefMaxSuggestions]; private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS]; - // Handle predictive correction for only the first 1280 characters for performance reasons - // If we support scripts that need latin characters beyond that, we should probably use some - // kind of a sparse array or language specific list with a mapping lookup table. - // 1280 is the size of the BASE_CHARS array in ExpandableDictionary, which is a basic set of - // latin characters. - private int[] mNextLettersFrequencies = new int[1280]; private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>(); ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>(); private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>(); @@ -109,6 +104,12 @@ public class Suggest implements Dictionary.WordCallback { initPool(); } + // For unit test + /* package */ Suggest(File dictionary, long startOffset, long length) { + mMainDict = BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN); + initPool(); + } + private void initPool() { for (int i = 0; i < mPrefMaxSuggestions; i++) { StringBuilder sb = new StringBuilder(getApproxMaxWordLength()); @@ -116,8 +117,8 @@ public class Suggest implements Dictionary.WordCallback { } } - public void setAutoTextEnabled(boolean enabled) { - mAutoTextEnabled = enabled; + public void setQuickFixesEnabled(boolean enabled) { + mQuickFixesEnabled = enabled; } public int getCorrectionMode() { @@ -209,7 +210,6 @@ public class Suggest implements Dictionary.WordCallback { mIsAllUpperCase = wordComposer.isAllUpperCase(); collectGarbage(mSuggestions, mPrefMaxSuggestions); Arrays.fill(mPriorities, 0); - Arrays.fill(mNextLettersFrequencies, 0); // Save a lowercase version of the original word CharSequence typedWord = wordComposer.getTypedWord(); @@ -224,6 +224,7 @@ public class Suggest implements Dictionary.WordCallback { mLowerOriginalWord = ""; } + double normalizedScore = Integer.MIN_VALUE; if (wordComposer.size() == 1 && (mCorrectionMode == CORRECTION_FULL_BIGRAM || mCorrectionMode == CORRECTION_BASIC)) { // At first character typed, search only the bigrams @@ -236,16 +237,13 @@ public class Suggest implements Dictionary.WordCallback { prevWordForBigram = lowerPrevWord; } if (mUserBigramDictionary != null) { - mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this, - mNextLettersFrequencies); + mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this); } if (mContactsDictionary != null) { - mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this, - mNextLettersFrequencies); + mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this); } if (mMainDict != null) { - mMainDict.getBigrams(wordComposer, prevWordForBigram, this, - mNextLettersFrequencies); + mMainDict.getBigrams(wordComposer, prevWordForBigram, this); } char currentChar = wordComposer.getTypedWord().charAt(0); char currentCharUpper = Character.toUpperCase(currentChar); @@ -270,10 +268,10 @@ public class Suggest implements Dictionary.WordCallback { // At second character typed, search the unigrams (scores being affected by bigrams) if (mUserDictionary != null || mContactsDictionary != null) { if (mUserDictionary != null) { - mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies); + mUserDictionary.getWords(wordComposer, this); } if (mContactsDictionary != null) { - mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies); + mContactsDictionary.getWords(wordComposer, this); } if (mSuggestions.size() > 0 && isValidWord(typedWord) @@ -285,14 +283,14 @@ public class Suggest implements Dictionary.WordCallback { mHasAutoCorrection = true; } } - if (mMainDict != null) mMainDict.getWords(wordComposer, this, mNextLettersFrequencies); + if (mMainDict != null) mMainDict.getWords(wordComposer, this); if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM) && mSuggestions.size() > 0 && mPriorities.length > 0) { // TODO: when the normalized score of the first suggestion is nearly equals to // the normalized score of the second suggestion, behave less aggressive. - final double normalizedScore = Utils.calcNormalizedScore( + normalizedScore = Utils.calcNormalizedScore( typedWord, mSuggestions.get(0), mPriorities[0]); - if (LatinImeLogger.sDBG) { + if (DBG) { Log.d(TAG, "Normalized " + typedWord + "," + mSuggestions.get(0) + "," + mPriorities[0] + ", " + normalizedScore + "(" + mAutoCorrectionThreshold + ")"); @@ -308,7 +306,7 @@ public class Suggest implements Dictionary.WordCallback { if (typedWord != null) { mSuggestions.add(0, typedWord.toString()); } - if (mAutoTextEnabled) { + if (mQuickFixesEnabled) { int i = 0; int max = 6; // Don't autotext the suggestions from the dictionaries @@ -354,11 +352,30 @@ public class Suggest implements Dictionary.WordCallback { } } removeDupes(); - return new SuggestedWords.Builder().addWords(mSuggestions, null); - } - - public int[] getNextLettersFrequencies() { - return mNextLettersFrequencies; + if (DBG) { + ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList = + new ArrayList<SuggestedWords.SuggestedWordInfo>(); + frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false)); + final int priorityLength = mPriorities.length; + for (int i = 0; i < priorityLength; ++i) { + if (normalizedScore > 0) { + final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" + + normalizedScore + ")"; + frequencyInfoList.add( + new SuggestedWords.SuggestedWordInfo(priorityThreshold, false)); + normalizedScore = 0.0; + } else { + final String priority = Integer.toString(mPriorities[i]); + frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false)); + } + } + for (int i = priorityLength; i < mSuggestions.size(); ++i) { + frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false)); + } + return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList); + } else { + return new SuggestedWords.Builder().addWords(mSuggestions, null); + } } private void removeDupes() { @@ -392,12 +409,12 @@ public class Suggest implements Dictionary.WordCallback { return mHasAutoCorrection; } - private boolean compareCaseInsensitive(final String mLowerOriginalWord, + private static boolean compareCaseInsensitive(final String lowerOriginalWord, final char[] word, final int offset, final int length) { - final int originalLength = mLowerOriginalWord.length(); + final int originalLength = lowerOriginalWord.length(); if (originalLength == length && Character.isUpperCase(word[offset])) { for (int i = 0; i < originalLength; i++) { - if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) { + if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) { return false; } } diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java index 56ee5b9e7..c06bd736e 100644 --- a/java/src/com/android/inputmethod/latin/UserDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserDictionary.java @@ -126,9 +126,8 @@ public class UserDictionary extends ExpandableDictionary { } @Override - public synchronized void getWords(final WordComposer codes, final WordCallback callback, - int[] nextLettersFrequencies) { - super.getWords(codes, callback, nextLettersFrequencies); + public synchronized void getWords(final WordComposer codes, final WordCallback callback) { + super.getWords(codes, callback); } @Override @@ -138,7 +137,7 @@ public class UserDictionary extends ExpandableDictionary { private void addWords(Cursor cursor) { clearDictionary(); - + if (cursor == null) return; final int maxWordLength = getMaxWordLength(); if (cursor.moveToFirst()) { final int indexWord = cursor.getColumnIndex(Words.WORD); diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index e980d3a30..149c5ca9e 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -16,13 +16,17 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.KeyboardId; + import android.inputmethodservice.InputMethodService; import android.os.AsyncTask; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; +import android.text.InputType; import android.text.format.DateUtils; import android.util.Log; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; @@ -55,7 +59,7 @@ public class Utils { } public static class GCUtils { - private static final String TAG = "GCUtils"; + private static final String GC_TAG = GCUtils.class.getSimpleName(); public static final int GC_TRY_COUNT = 2; // GC_TRY_LOOP_MAX is used for the hard limit of GC wait, // GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT. @@ -84,7 +88,7 @@ public class Utils { Thread.sleep(GC_INTERVAL); return true; } catch (InterruptedException e) { - Log.e(TAG, "Sleep was interrupted."); + Log.e(GC_TAG, "Sleep was interrupted."); LatinImeLogger.logOnException(metaData, t); return false; } @@ -261,6 +265,19 @@ public class Utils { return dp[sl][tl]; } + // Get the current stack trace + public static String getStackTrace() { + StringBuilder sb = new StringBuilder(); + try { + throw new RuntimeException(); + } catch (RuntimeException e) { + StackTraceElement[] frames = e.getStackTrace(); + // Start at 1 because the first frame is here and we don't care about it + for (int j = 1; j < frames.length; ++j) sb.append(frames[j].toString() + "\n"); + } + return sb.toString(); + } + // In dictionary.cpp, getSuggestion() method, // suggestion scores are computed using the below formula. // original score (called 'frequency') @@ -268,13 +285,22 @@ public class Utils { // (the number of matched characters between typed word and suggested word)) // * (individual word's score which defined in the unigram dictionary, // and this score is defined in range [0, 255].) - // * (when before.length() == after.length(), - // mFullWordMultiplier (this is defined 2)) - // So, maximum original score is pow(2, before.length()) * 255 * 2 - // So, we can normalize original score by dividing this value. + // Then, the following processing is applied. + // - If the dictionary word is matched up to the point of the user entry + // (full match up to min(before.length(), after.length()) + // => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2) + // - If the word is a true full match except for differences in accents or + // capitalization, then treat it as if the frequency was 255. + // - If before.length() == after.length() + // => multiply by mFullWordMultiplier (this is defined 2)) + // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2 + // For historical reasons we ignore the 1.2 modifier (because the measure for a good + // autocorrection threshold was done at a time when it didn't exist). This doesn't change + // the result. + // So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2. private static final int MAX_INITIAL_SCORE = 255; private static final int TYPED_LETTER_MULTIPLIER = 2; - private static final int FULL_WORD_MULTIPLYER = 2; + private static final int FULL_WORD_MULTIPLIER = 2; public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) { final int beforeLength = before.length(); final int afterLength = after.length(); @@ -284,7 +310,7 @@ public class Utils { // correction. final double maximumScore = MAX_INITIAL_SCORE * Math.pow(TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength)) - * FULL_WORD_MULTIPLYER; + * FULL_WORD_MULTIPLIER; // add a weight based on edit distance. // distance <= max(afterLength, beforeLength) == afterLength, // so, 0 <= distance / afterLength <= 1 @@ -439,4 +465,24 @@ public class Utils { return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */); } } + + // Please refer to TextView.isPasswordInputType + public static boolean isPasswordInputType(int inputType) { + final int variation = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return (variation + == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD)) + || (variation + == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) + || (variation + == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD)); + } + + // Please refer to TextView.isVisiblePasswordInputType + public static boolean isVisiblePasswordInputType(int inputType) { + final int variation = + inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION); + return variation + == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + } } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 2e415b771..e003dcd5b 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -16,12 +16,16 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.keyboard.KeyDetector; + import java.util.ArrayList; /** * A place to store the currently composing word with information such as adjacent key codes as well */ public class WordComposer { + public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE; + /** * The list of unicode values for each keystroke (including surrounding keys) */ diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java index 61a194a8d..277ef7e65 100644 --- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java +++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java @@ -38,16 +38,13 @@ import android.os.IBinder; import android.preference.PreferenceManager; import android.provider.Browser; import android.speech.SpeechRecognizer; -import android.text.Layout; -import android.text.Selection; -import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.text.TextUtils; import android.text.method.LinkMovementMethod; -import android.text.style.ClickableSpan; import android.text.style.URLSpan; import android.util.Log; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -83,9 +80,8 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { private static final String IME_OPTION_NO_MICROPHONE = "nm"; private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6; - @SuppressWarnings("unused") - private static final String TAG = "VoiceIMEConnector"; - private static boolean DEBUG = LatinImeLogger.sDBG; + private static final String TAG = VoiceIMEConnector.class.getSimpleName(); + private static final boolean DEBUG = LatinImeLogger.sDBG; private boolean mAfterVoiceInput; private boolean mHasUsedVoiceInput; @@ -177,7 +173,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { return; } - AlertDialog.Builder builder = new AlertDialog.Builder(mService); + AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService); builder.setCancelable(true); builder.setIcon(R.drawable.ic_mic_dialog); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @@ -215,90 +211,80 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { mService.getText(R.string.voice_warning_how_to_turn_off)); } builder.setMessage(message); - builder.setTitle(R.string.voice_warning_title); mVoiceWarningDialog = builder.create(); - Window window = mVoiceWarningDialog.getWindow(); - WindowManager.LayoutParams lp = window.getAttributes(); + final Window window = mVoiceWarningDialog.getWindow(); + final WindowManager.LayoutParams lp = window.getAttributes(); lp.token = token; lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; window.setAttributes(lp); window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); mVoiceInput.logKeyboardWarningDialogShown(); mVoiceWarningDialog.show(); - // Make URL in the dialog message clickable - TextView textView = (TextView) mVoiceWarningDialog.findViewById(android.R.id.message); - if (textView != null) { - final CustomLinkMovementMethod method = CustomLinkMovementMethod.getInstance(); - method.setVoiceWarningDialog(mVoiceWarningDialog); - textView.setMovementMethod(method); - } } - private static class CustomLinkMovementMethod extends LinkMovementMethod { - private static CustomLinkMovementMethod sLinkMovementMethodInstance = - new CustomLinkMovementMethod(); + private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder { private AlertDialog mAlertDialog; - public void setVoiceWarningDialog(AlertDialog alertDialog) { - mAlertDialog = alertDialog; + public UrlLinkAlertDialogBuilder(Context context) { + super(context); } - public static CustomLinkMovementMethod getInstance() { - return sLinkMovementMethodInstance; + @Override + public AlertDialog.Builder setMessage(CharSequence message) { + return super.setMessage(replaceURLSpan(message)); + } + + private Spanned replaceURLSpan(CharSequence message) { + // Replace all spans with the custom span + final SpannableStringBuilder ssb = new SpannableStringBuilder(message); + for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) { + int spanStart = ssb.getSpanStart(span); + int spanEnd = ssb.getSpanEnd(span); + int spanFlags = ssb.getSpanFlags(span); + ssb.removeSpan(span); + ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags); + } + return ssb; } - // Almost the same as LinkMovementMethod.onTouchEvent(), but overrides it for - // FLAG_ACTIVITY_NEW_TASK and mAlertDialog.cancel(). @Override - public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { - int action = event.getAction(); - - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { - int x = (int) event.getX(); - int y = (int) event.getY(); - - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - - ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); - - if (link.length != 0) { - if (action == MotionEvent.ACTION_UP) { - if (link[0] instanceof URLSpan) { - URLSpan urlSpan = (URLSpan) link[0]; - Uri uri = Uri.parse(urlSpan.getURL()); - Context context = widget.getContext(); - Intent intent = new Intent(Intent.ACTION_VIEW, uri); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); - if (mAlertDialog != null) { - // Go back to the previous IME for now. - // TODO: If we can find a way to bring the new activity to front - // while keeping the warning dialog, we don't need to cancel here. - mAlertDialog.cancel(); - } - context.startActivity(intent); - } else { - link[0].onClick(widget); - } - } else if (action == MotionEvent.ACTION_DOWN) { - Selection.setSelection(buffer, buffer.getSpanStart(link[0]), - buffer.getSpanEnd(link[0])); + public AlertDialog create() { + final AlertDialog dialog = super.create(); + + dialog.setOnShowListener(new DialogInterface.OnShowListener() { + @Override + public void onShow(DialogInterface dialogInterface) { + // Make URL in the dialog message click-able. + TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message); + if (textView != null) { + textView.setMovementMethod(LinkMovementMethod.getInstance()); } - return true; - } else { - Selection.removeSelection(buffer); } + }); + mAlertDialog = dialog; + return dialog; + } + + class ClickableSpan extends URLSpan { + public ClickableSpan(String url) { + super(url); + } + + @Override + public void onClick(View widget) { + Uri uri = Uri.parse(getURL()); + Context context = widget.getContext(); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + // Add this flag to start an activity from service + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName()); + // Dismiss the warning dialog and go back to the previous IME. + // TODO: If we can find a way to bring the new activity to front while keeping + // the warning dialog, we don't need to dismiss it here. + mAlertDialog.cancel(); + context.startActivity(intent); } - return super.onTouchEvent(widget, buffer, event); } } @@ -729,7 +715,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { mHandler.updateVoiceResults(); } - public FieldContext makeFieldContext() { + private FieldContext makeFieldContext() { SubtypeSwitcher switcher = SubtypeSwitcher.getInstance(); return new FieldContext(mService.getCurrentInputConnection(), mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(), |